Browse Source

Merge branch 'develop3.0-tmd' of http://106.12.23.251:10080/TEAMMODEL/TEAMModelOS into develop3.0-tmd

liqk 4 years ago
parent
commit
b8bca45e03
38 changed files with 1018 additions and 1716 deletions
  1. 2 1
      TEAMModelOS.SDK/DI/AzureCosmos/AzureCosmosFactory.cs
  2. 17 5
      TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs
  3. 1 1
      TEAMModelOS/ClientApp/src/api/index.js
  4. 1 1
      TEAMModelOS/ClientApp/src/api/newEvaluation.js
  5. 31 23
      TEAMModelOS/ClientApp/src/components/learnactivity/GradeList.vue
  6. 3 3
      TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaper.less
  7. 3 1
      TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaper.vue
  8. 7 0
      TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaperList.less
  9. 148 182
      TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaperList.vue
  10. 0 5
      TEAMModelOS/ClientApp/src/router/routes.js
  11. 5 0
      TEAMModelOS/ClientApp/src/utils/editorTools.js
  12. 108 13
      TEAMModelOS/ClientApp/src/utils/evTools.js
  13. 3 14
      TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue
  14. 29 0
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.less
  15. 97 20
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  16. 39 12
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseChild.vue
  17. 32 72
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseCreateChild.vue
  18. 87 56
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseEditExercise.vue
  19. 23 54
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue
  20. 4 3
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseImport.vue
  21. 117 0
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseObjectivePie.vue
  22. 29 7
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseTypePie.vue
  23. 0 107
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreateCompose.css
  24. 0 331
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreateCompose.vue
  25. 64 18
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreateExercises.vue
  26. 0 232
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreateNewChild.vue
  27. 120 36
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue
  28. 1 0
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.less
  29. 6 2
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue
  30. 0 174
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaperList.css
  31. 0 25
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaperList.vue
  32. 1 1
      TEAMModelOS/ClientApp/src/view/evaluation/types/BaseSingle.vue
  33. 0 298
      TEAMModelOS/ClientApp/src/view/evaluation/types/BaseSingleNew.vue
  34. 0 1
      TEAMModelOS/ClientApp/src/view/learnactivity/ExamPaperAnalysis.vue
  35. 19 18
      TEAMModelOS/Controllers/Exam/ImportExerciseController.cs
  36. 4 0
      TEAMModelOS/Controllers/Syllabus/ItemInfoController.cs
  37. 16 0
      TEAMModelOS/Controllers/xTest/BlobController.cs
  38. 1 0
      TEAMModelOS/Models/CommonInfo/ItemInfo.cs

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

@@ -24,6 +24,7 @@ using System.Text.Json;
 using System.Threading;
 using TEAMModelOS.SDK.Context.Exception;
 using TEAMModelOS.SDK.DI;
+using Azure.Cosmos.Serialization;
 
 namespace TEAMModelOS.SDK.DI
 {
@@ -59,7 +60,7 @@ namespace TEAMModelOS.SDK.DI
         {
             try
             {
-                var cm = CosmosClients.GetOrAdd(name, x => new CosmosClient(_optionsMonitor.Get(name).ConnectionString, new CosmosClientOptions() { ApplicationRegion = region }));
+                var cm = CosmosClients.GetOrAdd(name, x => new CosmosClient(_optionsMonitor.Get(name).ConnectionString, new CosmosClientOptions() { ApplicationRegion = region, SerializerOptions = new CosmosSerializationOptions() { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase } }));
                 return cm;
             }
             catch (Exception e)

+ 17 - 5
TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs

@@ -10,6 +10,7 @@ using System;
 using System.IO;
 using Azure.Storage.Blobs.Specialized;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace TEAMModelOS.SDK.DI
 {
@@ -38,14 +39,13 @@ namespace TEAMModelOS.SDK.DI
         }
 
         /// <summary>
+        /// https://teammodelstorage.blob.core.chinacloudapi.cn/ydzt/112315401795%2F
         /// 批量刪除Blobs
-        /// 注意單個批次最多支持256個子請求。批處理請求的正文大小不能超過4MB,超過返回False
         /// </summary>      
         /// <param name="prefix">篩選開頭名稱,Null代表容器</param>        
         public static async Task<bool> DelectBlobs(this BlobServiceClient client, string blobContainerName, string prefix = null)
         {
             if (string.IsNullOrWhiteSpace(prefix)) return false;
-
             try
             {
                 BlobContainerClient bcc = client.GetBlobContainerClient(blobContainerName);
@@ -57,9 +57,21 @@ namespace TEAMModelOS.SDK.DI
                     urib.Path += "/" + item.Name;
                     blobs.Add(urib.Uri);
                 };
-
-                await bbc.DeleteBlobsAsync(blobs);
-                return true;
+                if (blobs.Count <= 256)
+                {
+                    await bbc.DeleteBlobsAsync(blobs);
+                    return true;
+                }
+                else
+                {
+                    int pages = (blobs.Count + 255) / 256; //256是批量操作最大值,pages = (total + max -1) / max;
+                    for (int i = 0; i < pages; i++)
+                    {
+                        List<Uri> lists = blobs.Skip((i) * 256).Take(256).ToList();
+                        await bbc.DeleteBlobsAsync(lists);
+                    }
+                    return true;
+                }
             }
             catch
             {

+ 1 - 1
TEAMModelOS/ClientApp/src/api/index.js

@@ -109,7 +109,7 @@ export default {
 
     // 新建试卷到试卷库
     SaveAnalyzeHtml: function (data) {
-        return post('/api/ImportExercise/AnalyzeHtml', data)
+        return post('/common/import/parse-html', data)
     },
 
     // 获取所有学校信息

+ 1 - 1
TEAMModelOS/ClientApp/src/api/newEvaluation.js

@@ -1,7 +1,7 @@
 import { fetch, post } from '@/api/http'
 export default {
     SaveAnalyzeHtml: function(data) {
-        return post('/api/ImportExercise/AnalyzeHtml', data)
+        return post('/common/import/parse-html', data)
     },
     /**
      * 更新保存试题

+ 31 - 23
TEAMModelOS/ClientApp/src/components/learnactivity/GradeList.vue

@@ -17,34 +17,38 @@
         <div class="content">
             <Loading :top="200" type="1" style="text-align:center" v-show="dataLoading"></Loading>
             <div class="left-box">
+                <!--<Steps :current="2"  direction="vertical" size="small">
+                    <Step >1</Step>
+                    <Step >2</Step>
+                    <Step >3</Step>
+                    <Step >4</Step>
+                </Steps>-->
                 <!--<div class="student-box">-->
-                    <!--<div class="search-box dark-iview-select ">-->
-                        <!--<Select placeholder="请选择查看班级..." v-model="classData" style="width: 90%; margin-left: 5px; margin-bottom: 5px" @on-change="getClassInfo">
-                            <Icon type="ios-contact" slot="prefix" />
-                            <Option v-for="(item,index) in classList" :value="item.id" :key="index">{{ item.name }}</Option>
-                        </Select>-->
-                        <!--<Input placeholder="查询学生姓名..." v-model="inputData" style="width:90%;margin-left:5px;margin-bottom:5px">
-                        <Icon type="ios-contact" slot="prefix" />
-                        </Input>-->
-                    <!--</div>
-                    <div class="student-list">
-                        <vuescroll>
-                            <ul>
-                                <li :class="index == selectIndex ?' block-bg-active block-bg':' block-bg'" :key="index" v-for="(item,index) in studentData.students" @click="getStudentInfo(item,index)">
-                                    <div class="student-show">
-                                        <span>{{item.name}}</span>
-                                        <span :style="{ background: (item.mark === 0 ? '#949594' : item.mark === 1 ? '#0BADD4' : '#1CC0F3')}" class="activity-status">{{item.mark === 0 ? '未开始' : item.mark === 2 ? '已完成' : '未完成'}}</span>
-                                    </div>
-                                </li>
-                            </ul>
-                        </vuescroll>
-                    </div>-->
+                <!--<div class="search-box dark-iview-select ">-->
+                <!--<Select placeholder="请选择查看班级..." v-model="classData" style="width: 90%; margin-left: 5px; margin-bottom: 5px" @on-change="getClassInfo">
+        <Icon type="ios-contact" slot="prefix" />
+        <Option v-for="(item,index) in classList" :value="item.id" :key="index">{{ item.name }}</Option>
+    </Select>-->
+                <!--<Input placeholder="查询学生姓名..." v-model="inputData" style="width:90%;margin-left:5px;margin-bottom:5px">
+    <Icon type="ios-contact" slot="prefix" />
+    </Input>-->
+                <!--</div>
+    <div class="student-list">
+        <vuescroll>
+            <ul>
+                <li :class="index == selectIndex ?' block-bg-active block-bg':' block-bg'" :key="index" v-for="(item,index) in studentData.students" @click="getStudentInfo(item,index)">
+                    <div class="student-show">
+                        <span>{{item.name}}</span>
+                        <span :style="{ background: (item.mark === 0 ? '#949594' : item.mark === 1 ? '#0BADD4' : '#1CC0F3')}" class="activity-status">{{item.mark === 0 ? '未开始' : item.mark === 2 ? '已完成' : '未完成'}}</span>
+                    </div>
+                </li>
+            </ul>
+        </vuescroll>
+    </div>-->
                 <!--</div>-->
             </div>
             <div class="grade-box">
-                <vuescroll>
                     <ReviewPaper :paper="paperInfo" :answer="answerInfo" ref="exPaper" style="color:#515a6e;" :isShowTools="isShowTools"> </ReviewPaper>
-                </vuescroll>
             </div>
         </div>
     </div>
@@ -127,6 +131,9 @@
                     }
                 }
             },
+            handleItemClick(data) {
+                this.$refs.exPaper.handleItemClick(data)
+            },
             openAnswer() {
                 this.$refs.exPaper.onHandleToggles(this.isAllOpen)
                 this.isAllOpen = !this.isAllOpen
@@ -301,6 +308,7 @@
                     if (newV.classId) {
                         this.getClassInfo()
                         this.getClassData(newV.classId)
+                        this.handleItemClick(newV.queNo)
                         console.log(newV)
                     }
                 },

+ 3 - 3
TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaper.less

@@ -123,13 +123,13 @@
 .paper-content {
     width: 100%;
     background: #fff;
-    padding: 20px;
+    padding: 10px;
     display: flex;
     flex-direction: row;
 
     .paper-body {
-        width: 94%;
-        padding-left: 50px;
+        width: 100%;
+        padding-left: 10px;
 
         .paper-header {
             margin-top: 20px;

+ 3 - 1
TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaper.vue

@@ -51,7 +51,9 @@
                 this.$refs.exList.onHandleToggle(this.isAllOpen)
                 this.isAllOpen = !this.isAllOpen
             },
-
+            handleItemClick(data) {
+                this.$refs.exList.handleItemClick(1)
+            },
             /**
              * 计算试卷题目平均难度
              * @param arr 试题集合

+ 7 - 0
TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaperList.less

@@ -30,3 +30,10 @@
     margin-left: 10px;
     color: rgb(0, 173, 37);
 }
+.content-wrap{
+    width:100%;
+    height:60%;
+}
+.components-el-container{
+
+}

+ 148 - 182
TEAMModelOS/ClientApp/src/components/learnactivity/ReviewPaperList.vue

@@ -1,153 +1,158 @@
 <template>
 	<div class="components-el-container">
+
 		<!-- 题目列表部分 -->
 		<!--<div v-if="exerciseList.length === 0" class="no-data-text">
-			<img src="../../assets/icon/no_data.svg" width="120" />
-			<span style="margin-top:15px;color:#808080">请先选择学生信息</span>
-		</div>-->
+		<img src="../../assets/icon/no_data.svg" width="120" />
+		<span style="margin-top:15px;color:#808080">请先选择学生信息</span>
+	</div>-->
 		<Loading :top="200" type="1" style="text-align:center" v-if="dataLoading"></Loading>
-		<div class="content-wrap" ref="mathJaxContainer" v-if="!dataLoading">
-			<Loading :top="200" v-show="dataLoading" type="1"></Loading>
-			<div class="list-view" :key="typeIndex" v-for="(typeItem,typeIndex) in listData">
-				<p v-show="viewModel === 'type' && typeItem.list.length" class="type-name">
-					{{ numberConvertToUppercase(getLatestTypeIndex(typeItem.type) + 1) }}
-					 {{ exersicesType[typeItem.type] }}
-					({{ typeItem.score || 0 }} 分)
-				</p>
-
-				<div v-for="(item,index) of typeItem.list" :key="index" :class='["exercise-item",isError(item.id) ? "exercise-item-error":""]'
-					 @mouseenter="exerciseMouseover($event)" @mouseleave="exerciseMouseleave($event)">
-
-					<!--工具栏部分-->
-					<div class="item-tools-wrap"></div>
-					<!--<div class="item-error-wrap" v-if="errorList.indexOf(item) > -1">
+		<vuescroll ref="que">
+
+			<div class="content-wrap" ref="mathJaxContainer" v-if="!dataLoading">
+				<Loading :top="200" v-show="dataLoading" type="1"></Loading>
+				<div class="list-view" :key="typeIndex" v-for="(typeItem,typeIndex) in listData">
+					<p v-show="viewModel === 'type' && typeItem.list.length" class="type-name">
+						{{ numberConvertToUppercase(getLatestTypeIndex(typeItem.type) + 1) }}
+						{{ exersicesType[typeItem.type] }}
+						({{ typeItem.score || 0 }} 分)
+					</p>
+
+					<div ref="ques" v-for="(item,index) of typeItem.list" :key="index" :class='["exercise-item",isError(item.id) ? "exercise-item-error":""]'
+						 @mouseenter="exerciseMouseover($event)" @mouseleave="exerciseMouseleave($event)">
+						<!--工具栏部分-->
+						<div class="item-tools-wrap"></div>
+						<!--<div class="item-error-wrap" v-if="errorList.indexOf(item) > -1">
 						<span v-if="isNoAnswer(item)">请为当前客观题设置正确答案!</span>
 						<span v-else>试题题干或选项信息有误,请删除或者重新导入正确文档!</span>
 					</div>-->
 
-					<div @click="onQuestionToggle(exerciseList.indexOf(item),item.id,$event,typeItem.list)">
-						<!-- 题干部分 -->
-						<div class="item-question">
-							<div v-if="viewModel === 'list'">
-								<div class="item-question-order">{{ index + 1 }}  </div>
-								<div class="item-question-text" v-html="item.question"></div>
+						<div @click="onQuestionToggle(exerciseList.indexOf(item),item.id,$event,typeItem.list)">
+							<!-- 题干部分 -->
+							<div class="item-question">
+								<div v-if="viewModel === 'list'">
+									<div class="item-question-order">{{ index + 1 }}  </div>
+									<div class="item-question-text" v-html="item.question"></div>
+								</div>
+								<div v-else>
+									<div class="item-question-order">{{ pageSize * (pageNum - 1) + index + 1 }}. </div>
+									<div class="item-question-text" v-html="item.question" @click="onRichTextClick($event)"></div>
+								</div>
 							</div>
-							<div v-else>
-								<div class="item-question-order">{{ pageSize * (pageNum - 1) + index + 1 }} . </div>
-								<div class="item-question-text" v-html="item.question" @click="onRichTextClick($event)"></div>
+							<!-- 选项部分 -->
+							<div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="item-options">
+								<div class="item-option-content">
+									<div class="item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}} :</div>
+									<div class="item-option-text" v-html="option.value" @click="onRichTextClick($event)"></div>
+								</div>
 							</div>
 						</div>
-						<!-- 选项部分 -->
-						<div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="item-options">
-							<div class="item-option-content">
-								<div class="item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}} :</div>
-								<div class="item-option-text" v-html="option.value" @click="onRichTextClick($event)"></div>
-							</div>
+						<div class="item-btn-toggle" @click.stop>
+							<template>
+								<!--<InputNumber :max="item.score + surPlusScore" :min="0" v-model="item.score" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;"
+							@click.stop></InputNumber>-->
+								<span style="margin-right: 20px;">({{ item.score }} 分)</span>
+								<!-- <span class="item-score" title="设置题目分数" @click.stop="onSetSingleItem(item,index)" v-else>{{ item.score }} 分</span> -->
+							</template>
+							<Icon :type="collapseList.indexOf(index) > -1 ? 'ios-arrow-dropup' : 'ios-arrow-dropdown'" />
 						</div>
-					</div>
-					<div class="item-btn-toggle" @click.stop>
-						<template>
-							<!--<InputNumber :max="item.score + surPlusScore" :min="0" v-model="item.score" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;"
-						@click.stop></InputNumber>-->
-							<span style="margin-right: 20px;">({{ item.score }} 分)</span>
-							<!-- <span class="item-score" title="设置题目分数" @click.stop="onSetSingleItem(item,index)" v-else>{{ item.score }} 分</span> -->
-						</template>
-						<Icon :type="collapseList.indexOf(index) > -1 ? 'ios-arrow-dropup' : 'ios-arrow-dropdown'" />
-					</div>
 
-					<div class="item-explain">
-						<span class="answer-title">【作 答 答 案】</span>
-						<div class="item-explain-details">
-							<!--问答题答案-->
-							<div v-if="item.type === 'subjective'">
-								<div v-for="(answer,index) in item.answerData" :key="index">
-									<span v-html="item.answerData.length > 0 ? answer : '暂未作答'"></span><br />
-								</div>
-								<div class="answer-box">
-									<span class="answer-scores">【题 目 分 数】</span>
-									<!--<InputNumber v-model="answer.score" placeholder="请输入题目分数..." @on-change="inputScore(answer)" style=" width: 150px; margin-top: -5px;" />-->
-								</div>
-							</div>
-							<!--填空题答案-->
-							<div v-else-if="item.type === 'complete'">
-								<div v-for="(answer,index) in item.answerData" :key="index">
-									<div :class="[ item.type === 'complete' ? 'item-answer-items':'']">
-										<p>{{index+1}}:</p><span v-html="answer"></span><br />
+						<div class="item-explain">
+							<span class="answer-title">【作 答 答 案】</span>
+							<div class="item-explain-details">
+								<!--问答题答案-->
+								<div v-if="item.type === 'subjective'">
+									<div v-for="(answer,index) in item.answerData" :key="index">
+										<span v-html="item.answerData.length > 0 ? answer : '暂未作答'"></span><br />
 									</div>
 									<div class="answer-box">
-										<span class="answer-scores">【题 目 分 数 】</span>
-										<InputNumber v-model="item.stuScore" placeholder="请输入题目分数..." @on-blur="inputScore(item)" style=" width: 150px; margin-top: 5px;" />
+										<span class="answer-scores">【题 目 分 数】</span>
+										<!--<InputNumber v-model="answer.score" placeholder="请输入题目分数..." @on-change="inputScore(answer)" style=" width: 150px; margin-top: -5px;" />-->
 									</div>
 								</div>
-							</div>
-							<!--其余题型答案-->
-							<div v-else>
-								<div style="display:flex;margin-left:5px">
-									<span style="margin-left:5px" v-for="(answer,index) in item.answerData" :key="index">{{item.answerData.length > 0 ? answer : "暂未作答"}}</span>
+								<!--填空题答案-->
+								<div v-else-if="item.type === 'complete'">
+									<div v-for="(answer,index) in item.answerData" :key="index">
+										<div :class="[ item.type === 'complete' ? 'item-answer-items':'']">
+											<p>{{index+1}}:</p><span v-html="answer"></span><br />
+										</div>
+										<div class="answer-box">
+											<span class="answer-scores">【题 目 分 数 】</span>
+											<InputNumber v-model="item.stuScore" placeholder="请输入题目分数..." @on-blur="inputScore(item)" style=" width: 150px; margin-top: 5px;" />
+										</div>
+									</div>
 								</div>
-								<div class="answer-box">
-									<span class="answer-score">【题 目 分 数】</span>
-									<InputNumber v-model="item.stuScore" placeholder="请输入题目分数..." @on-blur="inputScore(item)" style=" width: 150px; margin-top: -5px;margin-right:10px;" />
-									<Button size="small" v-if="item.stuScore != -1" type="success">修改</Button>
+								<!--其余题型答案-->
+								<div v-else>
+									<div style="display:flex;margin-left:5px">
+										<span style="margin-left:5px" v-for="(answer,index) in item.answerData" :key="index">{{item.answerData.length > 0 ? answer : "暂未作答"}}</span>
+									</div>
+									<div class="answer-box">
+										<span class="answer-score">【题 目 分 数】</span>
+										<InputNumber v-model="item.stuScore" placeholder="请输入题目分数..." @on-blur="inputScore(item)" style=" width: 150px; margin-top: -5px;margin-right:10px;" />
+										<Button size="small" v-if="item.stuScore != -1" type="success">修改</Button>
+									</div>
 								</div>
 							</div>
 						</div>
-					</div>
-					<!-- 答案以及解析 -->
-					<transition name="slide">
-						<!-- <div v-show="collapseList.indexOf(exerciseList.indexOf(item)) > -1" class="toggle-area"> -->
-						<div v-show="collapseList.indexOf(exerciseList.indexOf(item)) > -1" class="toggle-area">
-							<div v-if="item.type !== 'compose'">
-								<!-- 答案展示部分 -->
-								<div class="item-explain" v-show="isShowAnswer">
-									<span class="explain-title">【答ㅤ案】</span>
-									<div class="item-explain-details" @click="onRichTextClick($event)">
-										<!-- 问答题答案 -->
-										<div v-if="item.type === 'subjective'">
-											<span v-for="(answer,index) in item.answer" :key="index" v-html="item.answer.length ? answer : '未设置答案'"></span>
-										</div>
-										<!-- 填空题答案 -->
-										<div v-else-if="item.type === 'complete'">
-											<span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer"
-												  :key="index" v-html="answer"></span>
-										</div>
-										<!-- 其余题型答案 -->
-										<div v-else>
-											<span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer"
-												  :key="index">{{answer}}</span>
+						<!-- 答案以及解析 -->
+						<transition name="slide">
+							<!-- <div v-show="collapseList.indexOf(exerciseList.indexOf(item)) > -1" class="toggle-area"> -->
+							<div v-show="collapseList.indexOf(exerciseList.indexOf(item)) > -1" class="toggle-area">
+								<div v-if="item.type !== 'compose'">
+									<!-- 答案展示部分 -->
+									<div class="item-explain" v-show="isShowAnswer">
+										<span class="explain-title">【答ㅤ案】</span>
+										<div class="item-explain-details" @click="onRichTextClick($event)">
+											<!-- 问答题答案 -->
+											<div v-if="item.type === 'subjective'">
+												<span v-for="(answer,index) in item.answer" :key="index" v-html="item.answer.length ? answer : '未设置答案'"></span>
+											</div>
+											<!-- 填空题答案 -->
+											<div v-else-if="item.type === 'complete'">
+												<span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer"
+													  :key="index" v-html="answer"></span>
+											</div>
+											<!-- 其余题型答案 -->
+											<div v-else>
+												<span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer"
+													  :key="index">{{answer}}</span>
+											</div>
 										</div>
 									</div>
-								</div>
-								<!-- 解析部分 -->
-								<div class="item-explain" v-show="isShowAnswer">
-									<span class="explain-title">【解ㅤ析】</span>
-									<div class="item-explain-details">
-										<span v-html="item.explain || '暂无解析'" @click="onRichTextClick($event)"></span>
+									<!-- 解析部分 -->
+									<div class="item-explain" v-show="isShowAnswer">
+										<span class="explain-title">【解ㅤ析】</span>
+										<div class="item-explain-details">
+											<span v-html="item.explain || '暂无解析'" @click="onRichTextClick($event)"></span>
+										</div>
 									</div>
-								</div>
-								<!-- 知识点部分 -->
-								<div class="item-explain" v-show="isShowAnswer">
-									<span class="explain-title">【知识点】</span>
-									<div class="item-explain-details">
-										<span v-if="! (_.compact(item.points).length)">暂未绑定知识点</span>
-										<div v-else>
-											<span v-for="(point,index) in item.points" :key="index" class="item-point-tag">
-												{{ point }}
-											</span>
+									<!-- 知识点部分 -->
+									<div class="item-explain" v-show="isShowAnswer">
+										<span class="explain-title">【知识点】</span>
+										<div class="item-explain-details">
+											<span v-if="! (_.compact(item.points).length)">暂未绑定知识点</span>
+											<div v-else>
+												<span v-for="(point,index) in item.points" :key="index" class="item-point-tag">
+													{{ point }}
+												</span>
+											</div>
 										</div>
 									</div>
 								</div>
+								<!-- 如果是综合题 则加载子题渲染组件 -->
+								<div v-else>
+									<BaseChild :children="item.children"></BaseChild>
+								</div>
+
 							</div>
-							<!-- 如果是综合题 则加载子题渲染组件 -->
-							<div v-else>
-								<BaseChild :children="item.children"></BaseChild>
-							</div>
+						</transition>
+					</div>
+
 
-						</div>
-					</transition>
 				</div>
 			</div>
-		</div>
+		</vuescroll>
 		<!-- 音频播放弹窗 -->
 		<Modal v-model="playAudioModal" width="400" footer-hide @on-visible-change="onAudioModalChange">
 			<div class="modal-header" slot="header">音频播放</div>
@@ -160,7 +165,6 @@
 			<BaseVideoPlayer :videoSrc="curVideoSrc" :audioName="curVideoName" ref="videoPlayer"></BaseVideoPlayer>
 		</Modal>
 
-
 	</div>
 </template>
 
@@ -277,7 +281,19 @@
                     this.$Message.warning('请完成题目评分!')
                 }
             },
-
+            // 点击右边题序 获取到题目DOM 进行滚动操作
+			handleItemClick(item) {
+				let questionList = this.$refs["ques"]
+				let questionDom = questionList[item]
+                let scrollDistance = 0
+			    scrollDistance = questionDom.offsetTop - 20 // 相对父级容器高度加上头部高度即为滚动距离
+                this.$refs["que"].scrollTo(
+                            {
+                            y: scrollDistance
+                            },
+                            500, 'easeInQuad'
+                        )
+            },
 			/* 音频弹窗切换事件 */
 			onAudioModalChange(val) {
 				if (!val) {
@@ -375,66 +391,6 @@
 				e.target.children[0].style.display = 'none'
 			},
 
-			/**
-			 * 给单个试题配分
-			 * @param item
-			 * @param index
-			 */
-			handleSetScore(item, index, arr, groupIndex) {
-				this.currentExerciseIndex = index
-				this.curIndex = groupIndex
-				this.curTypeItems = arr
-				this.curItemScore = item.score
-				this.lastScore = item.score
-				this.scoreModal = true
-			},
-
-			/**
-			 * 配分变化时的剩余分数处理
-			 * @param val
-			 */
-			onScoreChange(val) {
-				this.surPlusScore = this.surPlusScore + this.lastScore - val
-				this.lastScore = val
-				this.$emit('scoreUpdate', this.surPlusScore)
-			},
-
-			/** 确认单个试题配分 */
-			onConfirmScore() {
-				this.$set(this.exerciseList[this.currentExerciseIndex], 'score', this.curItemScore);
-				this.$set(this.curTypeItems[this.curIndex], 'score', this.curItemScore);
-			},
-
-			/** 编辑成功 */
-			onEditSuccess(item) {
-				if (item.id) {
-					this.$refs.paperEdit.isLoading = false
-					this.editExerciseModal = false
-					this.$Message.success("修改成功!")
-					this.exerciseList.splice(this.currentExerciseIndex, 1, item)
-					this.curTypeItems.splice(this.curIndex, 1, item)
-					this.$emit('dataUpdate', this.exerciseList)
-
-					let existIndex = this.modifyItems.map(i => i.id).indexOf(item.id)
-					if (existIndex < 0) {
-						this.modifyItems.push(item)
-					} else {
-						this.modifyItems[existIndex] = item
-					}
-
-				} else {
-					this.editExerciseModal = false
-					this.exerciseList.splice(this.currentExerciseIndex, 1, item)
-					this.curTypeItems.splice(this.curIndex, 1, item)
-					let listIndex = this.collapseList.indexOf(this.currentExerciseIndex);
-					if (listIndex > -1) {
-						this.collapseList.splice(listIndex, 1)
-					}
-					this.$Message.success("修改成功!")
-				}
-			},
-
-
 			/**
 			 * 全部展开与全部折叠
 			 * @param isAllOpen
@@ -455,8 +411,6 @@
 				})
 				return arr.indexOf(type)
 			}
-
-
 		},
 		mounted() {
 			this.dataLoading = false
@@ -550,6 +504,18 @@
 @import "../../view/evaluation/index/PickExercise.css";
 </style>
 <style scoped>
+    .components-el-container{
+		width:100%;
+		height:50%;
+    }
+    .content-wrap{
+		width:100%;
+		height:100%;
+    }
+    .list-view{
+		width:100%;
+		height:100%;
+    }
     .components-el-container .ivu-page {
         display: flex;
         flex-direction: row;

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

@@ -244,11 +244,6 @@ export const routes = [
 						path: 'personalBank',
 						name: 'personalBank',
 						component: resolve => require(['@/view/evaluation/bank/index.vue'], resolve)
-					},
-					{
-						path: 'createCompose',
-						name: 'createCompose',
-						component: resolve => require(['@/view/evaluation/index/CreateCompose.vue'], resolve)
 					}
 				]
 			},

+ 5 - 0
TEAMModelOS/ClientApp/src/utils/editorTools.js

@@ -110,6 +110,11 @@ export default {
 	},
 
 	addAudio(vm, editor) {
+		
+		editor.config.uploadImgMaxSize = 2 * 1024 * 1024 // 2M
+		editor.config.uploadImgShowBase64 = true;
+		editor.config.uploadImgMaxLength = 5 // 一次最多上传 5 个图片
+		editor.config.showLinkImg = false
 		// 获取必要的变量,这些在下文中都会用到
 		const {
 			$,

+ 108 - 13
TEAMModelOS/ClientApp/src/utils/evTools.js

@@ -9,10 +9,12 @@ export default {
 		return new Promise((r,j) => {
 			let itemJson = {
 				id:item.id,
+				pId:item.pId || '',
 				exercise:{
 					answer:item.answer,
 					explain:item.explain,
 					type:item.type,
+					opts:item.option.length,
 					points:item.points,
 					field:item.field,
 					level:item.level,
@@ -39,6 +41,7 @@ export default {
 		return new Promise((r,j) => {
 			let cosmosItem  = {
 				id:item.id,
+				pId:item.pId || '',
 				code:item.code,
 				scope:item.scope,
 				score:0,
@@ -57,7 +60,7 @@ export default {
 	},
 	
 	/* 生成试卷的index.json文件格式 */
-	createBlobPaper(paper,urls){
+	createBlobPaper(paper,slides){
 		return new Promise((r,j) => {
 			let paperItem  = {
 				id:paper.id,
@@ -65,8 +68,7 @@ export default {
 				code:paper.code,
 				scope:paper.scope,
 				multipleRule:paper.multipleRule,
-				urls:urls,
-				scoring:paper.scoring,
+				slides:slides,
 				points:paper.points,
 				periodId:paper.periodId,
 				gradeIds:paper.gradeIds,
@@ -100,6 +102,7 @@ export default {
 		})
 	},
 	
+	
 	/* 获取完整的试题数据 */
 	getFullItem(list){
 		return new Promise(async (resolve,reject) => {
@@ -112,6 +115,11 @@ export default {
 						let sasString = list[i].scope === 'school' ?  await $tools.getSchoolSas() : await $tools.getPrivateSas()
 						let jsonInfo = await $tools.getFile(blobHost + list[i].blob + sasString.sas)
 						let jsonData = JSON.parse(jsonInfo)
+						// 如果是综合题 那就拿到children里面的小题id集合 去换取小题的blobJSON文件 然后替换children的内容
+						if(jsonData.exercise.children.length && jsonData.exercise.type === 'compose'){
+							let childrenUrls = jsonData.exercise.children.map(i => blobHost + '/item/' + i + '.json' + sasString.sas)
+							jsonData.exercise.children = await this.getFullChildren(childrenUrls,list[i].code)
+						}
 						// 调整渲染试题数据结构
 						jsonData.id = list[i].id
 						jsonData.exercise.question = jsonData.item[0].question
@@ -131,6 +139,76 @@ export default {
 		})
 	},
 	
+	saveChildren(children,containerClient){
+		return new Promise((resolve,reject) => {
+			let promiseArr = []
+			let itemJsonFiles = []
+			children.forEach(exerciseItem => {
+				promiseArr.push(new Promise(async (r,j) => {
+					// 将当前的试题数据转化为BLOB内部的试题JSON格式
+					const itemJsonFile = await this.createBlobItem(exerciseItem)
+					const cosmosItem = await this.createCosmosItem(exerciseItem)
+					// 首先保存新题目的JSON文件到Blob 然后返回URL链接
+					let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+					try{
+						// 等待上传blob的返回结果
+						let blobFile = await containerClient.upload(file, 'item')
+						if (blobFile.blob) {
+							// 保存试题JSON文件到试卷文件夹需要
+							itemJsonFiles.push(file)
+							// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+							cosmosItem.blob = blobFile.blob
+							// 保存当前试题到数据库
+							that.saveExercise(cosmosItem).then(res => {
+								r(res.itemInfo)
+							})
+						}else{
+							j(500)
+						}
+					}catch(e){
+						j(500)
+					}
+				}))
+			})
+			Promise.all(promiseArr).then(result => {
+				if(result.length){
+					resolve(itemJsonFiles)
+				}else{
+					resolve([])
+				}
+			})
+			
+		})
+		
+	},
+	
+	/* 获取综合题子题的Blob数据 */
+	getFullChildren(urls,code){
+		return new Promise((resolve,reject) => {
+			let promiseArr = []
+			urls.forEach(url => {
+				promiseArr.push(new Promise(async (r,j) => {
+					let jsonData = JSON.parse(await $tools.getFile(url))
+					// 调整渲染试题数据结构
+					jsonData.exercise.question = jsonData.item[0].question
+					jsonData.exercise.blob = url
+					jsonData.exercise.code = code
+					jsonData.exercise.option = jsonData.item[0].option
+					jsonData.exercise.id = jsonData.id
+					r(jsonData.exercise)
+				}))
+			})
+			
+			Promise.all(promiseArr).then(result => {
+				if(result.length){
+					resolve(result)
+				}else{
+					resolve([])
+				}
+			})
+		})
+	},
+	
 	/* 获取完整的试卷数据 */
 	getFullPaper(paper){
 		return new Promise(async (r,j) => {
@@ -141,20 +219,37 @@ export default {
 				let jsonInfo = await $tools.getFile(blobHost + paper.blob + '/index.json' + sasString.sas)
 				let jsonData = JSON.parse(jsonInfo)
 				// 获取试卷包含的试题数据并包装好
-				if(jsonData.urls && jsonData.urls.length){
+				if(jsonData.slides && jsonData.slides.length){
+					let promiseArr = []
+					let allItems = []
 					jsonData.item = []
 					const path = blobHost + paper.blob
-					jsonData.urls.forEach(async (itemUrl,index) => {
-						// 获取题目JSON并且包装成完整试题对象
-						let itemJson = JSON.parse(await $tools.getFile(path + '/' + itemUrl + sasString.sas))
-						itemJson.exercise.question = itemJson.item[0].question
-						itemJson.exercise.option = itemJson.item[0].option
-						itemJson.exercise.id = itemJson.id 
-						itemJson.exercise.score = jsonData.scoring[index].score
-						jsonData.item.push(itemJson.exercise)
+					jsonData.slides.forEach(async (item,index) => {
+						promiseArr.push(new Promise(async (resolve,reject) => {
+							// 获取题目JSON并且包装成完整试题对象
+							let itemJson = JSON.parse(await $tools.getFile(path + '/' + item.url + sasString.sas))
+							itemJson.exercise.question = itemJson.item[0].question
+							itemJson.exercise.option = itemJson.item[0].option
+							itemJson.exercise.id = itemJson.id 
+							itemJson.exercise.pId = itemJson.pId 
+							itemJson.exercise.score = item.scoring ? item.scoring.score : 0,
+							resolve(itemJson.exercise)
+						}))
+					})
+					
+					Promise.all(promiseArr).then(res => {
+						res.forEach((resItem,resIndex) => {
+							resItem.children = []
+							if(resItem.pId){
+								let pItem = res.filter(i => i.id === resItem.pId)[0]
+								pItem.children.push(resItem)
+								pItem.score = pItem.score + resItem.score
+							}
+						})
+						jsonData.item = res.filter(i => !i.pId)
+						r(jsonData)
 					})
 				}
-				r(jsonData)
 			}catch(e){
 				j(e)
 			}

+ 3 - 14
TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue

@@ -170,9 +170,9 @@
 					<!--<span class="item-tools-info" style="border:0">更新时间:{{ formatDateTime(new Date(item.createTime * 1000)) }}</span>-->
 					<!--<Button type="info" :style="{backgroundColor:basketList.all.indexOf(item) > -1 ? '#bbbbbb' : ''}" @click="handleChoose(item)">{{basketList.all.indexOf(item) > -1 ? '已选入' : '选题'}} </Button>-->
 
-					<Button type="text" @click.stop="handleEdit(item)" v-if="$access.can('admin.*|teacher.*|Exercise_Edit')" icon="md-create"
+					<Button type="text" @click.stop="handleEdit(item)" v-if="($access.can('admin.*||exercise-upd') ||  (filterOrigin !== schoolCode))" icon="md-create"
 					 style="margin-right: 10px">编辑</Button>
-					<Button type="text" @click.stop="handleDelete(item)" v-if="$access.can('admin.*|teacher.*|Exercise_Edit')" icon="md-trash"
+					<Button type="text" @click.stop="handleDelete(item)" v-if="($access.can('admin.*||exercise-upd') ||  (filterOrigin !== schoolCode))" icon="md-trash"
 					 style="margin-right: 10px">删除</Button>
 				</div>
 			</div>
@@ -188,18 +188,6 @@
 			</div>
 		</Modal>
 
-		<!-- 音频播放弹窗 -->
-		<Modal v-model="playAudioModal" width="400" footer-hide @on-visible-change="onAudioModalChange">
-			<div class="modal-header" slot="header">音频播放</div>
-			<BaseAudioPlayer :audioSrc="curAudioSrc" :audioName="curAudioName" ref="audioPlayer"></BaseAudioPlayer>
-		</Modal>
-
-		<!-- 视频播放弹窗 -->
-		<Modal v-model="playVideoModal" width="400" footer-hide @on-visible-change="onVideoModalChange">
-			<div class="modal-header" slot="header">视频播放</div>
-			<BaseVideoPlayer :videoSrc="curVideoSrc" :audioName="curVideoName" ref="videoPlayer"></BaseVideoPlayer>
-		</Modal>
-
 		<!-- 底部分页区域 -->
 		<Page :total="totalNum" show-sizer show-total :page-size="pageSize" :current="pageNum" @on-page-size-change="pageSizeChange"
 		 @on-change="pageChange" :page-size-opts="[5, 10, 15, 20]" />
@@ -379,6 +367,7 @@
 					type: this.deleteFalse(this.filterType),
 					field: this.deleteFalse(this.filterField),
 					scope: this.curScope,
+					pId:''
 				};
 				this.getExerciseList(this.filterParams);
 			},

+ 29 - 0
TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.less

@@ -187,6 +187,35 @@
             }
         }
     }
+	
+	
+	.pl-review-wrap{
+		width: 100%;
+		display: flex;
+		
+		&-left{
+			width: 75%;
+		}
+		
+		&-right{
+			position: relative;
+			flex:1;
+			margin: 30px 0 0 30px;
+			background-color: #fff;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding: 30px 20px 0 20px;
+			
+			.btn-back-list{
+				position: absolute;
+				right: 20px;
+				top: 10px;
+				color: gray;
+				cursor: pointer;
+			}
+		}
+	}
 }
 
 .no-data-text {

+ 97 - 20
TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue

@@ -12,27 +12,50 @@
 		<!-- 试卷列表页面 -->
 		<div class="pl-content-wrap" v-else>
 			<Loading :top="100" v-show="dataLoading" type="1" hideMask></Loading>
-			<div class="paper-item" v-for="(paper,index) in paperList" :key="index">
-				<div class="paper-item-name">
-					<span class="paper-item-tag" v-if="isSchool">{{ getSubjectName(paper.subjectId) }}</span>
-					<span style="margin-left: 8px;">{{ paper.name }}</span>
+			<div style="width: 100%;" v-if="!isPreview">
+				<div class="paper-item" v-for="(paper,index) in paperList" :key="index">
+					<div class="paper-item-name">
+						<span class="paper-item-tag" v-if="isSchool">{{ getSubjectName(paper.subjectId) }}</span>
+						<span style="margin-left: 8px;">{{ paper.name }}</span>
+					</div>
+					<div class="paper-item-info">
+						<span class="info-item" v-if="isSchool">适用学段:<span class="info-bold">{{ getPeriodName(paper.periodId) }}</span></span>
+						<span class="info-item" v-if="isSchool">适用年级:<span class="info-bold" v-for="(grade,gIndex) in paper.gradeIds" :key="gIndex">{{ getGradeName(paper.periodId,grade) }}
+								<span v-show="gIndex !== paper.gradeIds.length - 1"> / </span></span></span>
+						<span class="info-item">题量:<span class="info-bold">{{ paper.scoring ? paper.scoring.length : 0 }}</span></span>
+						<!-- <span class="info-item">难度系数:<span class="info-bold">{{ paper.item ? handleDiffCalc(paper.item) : 0 }}</span></span> -->
+					</div>
+					<div class="paper-item-tools">
+						<span class="paper-item-tools-edit" @click="onPreviewPaper(paper)">
+							<Icon type="ios-create" />
+							<span>预览</span>
+						</span>
+						<span class="paper-item-tools-edit" @click="goToPaper(paper)" v-if="($access.can('admin.*||exercise-upd') || !isSchool)">
+							<Icon type="ios-create" />
+							<span>编辑</span>
+						</span>
+						<span class="paper-item-tools-delete" @click.stop="onDeletePaper(paper)" v-if="($access.can('admin.*||exercise-upd') || !isSchool)">
+							<Icon type="md-trash" />
+							<span>删除</span>
+						</span>
+					</div>
 				</div>
-				<div class="paper-item-info">
-					<span class="info-item" v-if="isSchool">适用学段:<span class="info-bold">{{ getPeriodName(paper.periodId) }}</span></span>
-					<span class="info-item" v-if="isSchool">适用年级:<span class="info-bold" v-for="(grade,gIndex) in paper.gradeIds" :key="gIndex">{{ getGradeName(paper.periodId,grade) }}
-							<span v-show="gIndex !== paper.gradeIds.length - 1"> / </span></span></span>
-					<span class="info-item">题量:<span class="info-bold">{{ paper.scoring ? paper.scoring.length : 0 }}</span></span>
-					<!-- <span class="info-item">难度系数:<span class="info-bold">{{ paper.item ? handleDiffCalc(paper.item) : 0 }}</span></span> -->
+			</div>
+			
+			<div class="pl-review-wrap" v-if="isPreview">
+				<div class="pl-review-wrap-left">
+					<TestPaper :paper="evaluationInfo" isPreview></TestPaper>
 				</div>
-				<div class="paper-item-tools">
-					<span class="paper-item-tools-edit" @click="goToPaper(paper)" v-if="$access.can('admin.*|teacher.*|Paper_Edit')">
-						<Icon type="ios-create" />
-						<span>编辑</span>
-					</span>
-					<span class="paper-item-tools-delete" @click.stop="onDeletePaper(paper)" v-if="$access.can('admin.*|teacher.*|Paper_Edit')">
-						<Icon type="md-trash" />
-						<span>删除</span>
-					</span>
+				
+				<div class="pl-review-wrap-right">
+					<p class="btn-back-list" @click="isPreview = false">
+						<Icon type="md-arrow-back" />
+						返回列表
+						</p>
+					<h2>试卷分析</h2>
+					<p>(总分:{{ evaluationInfo.score }} 分)</p>
+					<BaseTypePie :echartsData="evaluationInfo"></BaseTypePie>
+					<!-- <BaseObjectivePie :echartsData="evaluationInfo"></BaseObjectivePie> -->
 				</div>
 			</div>
 
@@ -48,12 +71,14 @@
 	import BaseFilter from '../components/BaseFilter'
 	import BaseImport from '../components/BaseImport'
 	import AutoCreate from '../../learnactivity/AutoCreate'
+	import TestPaper from '../index/TestPaper.vue'
 	export default {
 		components: {
 			Loading,
 			BaseFilter,
 			AutoCreate,
-			BaseImport
+			BaseImport,
+			TestPaper
 		},
 		data() {
 			return {
@@ -74,6 +99,8 @@
 				originList: [],
 				schoolInfo: {},
 				filterSort: 'createTime',
+				evaluationInfo:null,
+				isPreview:false,
 				paperInfo: {
 					name: "",
 					score: 100,
@@ -87,10 +114,60 @@
 
 		},
 		methods: {
+			
+			async onPreviewPaper(paper){
+				let curPaper = paper
+				try {
+					// 获取完整试卷数据再跳转编辑页面
+					let fullPaperJson = await this.$evTools.getFullPaper(curPaper)
+					fullPaperJson.code = curPaper.code
+					let paper = fullPaperJson
+					let schoolInfo = null
+					if(paper.scope === 'school'){
+						schoolInfo = await this.getSchoolBaseInfo()
+						this.subjectList = schoolInfo ? this.schoolInfo.period.filter(i => i.id === paper.periodId)[0].subjects : []
+					}
+					this.evaluationInfo = {
+						id: paper.id,
+						name: paper.name,
+						code:paper.code,
+						type: paper.scope,
+						paperPeriod: schoolInfo && paper.scope === 'school' ? this.schoolInfo.period.map(i => i.id).indexOf(paper.periodId) : null,
+						paperGrade: schoolInfo && paper.scope === 'school' ? paper.gradeIds : [],
+						paperSubject: schoolInfo && paper.scope === 'school' ? this.subjectList.map(i => i.id).indexOf(paper.subjectId) : [],
+						score: paper.score,
+						item: paper.item,
+						multipleRule:paper.multipleRule,
+						startTime: 0,
+						endTime: 0,
+						createType:'manual'
+					}
+					this.isPreview = true
+				} catch (e) {
+					console.log(e)
+					this.$Message.error('获取试卷数据失败!请稍后再试!')
+				}
+			},
+			
+			/** 获取当前学校基础数据 */
+			getSchoolBaseInfo() {
+				return new Promise((r, j) => {
+					this.$store.dispatch("user/getSchoolProfile").then((res) => {
+						let schoolBaseInfo = res.school_base;
+						if (schoolBaseInfo) {
+							r(schoolBaseInfo)
+						}else{
+							r(null)
+						}
+					});
+				})
+			
+			},
 
 			/** 执行筛选条件获取数据 */
 			doFilter() {
 				this.dataLoading = true
+				this.isPreview = false
 				this.getPaperList(this.filterParams)
 			},
 

+ 39 - 12
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseChild.vue

@@ -3,7 +3,7 @@
 		<div class="child-item" v-for="(item,index) in children" :key="index">
 			<div class="child-item-question">
 				<span class="child-item-question-order">【小题{{ index + 1 }}】</span>
-				<p class="child-item-question-content" v-html="item.question" @click="onRichTextClick($event)"></p>
+				<p class="child-item-question-content" v-html="item.question"></p>
 			</div>
 			<!-- 选项部分 -->
 			<div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="child-item-option" style="pointer-events:none">
@@ -12,6 +12,14 @@
 					<div class="child-item-option-text" v-html="option.value"></div>
 				</div>
 			</div>
+			<div class="item-btn-toggle" v-if="$route.name === 'createPaper'">
+				<template>
+					<InputNumber :max="totalScore" :min="0" v-model="item.score" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;"
+					 @click.stop></InputNumber>
+					<span style="margin-right: 20px;">分</span>
+					<!-- <span class="item-score" title="设置题目分数" @click.stop="onSetSingleItem(item,index)" v-else>{{ item.score }} 分</span> -->
+				</template>
+			</div>
 			<!-- 答案展示部分 -->
 			<div class="item-explain">
 				<span class="explain-title">【答ㅤ案】</span>
@@ -21,13 +29,13 @@
 						<span v-for="(answer,index) in item.answer" :key="index" v-html="item.answer.length ? answer : '未设置答案'"></span>
 					</div>
 					<!-- 填空题答案 -->
-					<div v-else-if="item.type === 'Complete'">
-						<span :class="[ item.type === 'Complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer" :key="index"
+					<div v-else-if="item.type === 'complete'">
+						<span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer" :key="index"
 						 v-html="answer"></span>
 					</div>
 					<!-- 其余题型答案 -->
 					<div v-else>
-						<span :class="[ item.type === 'Complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer" :key="index" v-html="answer"></span>
+						<span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer" :key="index" v-html="answer"></span>
 					</div>
 				</div>
 			</div>
@@ -35,7 +43,7 @@
 			<div class="item-explain">
 				<span class="explain-title">【解ㅤ析】</span>
 				<div class="item-explain-details">
-					<span v-html="item.explain || '暂无解析'"  @click="onRichTextClick($event)"></span>
+					<span v-html="item.explain || '暂无解析'"></span>
 				</div>
 			</div>
 			<!-- 知识点部分 -->
@@ -62,21 +70,35 @@
 			children: {
 				type: Array,
 				default: []
+			},
+			totalScore:{
+				type:Number,
+				default:0
 			}
 		},
 		data() {
 			return {
-				
+				surPlusScore:0
 			}
 		},
 		created() {
+			if(this.children.length){
+				console.log(this.totalScore)
+				console.log(this.children)
+				this.children.forEach(i => {
+					if(!i.score){
+						i.score = 0
+					}
+					this.surPlusScore = this.totalScore - i.score
+				})
+			}
+			
 		},
 		methods: {
-			/* 富文本框点击事件 */
-			onRichTextClick(e) {
-				this.$parent.onRichTextClick(e)
-			},
-
+			/* 给小题配分 */
+			doDistribution(){
+				
+			}
 			
 		},
 
@@ -91,6 +113,11 @@
 						window.MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
 					})
 				}
+			},
+			totalScore:{
+				handler(n){
+					
+				}
 			}
 		}
 
@@ -100,10 +127,10 @@
 <style lang="less" scoped>
 	.child-wrap{
 		.child-item{
+			position: relative;
 			margin:40px 10px 0 10px;
 			
 			&-question{
-				font-weight: bold;
 				font-size: 14px;
 				// color:#01b087;
 				

+ 32 - 72
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseCreateChild.vue

@@ -9,10 +9,10 @@
 			</div>
 			<div class="exersices-attr my-radio-style">
 				<IconText :text="'关联知识点'" :color="'#00b8ff'" :icon="'md-infinite'"></IconText>
-				<Button type="info" style="margin-top: 20px" @click="selectPointsModal = true" v-if="exercisePoints.length === 0">选择知识点</Button>
+				<Button type="info" style="margin-top: 20px" @click="onSelectPoints" v-if="exercisePoints.length === 0">选择知识点</Button>
 				<div v-else style="margin-top: 10px">
 					<span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
-						{{ item.name }}
+						{{ item }}
 						<span class="exercise-item-point-close">
 							<Icon type="md-close" @click="onDeletePoint(index)" /></span>
 					</span>
@@ -71,7 +71,7 @@
 		<!-- 选择知识点弹窗 -->
 		<Modal v-model="selectPointsModal" :title="'选择知识点'" width="600px" class="related-point-modal" style="z-index: 99999">
 			<BasePoints v-if="selectPointsModal" ref="pointRef" :period="schoolInfo.period[curPeriodIndex].periodCode" :subject="subjectList[curSubjectIndex].subjectCode"
-			 @onCheckChange="onCheckChange"></BasePoints>
+			 @onCheckChange="onCheckChange" :points="exercisePoints" :scope="curScope"></BasePoints>
 			<!--<CreateNewChild v-if="isLoadModal" ref="newChild" :isChildEdit="isChildEdit" :editItem="editChild"></CreateNewChild>-->
 			<div slot="footer">
 				<Button type="text" @click="selectPointsModal = false">取消</Button>
@@ -86,18 +86,6 @@
 
 			<Button class="modal-btn" :loading="isLoading" @click="onConfirmRelate">确认</Button>
 		</Modal>
-
-		<!-- 音频播放弹窗 -->
-		<Modal v-model="playAudioModal" width="400" footer-hide @on-visible-change="onAudioModalChange">
-			<div class="modal-header" slot="header">音频播放</div>
-			<BaseAudioPlayer :audioSrc="curAudioSrc" :audioName="curAudioName" ref="audioPlayer"></BaseAudioPlayer>
-		</Modal>
-
-		<!-- 视频播放弹窗 -->
-		<Modal v-model="playVideoModal" width="400" footer-hide @on-visible-change="onVideoModalChange">
-			<div class="modal-header" slot="header">视频播放</div>
-			<BaseVideoPlayer :videoSrc="curVideoSrc" :audioName="curVideoName" ref="videoPlayer"></BaseVideoPlayer>
-		</Modal>
 	</div>
 </template>
 <script>
@@ -177,16 +165,6 @@
 				stemContent: "",
 				analysisEditor: null,
 				repairEditor: null,
-				defaultConfig: {
-					uploadImgShowBase64: true,
-					menus: this.$tools.wangEditorMenu,
-				},
-				curAudioSrc: "",
-				curAudioName: "",
-				curVideoSrc: "",
-				curVideoName: "",
-				playAudioModal: false,
-				playVideoModal: false,
 			};
 		},
 		created() {
@@ -205,34 +183,15 @@
 					}
 				});
 			},
-
-			/* 音频弹窗切换事件 */
-			onAudioModalChange(val) {
-				if (!val) {
-					this.$refs.audioPlayer.onCloseAudio();
+			
+			onSelectPoints(){
+				if(this.hasSchool){
+					this.selectPointsModal = true
+				}else{
+					this.$Message.warning('您当前未加入学校,无法选择知识点!')
 				}
 			},
 
-			/* 富文本框点击事件 */
-			onRichTextClick(e) {
-				console.log(e);
-				if (e.srcElement.classList[0] === "richText-audio") {
-					this.playAudioModal = true;
-					this.curAudioSrc = e.srcElement.dataset.url;
-					this.curAudioName = e.srcElement.dataset.name;
-				} else if (e.srcElement.classList[0] === "richText-video") {
-					this.playVideoModal = true;
-					this.curVideoSrc = e.srcElement.dataset.url;
-					this.curVideoName = e.srcElement.dataset.name;
-				}
-			},
-
-			/* 视频弹窗切换事件 */
-			onVideoModalChange(val) {
-				if (!val) {
-					// this.$refs.audioPlayer.onCloseAudio()
-				}
-			},
 
 			onSelectFile(val) {
 				this.relateFileList = val.files;
@@ -247,34 +206,26 @@
 				let exerciseItem = Object.assign({}, defaultExercise);
 				switch (type) {
 					case "single":
-						console.log(this.$refs.single);
+						this.$refs.single.doSave()
 						exerciseItem.question = this.$refs.single.stemContent;
-						exerciseItem.option =
-							this.$refs.single.optionsContent.length ===
-							this.$refs.single.options.length &&
-							this.checkOptionNull(this.$refs.single.optionsContent) ?
-							this.$refs.single.optionsContent :
-							null;
+						exerciseItem.option = this.checkOptionNull(this.$refs.single.optionsContent) ? this.$refs.single.optionsContent : null;
 						exerciseItem.type = this.exersicesType;
 						exerciseItem.level = +this.exersicesDiff;
 						exerciseItem.explain = this.analysisContent;
 						exerciseItem.answer = [
 							String.fromCharCode(64 + parseInt(this.$refs.single.trueIndex + 1)),
 						];
-
 						break;
 					case "multiple":
+					this.$refs.multiple.doSave()
 						exerciseItem.question = this.$refs.multiple.stemContent;
-						exerciseItem.option =
-							this.$refs.multiple.optionsContent.length ===
-							this.$refs.multiple.options.length &&
-							this.checkOptionNull(this.$refs.multiple.optionsContent) ?
+						exerciseItem.option = this.checkOptionNull(this.$refs.multiple.optionsContent) ?
 							this.$refs.multiple.optionsContent :
 							null;
 						exerciseItem.type = this.exersicesType;
 						exerciseItem.level = +this.exersicesDiff;
 						exerciseItem.explain = this.analysisContent;
-						exerciseItem.answer = this.$refs.multiple.transferArr;
+						exerciseItem.answer = this.$refs.multiple.multipleAnswers;
 						break;
 					case "judge":
 						exerciseItem.question = this.$refs.judge.stemContent;
@@ -320,6 +271,7 @@
 					this.getSimpleText(exerciseItem.question)
 				) {
 					// this.saveLoading = true
+					exerciseItem.id = this.editInfo.id || this.$tools.guid();
 					this.$emit("addFinish", exerciseItem);
 				} else {
 					console.log(exerciseItem);
@@ -329,10 +281,7 @@
 
 			/* 知识点勾选变动事件 */
 			onCheckChange(val, list) {
-				this.exercisePoints = [];
-				val.forEach((item) => {
-					this.exercisePoints.push(list.filter((point) => point.id === item)[0]);
-				});
+				this.exercisePoints = val;
 			},
 
 			/**
@@ -348,11 +297,7 @@
 			typeChange(val) {
 				if (this.isEdit) {
 					this.$Message.warning("暂不支持更换题型!");
-				} else if (val === "compose") {
-					this.$router.push({
-						name: "createCompose",
-					});
-				} else {
+				}else {
 					this.exersicesType = val;
 					this.analysisEditor.txt.clear();
 					// this.repairEditor.txt.clear()
@@ -556,6 +501,21 @@
 				this.renderExercise(JSON.parse(JSON.stringify(editItem)));
 			}
 		},
+		computed:{
+			curScope(){
+				return this.exerciseScope === 1 ? 'school' : 'private'
+			},
+			hasSchool() {
+			  let user = JSON.parse(
+			    decodeURIComponent(localStorage.user_profile, "utf-8")
+			  );
+			  return (
+				user.schools && 
+			    user.schools.length &&
+			    user.schools.filter((i) => i.status === "join").length > 0
+			  );
+			},
+		},
 		watch: {
 			editItem: {
 				handler(newValue, oldValue) {

+ 87 - 56
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseEditExercise.vue

@@ -127,18 +127,6 @@
 			<BaseCreateChild @addFinish="onAddChildFinish" v-if="addChildModal" :curPeriodIndex="exercisePeriod"
 			 :curSubjectIndex="exerciseSubject"></BaseCreateChild>
 		</Modal>
-
-		<!-- 音频播放弹窗 -->
-		<Modal v-model="playAudioModal" width="400" footer-hide @on-visible-change="onAudioModalChange">
-			<div class="modal-header" slot="header">音频播放</div>
-			<BaseAudioPlayer :audioSrc="curAudioSrc" :audioName="curAudioName" ref="audioPlayer"></BaseAudioPlayer>
-		</Modal>
-
-		<!-- 视频播放弹窗 -->
-		<Modal v-model="playVideoModal" width="400" footer-hide @on-visible-change="onVideoModalChange">
-			<div class="modal-header" slot="header">视频播放</div>
-			<BaseVideoPlayer :videoSrc="curVideoSrc" :audioName="curVideoName" ref="videoPlayer"></BaseVideoPlayer>
-		</Modal>
 	</div>
 </template>
 <script>
@@ -185,6 +173,7 @@
 				exerciseScope: 0,
 				exercisePoints: [],
 				childList: [],
+				oldChildList:[],
 				scopeList: ["个人题库", "校本题库"],
 				fieldList: ["知识", "理解", "应用", "分析", "综合", "评鉴"],
 				periodList: [],
@@ -198,16 +187,6 @@
 				analysisEditor: null,
 				repairEditor: null,
 				videoHtml: "",
-				defaultConfig: {
-					uploadImgShowBase64: true,
-					menus: this.$tools.wangEditorMenu,
-				},
-				curAudioSrc: "",
-				curAudioName: "",
-				curVideoSrc: "",
-				curVideoName: "",
-				playAudioModal: false,
-				playVideoModal: false,
 			};
 		},
 		created() {
@@ -227,33 +206,6 @@
 					}
 				});
 			},
-
-			/* 音频弹窗切换事件 */
-			onAudioModalChange(val) {
-				if (!val) {
-					this.$refs.audioPlayer.onCloseAudio();
-				}
-			},
-
-			/* 视频弹窗切换事件 */
-			onVideoModalChange(val) {
-				if (!val) {
-					// this.$refs.audioPlayer.onCloseAudio()
-				}
-			},
-
-			/* 富文本框点击事件 */
-			onRichTextClick(e) {
-				if (e.srcElement.classList[0] === "richText-audio") {
-					this.playAudioModal = true;
-					this.curAudioSrc = e.srcElement.dataset.url;
-					this.curAudioName = e.srcElement.dataset.name;
-				} else if (e.srcElement.classList[0] === "richText-video") {
-					this.playVideoModal = true;
-					this.curVideoSrc = e.srcElement.dataset.url;
-					this.curVideoName = e.srcElement.dataset.name;
-				}
-			},
 			
 			onSelectPoints(){
 				if(this.hasSchool){
@@ -373,6 +325,18 @@
 						const guid = this.editInfo.id ? this.editInfo.id : this.$tools.guid();
 						// 给新增的试题赋值ID
 						exerciseItem.id = guid;
+						if(exerciseItem.children && exerciseItem.children.length && exerciseItem.type === 'compose'){
+							exerciseItem.children.forEach((child) => {
+								child.periodId = exerciseItem.periodId
+								child.gradeIds = exerciseItem.gradeIds
+								child.subjectId = exerciseItem.subjectId
+								child.scope = exerciseItem.scope
+								child.pId = exerciseItem.id
+							})
+							console.log(exerciseItem)
+							exerciseItem.children  = await this.saveChildrens(exerciseItem.children)
+							console.log(exerciseItem)
+						}
 						// 将当前的试题数据转化为BLOB内部的试题JSON格式
 						const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
 						// 首先保存新题目的JSON文件到Blob 然后返回URL链接
@@ -393,14 +357,17 @@
 						);
 
 						try {
+							console.log(exerciseItem)
 							// 等待上传blob的返回结果
 							let blobFile = await containerClient.upload(file, "item");
 							if (blobFile.blob) {
+								console.log(exerciseItem)
 								// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
 								exerciseItem.blob = blobFile.blob;
 								let cosmosItem = await this.$evTools.createCosmosItem(
 									exerciseItem
 								);
+								console.log(exerciseItem)
 								this.saveExercise(cosmosItem);
 							} else {
 								this.$Message.error("试题文件上传失败,请稍后重试!");
@@ -421,6 +388,62 @@
 				}
 				console.log(exerciseItem);
 			},
+			
+			/* 保存综合题的子题 */
+			saveChildrens(childrens){
+				return new Promise((resolve,reject) => {
+					let promiseArr = []
+					childrens.forEach(exerciseItem => {
+						promiseArr.push(new Promise(async (r,j) => {
+							// 将当前的试题数据转化为BLOB内部的试题JSON格式
+							const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
+							// 首先保存新题目的JSON文件到Blob 然后返回URL链接
+							let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+							// 获取初始化Blob需要的数据
+							let sasData =
+								exerciseItem.scope === 'private' ?
+								await this.$tools.getPrivateSas() :
+								await this.$tools.getSchoolSas();
+							//初始化Blob
+							let containerClient = new blobTool(
+								sasData.url,
+								sasData.name,
+								sasData.sas,
+								exerciseItem.scope
+							);
+							try {
+								// 等待上传blob的返回结果
+								let blobFile = await containerClient.upload(file, "item");
+								if (blobFile.blob) {
+									// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+									exerciseItem.blob = blobFile.blob;
+									let cosmosItem = await this.$evTools.createCosmosItem(exerciseItem)
+									let isNew = this.oldChildList.map(i => i.id).indexOf(exerciseItem.id) === -1
+									this.$api.newEvaluation.SaveSingleExercise({
+											itemInfo: cosmosItem,
+											option: isNew ? "insert" : "update",
+										}).then((res) => {
+											r(res.itemInfo)
+										});
+								} else {
+									this.$Message.error("试题文件上传失败,请稍后重试!");
+								}
+							} catch (e) {
+								this.$Message.error(e);
+							}
+						}))
+					})
+					
+					Promise.all(promiseArr).then(result => {
+						console.log(result)
+						if(result.length){
+							resolve(result.map(i => i.id))
+						}else{
+							resolve([])
+						}
+					})
+				})
+			},
 
 			/**
 			 * 保存编辑后的试题
@@ -473,11 +496,7 @@
 			typeChange(val) {
 				if (this.isEdit) {
 					this.$Message.warning("暂不支持更换题型!");
-				} else if (val === "Compose") {
-					this.$router.push({
-						name: "createCompose",
-					});
-				} else {
+				}else {
 					this.exersicesType = val;
 					this.analysisEditor.txt.clear();
 					this.repairEditor.txt.clear();
@@ -634,7 +653,9 @@
 				
 				let schoolProfile = await this.$store.dispatch('user/getSchoolProfile')
 				let schoolInfo = schoolProfile.school_base;
-				if (this.isSchool && schoolInfo) {
+				
+				if (editItem.scope === "school" && schoolInfo) {
+					console.log('进来了')
 					this.schoolInfo = schoolInfo;
 					this.exerciseGrade = editItem.gradeIds;
 					this.exercisePeriod = schoolInfo.period
@@ -645,8 +666,15 @@
 					this.exerciseSubject = this.subjectList
 						.map((item) => item.id)
 						.indexOf(editItem.subjectId);
+					console.log(schoolInfo)
+					console.log(this.exercisePeriod)
+					console.log(this.exerciseSubject)	
 				}
 				
+				console.log(schoolInfo)
+				console.log(this.exercisePeriod)
+				console.log(this.exerciseSubject)
+				
 				this.isEdit = true;
 				this.exersicesDiff = editItem.level.toString() || "0";
 				this.exerciseScope = editItem.scope === "private" ? 0 : 1;
@@ -680,7 +708,10 @@
 						ac[i].style.color = "#515a6e";
 					}
 				}
-
+				
+				if(editItem.type === 'compose'){
+					this.oldChildList = JSON.parse(JSON.stringify(editItem.children)) || []
+				}
 				this.childList = editItem.children || [];
 				this.stemContent = editItem.question;
 				this.relateFileList = editItem.repairResource || [];

+ 23 - 54
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue

@@ -47,18 +47,18 @@
 							</div>
 							<div v-else>
 								<div class="item-question-order">{{ pageSize * (pageNum - 1) + index + 1 }} : </div>
-								<div class="item-question-text" v-html="item.question" @click="onRichTextClick($event)"></div>
+								<div class="item-question-text" v-html="item.question"></div>
 							</div>
 						</div>
 						<!-- 选项部分 -->
 						<div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="item-options">
 							<div class="item-option-content">
 								<div class="item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}} :</div>
-								<div class="item-option-text" v-html="option.value" @click="onRichTextClick($event)"></div>
+								<div class="item-option-text" v-html="option.value"></div>
 							</div>
 						</div>
 					</div>
-					<div class="item-btn-toggle" @click.stop v-show="isShowTools" >
+					<div class="item-btn-toggle" @click.stop v-show="isShowTools && !isPreview" >
 						<template>
 							<InputNumber :max="item.score + surPlusScore" :min="0" v-model="item.score" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;"
 							 @click.stop></InputNumber>
@@ -75,7 +75,7 @@
 								<!-- 答案展示部分 -->
 								<div class="item-explain" v-show="isShowAnswer">
 									<span class="explain-title">【答ㅤ案】</span>
-									<div class="item-explain-details" @click="onRichTextClick($event)">
+									<div class="item-explain-details">
 										<!-- 问答题答案 -->
 										<div v-if="item.type === 'subjective'">
 											<span v-for="(answer,index) in item.answer" :key="index" v-html="item.answer.length ? answer : '未设置答案'"></span>
@@ -96,7 +96,7 @@
 								<div class="item-explain" v-show="isShowAnswer">
 									<span class="explain-title">【解ㅤ析】</span>
 									<div class="item-explain-details">
-										<span v-html="item.explain || '暂无解析'" @click="onRichTextClick($event)"></span>
+										<span v-html="item.explain || '暂无解析'"></span>
 									</div>
 								</div>
 								<!-- 知识点部分 -->
@@ -114,7 +114,7 @@
 							</div>
 							<!-- 如果是综合题 则加载子题渲染组件 -->
 							<div v-else>
-								<BaseChild :children="item.children"></BaseChild>
+								<BaseChild :children="item.children" :totalScore="item.score"></BaseChild>
 							</div>
 
 						</div>
@@ -184,20 +184,6 @@
 				<Button type="success">确认</Button>
 			</div>
 		</Modal> -->
-
-		<!-- 音频播放弹窗 -->
-		<Modal v-model="playAudioModal" width="400" footer-hide @on-visible-change="onAudioModalChange">
-			<div class="modal-header" slot="header">音频播放</div>
-			<BaseAudioPlayer :audioSrc="curAudioSrc" :audioName="curAudioName" ref="audioPlayer"></BaseAudioPlayer>
-		</Modal>
-
-		<!-- 视频播放弹窗 -->
-		<Modal v-model="playVideoModal" width="400" footer-hide @on-visible-change="onVideoModalChange">
-			<div class="modal-header" slot="header">视频播放</div>
-			<BaseVideoPlayer :videoSrc="curVideoSrc" :audioName="curVideoName" ref="videoPlayer"></BaseVideoPlayer>
-		</Modal>
-
-
 	</div>
 </template>
 
@@ -211,7 +197,11 @@
 			isShowTools: {
 				type: Boolean,
 				default: false
-			}
+			},
+			isPreview: {
+				type: Boolean,
+				default: false
+			},
 		},
 		data() {
 			return {
@@ -262,12 +252,6 @@
 				scoreModal: false,
 				currentExercise: {},
 				allPointList: [],
-				playAudioModal: false,
-				playVideoModal: false,
-				curAudioSrc: "",
-				curAudioName: "",
-				curVideoSrc: "",
-				curVideoName: "",
 				modifyItems: [],
 				errorList: [],
 				paperInfo: {
@@ -287,33 +271,6 @@
 			//console.log(this.exerciseList)
 		},
 		methods: {
-
-			/* 音频弹窗切换事件 */
-			onAudioModalChange(val) {
-				if (!val) {
-					this.$refs.audioPlayer.onCloseAudio()
-				}
-			},
-			
-			/* 视频弹窗切换事件 */
-			onVideoModalChange(val) {
-				if (!val) {
-					this.$refs.videoPlayer.onCloseAudio()
-				}
-			},
-
-			/* 音频点击播放事件 */
-			onRichTextClick(e) {
-				if (e.srcElement.classList[0] === 'richText-audio') {
-					this.playAudioModal = true
-					this.curAudioSrc = e.srcElement.dataset.url
-					this.curAudioName = e.srcElement.dataset.name
-				} else if (e.srcElement.classList[0] === 'richText-video') {
-					this.playVideoModal = true
-					this.curVideoSrc = e.srcElement.dataset.url
-					this.curVideoName = e.srcElement.dataset.name
-				}
-			},
 			/**
 			 * 题干展开与收缩
 			 * @param index
@@ -532,6 +489,16 @@
 								exercise.score = lastItem ? integerScore + remainder : integerScore
 							}
 							listItem.score = exercise.score
+							
+							if(exercise.type === 'compose' && exercise.children && exercise.children.length){
+								exercise.children.forEach((child,childIndex) => {
+									let remainder = exercise.score % exercise.children.length
+									let integerScore = parseInt(exercise.score / exercise.children.length)
+									let lastItem = childIndex === exercise.children.length - 1
+									child.score = lastItem ? integerScore + remainder : integerScore
+								})
+							}
+							
 						})
 					})
 
@@ -667,6 +634,7 @@
 						this.exerciseList = []
 						this.orderList = []
 						this.paperInfo = newPaper
+						this.multipleRule = newPaper.multipleRule
 						if (newPaper.item.length) {
 							newPaper.item.forEach(i => {
 								if (!i.score) i.score = 0
@@ -713,6 +681,7 @@
 							}]
 						} else {
 							this.groupList = this.groupTypeList
+							console.log(this.groupList)
 						}
 					}
 				},

+ 4 - 3
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseImport.vue

@@ -18,7 +18,8 @@
 					<div style="display: flex;margin-bottom: 20px;">
 						<Dropdown @on-click="onTemplateSelect">
 						        <Button type="primary">
-						            多语言模板下载
+									<Icon type="md-cloud-download"  style="margin-right: 5px;"/>
+						            模板下载
 						            <Icon type="ios-arrow-down"></Icon>
 						        </Button>
 						        <DropdownMenu slot="list">
@@ -27,7 +28,7 @@
 						            <DropdownItem :name="2">英文(en-US)</DropdownItem>
 						        </DropdownMenu>
 						    </Dropdown>
-						<Button type="primary" style="margin-left: 20px;" @click="onDownloadDetails">下载模板制作详情说明</Button>	
+						<Button type="primary" icon="md-cloud-download" style="margin-left: 20px;" @click="onDownloadDetails">下载模板制作详情说明</Button>	
 					</div>
 					
 					<p style="font-size:18px;font-weight:bold;color: #fff;">导入注意事项</p>
@@ -71,7 +72,7 @@
             }
         },
         created() {
-            this.uploadUrl = window.location.origin  + '/api/ImportExercise/uploadWord'
+            this.uploadUrl = window.location.origin  + ' /common/import/upload-word'
 			this.curLang = localStorage.getItem('local')
 			this.hostName = this.$store.state.privateSas.url || 'https://teammodelstorage.blob.core.chinacloudapi.cn'
         },

+ 117 - 0
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseObjectivePie.vue

@@ -0,0 +1,117 @@
+<template>
+    <div id="myTypePie"></div>
+</template>
+
+<script>
+    export default {
+        name: 'BaseTypePie',
+        props: ['pieId','echartsData'],
+        data() {
+            return {
+                pieData: [],
+				types:{
+					single:'单选题',
+					multiple:'多选题',
+					judge:'判断题',
+					complete:'填空题',
+					subjective:'问答题',
+					compose:'综合题'
+				}
+            }
+        },
+        methods: {
+
+            drawLine(data) {
+                let that = this
+                // 基于准备好的dom,初始化echarts实例
+                let myPie = this.$echarts.init(document.getElementById('myTypePie'), 'chalk')
+
+                // 指定图表的配置项和数据
+                var option = {
+                    title: {
+                        'text': '试卷主观客观分布图',
+                        'bottom': '5%',
+                        'left': '35%',
+                        'textStyle': {
+                            'fontSize': 14,
+                            'color': '#595959'
+                        }
+                    },
+                    color: ['#37a2da', '#e7bcf3', '#9fe6b8', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378ea'],
+                    tooltip: {
+                        trigger: 'item',
+                        formatter: '{a} <br/>{b} : {c} ({d}%)'
+                    },
+                    calculable: true,
+                    series: [
+                        {
+                            name: '题型分布饼图',
+                            type: 'pie',
+                            radius: [20, 100],
+                            data: data
+                        }
+                    ]
+                }
+
+                // 绘制图表
+                myPie.setOption(option)
+
+                window.addEventListener('resize', function() {
+                    myPie.resize()
+                })
+            }
+        },
+        mounted() {
+			let arr = []
+			let typeList = this._.groupBy(this.echartsData.item, 'type')
+			let subjectiveCount = 0
+			let objectiveCount = 0
+			for(let key in typeList){
+				if(key === 'single' || key === 'multiple' || key === 'judge'){
+					subjectiveCount += typeList[key].length
+				}else{
+					objectiveCount += typeList[key].length
+				}
+			}
+			arr = [{
+				name:'主观题',
+				value:subjectiveCount
+			},{
+				name:'客观题',
+				value:objectiveCount
+			}]
+			console.log(arr)
+			this.drawLine(arr)
+        },
+        computed: {
+            // 获取最新知识点占比饼图数据
+            getPieData() {
+                return this.$store.state.totalAnalysis.knowledgeData
+            }
+        },
+        watch: {
+            echartsData(val) {
+    //             if (!val) return
+				// console.log('接收到图 表信息')
+				// console.log(val)
+				// let typeList = this._.groupBy(val.item, 'type')
+				// for(let key in typeList){
+				// 	console.log(key)
+				// }
+    //             this.drawLine(val)
+            }
+        }
+
+    }
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+    #myTypePie {
+        width: 100%;
+        height: 380px;
+        margin: 20px auto;
+        display: block;
+    }
+</style>

+ 29 - 7
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseTypePie.vue

@@ -8,7 +8,15 @@
         props: ['pieId','echartsData'],
         data() {
             return {
-                pieData: []
+                pieData: [],
+				types:{
+					single:'单选题',
+					multiple:'多选题',
+					judge:'判断题',
+					complete:'填空题',
+					subjective:'问答题',
+					compose:'综合题'
+				}
             }
         },
         methods: {
@@ -22,11 +30,11 @@
                 var option = {
                     title: {
                         'text': '试卷题型分布图',
-                        'top': '5%',
-                        'left': '30%',
+                        'bottom': '5%',
+                        'left': '35%',
                         'textStyle': {
                             'fontSize': 14,
-                            'color': '#8D8D8D'
+                            'color': '#595959'
                         }
                     },
                     color: ['#37a2da', '#e7bcf3', '#9fe6b8', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378ea'],
@@ -54,7 +62,15 @@
             }
         },
         mounted() {
-            //this.drawLine()
+			let arr = []
+			let typeList = this._.groupBy(this.echartsData.item, 'type')
+			for(let key in typeList){
+				arr.push({
+					value:typeList[key].length,
+					name:this.types[key]
+				})
+			}
+			this.drawLine(arr)
         },
         computed: {
             // 获取最新知识点占比饼图数据
@@ -64,8 +80,14 @@
         },
         watch: {
             echartsData(val) {
-                if (!val) return
-                this.drawLine(val)
+    //             if (!val) return
+				// console.log('接收到图 表信息')
+				// console.log(val)
+				// let typeList = this._.groupBy(val.item, 'type')
+				// for(let key in typeList){
+				// 	console.log(key)
+				// }
+    //             this.drawLine(val)
             }
         }
 

+ 0 - 107
TEAMModelOS/ClientApp/src/view/evaluation/index/CreateCompose.css

@@ -1,107 +0,0 @@
-.children-list {
-    width:100%;
-    height:auto;
-}
-
-    .children-list .child-Index {
-        font-size:16px;
-        margin:10px;
-    }
-
-    .children-list .child-item {
-        position:relative;
-        font-size: 16px;
-        margin-top: 20px;
-        padding:20px;
-        border:1px solid #fff;
-    }
-    .children-list .child-item:hover {
-        border:1px solid #808080;
-    }
-
-        .children-list .child-item  .item-content {
-            /*display: inline-block;*/
-        }
-
-        .children-list .child-item  .item-content p {
-            display: inline-block;
-        }
-
-        .children-list .child-item .item-question {
-            font-size:18px;
-            
-        }
-        .children-list .child-item .item-option {
-            margin:10px;
-        }
-        
-        .children-list .child-item .item-answer {
-            margin:10px 0;
-            display:inline-block;
-        }
-        
-        .children-list .child-item .item-tools {
-            margin:10px 0;
-            display:inline-block;
-        }
-
-        .children-list .child-item .item-tools {
-            position: absolute;
-            right: -1px;
-            top: -30px;
-            height: 30px;
-            margin: 0;
-            padding: 0;
-            background: #01b4ef;
-            display: none;
-        }
-
-        .children-list .child-item .item-tools-t {
-            height: 100%;
-            padding: 0 15px;
-            float: left;
-            color: white;
-            cursor: pointer;
-        }
-            .children-list .child-item .item-tools-t:hover {
-                background: #4a5ae6;
-            }
-
-            .children-list .child-item .item-tools-t .ivu-icon {
-                color: white;
-                font-size: 14px;
-                margin: 0 3px;
-            }
-
-        .children-list .child-item .item-answer-item {
-            margin-left: 25px;
-            padding: 0 25px;
-            border-bottom: 2px solid rgb(128, 128, 128);
-        }
-        
-        .children-list .child-item .item-answer-item p {
-            display:inline-block;
-        }
-
-.complete-line {
-    margin: 0 5px;
-    padding: 0 45px;
-    border-bottom: 2px solid rgb(128, 128, 128);
-}
-
-
-/*横向垂直水平居中*/
-.flex-row-center {
-    display: flex;
-    flex-direction: row;
-    justify-content: center;
-    align-items: center;
-}
-
-/*向垂直水平居中*/
-.flex-col-center {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    align-items: center;
-}

File diff suppressed because it is too large
+ 0 - 331
TEAMModelOS/ClientApp/src/view/evaluation/index/CreateCompose.vue


+ 64 - 18
TEAMModelOS/ClientApp/src/view/evaluation/index/CreateExercises.vue

@@ -335,6 +335,17 @@
 					const guid = this.editInfo.id ? this.editInfo.id : this.$tools.guid();
 					// 给新增的试题赋值ID
 					exerciseItem.id = guid;
+					if(exerciseItem.children && exerciseItem.children.length && exerciseItem.type === 'compose'){
+						exerciseItem.children.forEach((child) => {
+							child.periodId = exerciseItem.periodId
+							child.gradeIds = exerciseItem.gradeIds
+							child.subjectId = exerciseItem.subjectId
+							child.scope = exerciseItem.scope
+							child.code = exerciseItem.code
+							child.pId = exerciseItem.id
+						})
+						exerciseItem.children  = await this.saveChildrens(exerciseItem.children)
+					}
 					// 将当前的试题数据转化为BLOB内部的试题JSON格式
 					const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
 					// 首先保存新题目的JSON文件到Blob 然后返回URL链接
@@ -384,26 +395,61 @@
 					this.saveLoading = false;
 				}
 			},
-
-			/* 获取保存在COSMOS里面的试题格式 */
-			getCosmosItem(item) {
-				let cosmosItem = {
-					id: item.id,
-					code: item.code,
-					scope: item.scope,
-					type: item.type,
-					question: this.getSimpleText(item.question),
-					points: item.points,
-					field: item.field,
-					level: item.level,
-					periodId: item.periodId,
-					gradeIds: item.gradeIds,
-					subjectId: item.subjectId,
-					blob: item.blob,
-				};
-				return cosmosItem;
+			
+			/* 保存综合题的子题 */
+			saveChildrens(childrens){
+				return new Promise((resolve,reject) => {
+					let promiseArr = []
+					childrens.forEach(exerciseItem => {
+						promiseArr.push(new Promise(async (r,j) => {
+							// 将当前的试题数据转化为BLOB内部的试题JSON格式
+							const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
+							// 首先保存新题目的JSON文件到Blob 然后返回URL链接
+							let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+							// 获取初始化Blob需要的数据
+							let sasData =
+								exerciseItem.scope === 'private' ?
+								await this.$tools.getPrivateSas() :
+								await this.$tools.getSchoolSas();
+							//初始化Blob
+							let containerClient = new blobTool(
+								sasData.url,
+								sasData.name,
+								sasData.sas,
+								exerciseItem.scope
+							);
+							try {
+								// 等待上传blob的返回结果
+								let blobFile = await containerClient.upload(file, "item");
+								if (blobFile.blob) {
+									// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+									exerciseItem.blob = blobFile.blob;
+									this.saveExercise({
+										itemInfo: await this.$evTools.createCosmosItem(exerciseItem),
+										option: "insert",
+									}).then((res) => {
+										r(res.itemInfo)
+									});
+								} else {
+									this.$Message.error("试题文件上传失败,请稍后重试!");
+								}
+							} catch (e) {
+								this.$Message.error(e.spaceError);
+							}
+						}))
+					})
+					
+					Promise.all(promiseArr).then(result => {
+						if(result.length){
+							resolve(result.map(i => i.id))
+						}else{
+							resolve([])
+						}
+					})
+				})
 			},
 
+
 			/* 保存单个试题 */
 			saveExercise(item) {
 				return new Promise((r, j) => {

+ 0 - 232
TEAMModelOS/ClientApp/src/view/evaluation/index/CreateNewChild.vue

@@ -1,232 +0,0 @@
-<template>
-  <div class="ev-container" style="padding:30px 0">
-    <div class="exersices-attr display-flex">
-      <div class="exersices-attr-type my-radio-style">
-        <IconText :text="'选择题型'" :color="'green'" :icon="'md-apps'"></IconText>
-        <RadioGroup v-model="exersicesType" type="button" @on-change="typeChange">
-          <Radio label="Single" :disabled="isEdit">单选</Radio>
-          <Radio label="Multiple" :disabled="isEdit">多选</Radio>
-          <Radio label="Judge" :disabled="isEdit">判断</Radio>
-          <Radio label="Complete" :disabled="isEdit">填空</Radio>
-          <Radio label="Subjective" :disabled="isEdit">问答</Radio>
-        </RadioGroup>
-      </div>
-      <div class="exersices-attr-diff my-radio-style">
-        <IconText :text="'题目难度'" :color="'red'" :icon="'md-pulse'"></IconText>
-        <RadioGroup v-model="exersicesDiff" type="button">
-          <Radio label="0" @click.native="diffChange($event,'0')">容易</Radio>
-          <Radio label="1" @click.native="diffChange($event,'1')">较易</Radio>
-          <Radio label="2" @click.native="diffChange($event,'2')">一般</Radio>
-          <Radio label="3" @click.native="diffChange($event,'3')">较难</Radio>
-          <Radio label="4" @click.native="diffChange($event,'4')">困难</Radio>
-        </RadioGroup>
-      </div>
-    </div>
-
-    <BaseSingle v-if="exersicesType==='Single'" ref="single" :editInfo="editInfo"></BaseSingle>
-    <BaseMultiple v-else-if="exersicesType==='Multiple'" ref="multiple" :editInfo="editInfo"></BaseMultiple>
-    <BaseJudge v-else-if="exersicesType==='Judge'" ref="judge" :editInfo="editInfo"></BaseJudge>
-    <BaseCompletion v-else-if="exersicesType==='Complete'" ref="complete" :editInfo="editInfo"></BaseCompletion>
-    <BaseSubjective v-else-if="exersicesType==='Subjective'" ref="subjective" :editInfo="editInfo"></BaseSubjective>
-
-    <div class="exersices-analysis">
-      <IconText :text="'解析'" :color="'#2892DD'" :icon="'md-list'" style="margin-bottom:15px;"></IconText>
-      <Upload action="/api/file/uploadWangEditor" name="files" :show-upload-list="false" :on-success="handleSuccess">
-        <Button icon="ios-cloud-upload-outline" class="btn-upload" type="primary">上传文件</Button>
-      </Upload>
-      <div>
-        <div ref="analysisEditor" style="text-align:left"></div>
-      </div>
-    </div>
-    <div class="save-wrap display-flex">
-      <!--<Button type="success" @click="getContent(exersicesType)">保存</Button>
-      <Button type="success" @click="reloadCreate" style="margin-left:10px" v-show="isEdit">新增习题</Button>
-      <Button type="success" @click="resetEditor" style="margin-left:10px">题库</Button>-->
-    </div>
-  </div>
-</template>
-<script>
-  import 'videojs-contrib-hls.js/src/videojs.hlsjs'
-  import IconText from '@/components/evaluation/IconText.vue'
-  import BaseSingle from '@/view/evaluation/types/BaseSingle.vue'
-  import BaseMultiple from '@/view/evaluation/types/BaseMultiple.vue'
-  import BaseCompletion from '@/view/evaluation/types/BaseCompletion.vue'
-  import BaseJudge from '@/view/evaluation/types/BaseJudge.vue'
-  import BaseSubjective from '@/view/evaluation/types/BaseSubjective.vue'
-  import E from '@/utils/wangEditor.js'
-  // 默认创建题目模板
-  const defaultExercise = {
-    question: '',
-    option: [],
-    difficulty: '',
-    answer: [],
-    explain: '',
-    type: ''
-  }
-  export default {
-    components: {
-      IconText, BaseSingle, BaseJudge, BaseMultiple, BaseCompletion, BaseSubjective
-    },
-    props: ['isChildEdit', 'editItem'],
-    data() {
-      return {
-        isEdit: false,
-        exerciseItem: {},
-        editInfo: {},
-        children: [],
-        exersicesType: 'Single',
-        exersicesDiff: '0',
-        analysisContent: '',
-        stemContent: '',
-        analysisEditor: null,
-        videoHtml: '',
-        isCreateFinish: false
-      }
-    },
-    created() {
-
-    },
-    methods: {
-      getContent: function(type) {
-        let exerciseItem = Object.assign({}, defaultExercise)
-        switch (type) {
-          case 'Single':
-            exerciseItem.question = this.$refs.single._data.stemContent
-            exerciseItem.option = this.$refs.single._data.optionsContent.length === this.$refs.single._data.options.length ? this.$refs.single._data.optionsContent : null
-            exerciseItem.type = this.exersicesType
-            exerciseItem.difficulty = this.exersicesDiff
-            exerciseItem.explain = this.analysisContent
-            exerciseItem.answer = [String.fromCharCode(64 + parseInt(this.$refs.single._data.trueIndex + 1))]
-            break
-          case 'Multiple':
-            exerciseItem.question = this.$refs.multiple._data.stemContent
-            exerciseItem.option = this.$refs.multiple._data.optionsContent.length === this.$refs.multiple._data.options.length ? this.$refs.multiple._data.optionsContent : null
-            exerciseItem.type = this.exersicesType
-            exerciseItem.difficulty = this.exersicesDiff
-            exerciseItem.explain = this.analysisContent
-            exerciseItem.answer = this.$refs.multiple._data.transferArr
-            break
-          case 'Judge':
-            exerciseItem.question = this.$refs.judge._data.stemContent
-            exerciseItem.option = []
-            exerciseItem.type = this.exersicesType
-            exerciseItem.difficulty = this.exersicesDiff
-            exerciseItem.explain = this.analysisContent
-            exerciseItem.answer = this.$refs.judge._data.trueAnswer
-            break
-          case 'Complete':
-            exerciseItem.question = this.$refs.complete._data.stemContent
-            exerciseItem.option = []
-            exerciseItem.type = this.exersicesType
-            exerciseItem.difficulty = this.exersicesDiff
-            exerciseItem.explain = this.analysisContent
-            exerciseItem.answer = this.$refs.complete._data.optionsContent.map(item => item.value)
-            break
-          case 'Subjective':
-            exerciseItem.question = this.$refs.subjective._data.stemContent
-            exerciseItem.option = []
-            exerciseItem.type = this.exersicesType
-            exerciseItem.difficulty = this.exersicesDiff
-            exerciseItem.explain = this.analysisContent
-            exerciseItem.answer = this.$refs.subjective._data.answerContent
-            break
-        }
-
-        // 判断获取的数据是否有空数据以及是否为空字符串
-        if (this.checkContent(exerciseItem) && this.getSimpleText(exerciseItem.question) && this.getSimpleText(exerciseItem.explain)) {
-          // this.$router.push({
-          //  name: 'exercisesList',
-          //  params: {
-          //    exerciseItem: exerciseItem,
-          //  }
-          // })
-          this.exerciseItem = exerciseItem
-          this.isCreateFinish = true
-        } else {
-          this.$Message.warning('请将题目填写完整!')
-        }
-
-        console.log(exerciseItem)
-      },
-
-      // 题目类型转换
-      typeChange(val) {
-        if (this.isEdit) {
-          this.$Message.warning('暂不支持更换题型!')
-        } else if (val === 'Compose') {
-          this.$router.push({
-            name: 'createCompose'
-          })
-        } else {
-          this.exersicesType = val
-          this.analysisEditor.txt.clear()
-        }
-      },
-
-      // 难度与背景颜色切换
-      diffChange(e, type) {
-        this.exersicesDiff = type
-        e.preventDefault()
-        let colorArr = ['#32CF74', '#E8BE15', '#F19300', '#EB5E00', '#D30000']
-        let ac = document.getElementsByClassName('exersices-attr-diff')[0].children[1].children
-        for (let i = 0; i < ac.length; i++) {
-          ac[i].style.background = '#fff'
-          ac[i].style.color = '#515a6e'
-        }
-        e.target.style.background = colorArr[type]
-        e.target.style.color = '#fff'
-      },
-
-      // 提取富文本内容中的文本
-      getSimpleText(html) {
-        var msg = html.replace(/<(?!img|video).*?>/g, '')// 执行替换成空字符
-        return msg.replace(/&nbsp;/ig, '')
-      },
-      // 排除对象空属性
-      checkContent(Obj) {
-        let flag = true
-        for (let key in Obj) {
-          if (!Obj[key]) {
-            flag = false
-          }
-        }
-        return flag
-      },
-
-      // 重置编辑器
-      resetEditor() {
-        this.exersicesDiff = '2'
-        this.exersicesType = 'Single'
-        // this.stemContent = '';
-        this.analysisContent = ''
-      },
-
-      reloadCreate() {
-        location.reload()
-      },
-
-      // 文件上传成功回调
-      handleSuccess(res, file) {
-        this.videoHtml = "<br><iframe src='https://teammodelstorage.blob.core.chinacloudapi.cn/wechatfilescontainer/wechatfilescontainer/HilearningMobile_function_10.mp4' frameborder='0' allowfullscreen='true'></iframe><br>"
-        this.analysisContent = this.analysisContent + this.videoHtml
-        this.analysisEditor.txt.append(this.videoHtml)
-        console.log(this.analysisContent)
-      }
-
-    },
-    mounted() {
-      let analysisEditor = new E(this.$refs.analysisEditor)
-      analysisEditor.customConfig.onchange = (html) => {
-        this.analysisContent = html
-      },
-      analysisEditor.customConfig.uploadImgServer = '/api/file/uploadWangEditor'
-      analysisEditor.customConfig.showLinkImg = false
-      analysisEditor.customConfig.uploadFileName = 'files'
-      analysisEditor.create()
-      this.analysisEditor = analysisEditor
-    }
-  }
-</script>
-<style src="../index/CreateExercises.less" lang="less" scoped>
-  /*@import"../index/CreateExercises.css";*/
-</style>

+ 120 - 36
TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue

@@ -240,6 +240,8 @@
 						if (i.children.length) {
 							i.children.forEach(j => {
 								j.id = this.$tools.guid()
+								j.pId = i.id
+								j.scope = i.scope
 								j.code = code
 								j.level = 3
 								j.field = 1
@@ -346,30 +348,65 @@
 					for (let i = 0; i < list.length; i++) {
 						let exerciseItem = list[i]
 						if (isEdit || exerciseItem.blob) {
-							// 如果已有blob字段 说明是已经保存到blob的试题 只需要将最新试题更新到paper目录下(自动手动挑题情况)
-							promiseArr.push(new Promise(async (r, j) => {
-								// 保存试题的blob链接
-								const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem)
-								let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
-								itemJsonFiles.push(file)
-								
-								// 如果有修改的试题 并且为个人题目 则需要同步更新个人题库里面的JSON数据内容
-								// if(modifyItems.length){
-								// 	modifyItems.forEach(async item => {
-								// 		console.log(item)
-								// 		if(item.scope === 'private'){
-								// 			const cosmosItem = await this.$evTools.createCosmosItem(item)
-								// 			const itemJsonFile = await this.$evTools.createBlobItem(item)
-								// 			const privateSas = await this.$tools.getPrivateSas()
-								// 			let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
-								// 			let blobFile = await containerClient.upload(file, 'item')
-								// 			console.log(blobFile)
-								// 		}
-								// 	})
-								// }
-								
-								r(exerciseItem)
-							}))
+							if(exerciseItem.children.length){
+								// 如果是编辑状态下的综合题
+								let composeArr = [exerciseItem,...exerciseItem.children]
+								composeArr.forEach(composeItem => {
+									promiseArr.push(new Promise(async (r, j) => {
+										if(composeItem.children.length && composeItem.children[0].id){
+											composeItem.children = composeItem.children.map(i => i.id)
+										}
+										// 保存试题的blob链接
+										const itemJsonFile = await this.$evTools.createBlobItem(composeItem)
+										let file = new File([JSON.stringify(itemJsonFile)], composeItem.id + ".json");
+										itemJsonFiles.push(file)
+										r(composeItem)
+									}))
+								})
+							}else{
+								// 如果已有blob字段 说明是已经保存到blob的试题 只需要将最新试题更新到paper目录下(自动手动挑题情况)
+								promiseArr.push(new Promise(async (r, j) => {
+									// 保存试题的blob链接
+									const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem)
+									let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+									itemJsonFiles.push(file)
+									r(exerciseItem)
+								}))
+							}
+						}else if(exerciseItem.children.length){
+							// 如果是综合题 则需要把
+							let composeArr = [exerciseItem,...exerciseItem.children]
+							composeArr.forEach(composeItem => {
+								promiseArr.push(new Promise(async (r, j) => {
+									if(composeItem.children.length && composeItem.children[0].id){
+										composeItem.children = composeItem.children.map(i => i.id)
+									}
+									// 将当前的试题数据转化为BLOB内部的试题JSON格式
+									const itemJsonFile = await this.$evTools.createBlobItem(composeItem)
+									const cosmosItem = await this.$evTools.createCosmosItem(composeItem)
+									// 首先保存新题目的JSON文件到Blob 然后返回URL链接
+									let file = new File([JSON.stringify(itemJsonFile)], composeItem.id + ".json");
+									try{
+										// 等待上传blob的返回结果
+										let blobFile = await containerClient.upload(file, 'item')
+										if (blobFile.blob) {
+											// 保存试题JSON文件到试卷文件夹需要
+											itemJsonFiles.push(file)
+											// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+											cosmosItem.blob = blobFile.blob
+											// 保存当前试题到数据库
+											that.saveExercise(cosmosItem).then(res => {
+												r(composeItem)
+											})
+										}else{
+											j(500)
+										}
+									}catch(e){
+										this.$Message.error(e.spaceError)
+										this.isLoading = false
+									}
+								}))
+							})
 						} else {
 							// 如果没有blob字段 说明试题还没有保存到item目录下 则需要进行保存item文件夹后再保存到paper文件夹(导入试题情况)
 							promiseArr.push(new Promise(async (r, j) => {
@@ -388,7 +425,7 @@
 										cosmosItem.blob = blobFile.blob
 										// 保存当前试题到数据库
 										that.saveExercise(cosmosItem).then(res => {
-											r(res.itemInfo)
+											r(exerciseItem)
 										})
 									}else{
 										j(500)
@@ -402,8 +439,23 @@
 					}
 					Promise.all(promiseArr).then(result => {
 						console.log(result)
+						let slides = []
+						// 主观题的answer为空数组
+						let nullType = ['complete','subjective','compose']
+						result.forEach(item => {
+							let o = {
+								url: item.id + '.json',
+								type: item.type,
+								scoring: {
+									score: item.score,
+									ans: nullType.includes(item.type) ? [] : item.answer
+								}
+							}
+							item.type === 'compose' && delete o.scoring
+							slides.push(o)
+						})
 						resolve({
-							urls:result.map(i => i.id + '.json'),
+							slides:slides,
 							files:itemJsonFiles
 						})
 					}).catch(err => {
@@ -460,22 +512,29 @@
 
 			/** 保存当前试卷数据 */
 			async saveTestPaper() {
-				let hasSurplus = this.$refs.testPaper.$refs.exList.surPlusScore // 判断是否有剩余分数未分配
-				let noScoreList = this.$refs.testPaper.$refs.exList.exerciseList.filter(item => item.score === 0) // 判断是否有未配分的题目
-				let hasErrorItem = this.$refs.testPaper.$refs.exList.errorList.length > 0
+				let exListVm = this.$refs.testPaper.$refs.exList
+				let hasSurplus = exListVm.surPlusScore // 判断是否有剩余分数未分配
+				let noScoreList = exListVm.exerciseList.filter(item => item.score === 0) // 判断是否有未配分的题目
+				let hasErrorItem = exListVm.errorList.length > 0
+				let groupTypeList  = exListVm.groupTypeList
 				let list = this.evaluationInfo.item
 				if(this.evaluationInfo.name.trim() === ''){
 					this.$Message.warning('试卷名称不能为空!')
 					return 
 				}
-				console.log(this.$refs.testPaper.$refs.exList.errorList)
 				if(!hasErrorItem){
 					if (hasSurplus === 0) {
 						if (!noScoreList.length) {
 							let isPaperExist = await this.isPaperExist(this.evaluationInfo.name)
 							if(!isPaperExist){
 								if (list.length) {
-									this.doSavePaper(list)
+									// 拿到题型顺序的试题数组进行拼接
+									let arr = []
+									groupTypeList.forEach(i => {
+										arr = arr.concat(i.list)
+									})
+									if(!this.checkComposeScore(arr)) return
+									this.doSavePaper(arr)
 								} else {
 									this.$Message.warning('未选择任何试题!')
 								}
@@ -486,11 +545,16 @@
 									okText: '确认',
 									cancelText: '取消',
 									onOk: async () => {
+										// 拿到题型顺序的试题数组进行拼接
+										let arr = []
+										groupTypeList.forEach(i => {
+											arr = arr.concat(i.list)
+										})
+										if(!this.checkComposeScore(arr)) return
 										let blobList = await this.getPaperFiles('paper/' + this.evaluationInfo.name)
 										let files = blobList.blobList.map(i => i.blob)
-										console.log(files)
 										this.onDeleteBlobPaper(files).then(r => {
-											this.doSavePaper(list)
+											this.doSavePaper(arr)
 										})
 									},
 									onCancel:() => {
@@ -502,13 +566,33 @@
 							this.$Message.warning(`存在未配分的题目,请配分后再保存!`)
 						}
 					} else {
-						this.$Message.warning(`试卷配分未完成!剩余 ${hasSurplus} 分数可分配`)
+						this.$Message.warning(`保存前请确认试卷配分是否与试卷总分一致!`)
 					}
 				}else{
 					this.$Message.warning(`存在异常试题,请修改或者重新导入!`)
 				}
 			},
 			
+			/* 检查综合题配分是否正确 */
+			checkComposeScore(arr){
+				let flag = true
+				arr.forEach((i,index) => {
+					if(i.type === 'compose' && i.children.length){
+						let childTotalScore = i.children.reduce((p, e) => parseInt(p) + parseInt(e.score), 0)
+						let hasNoScoreChild = i.children.filter(j => !j.score).length > 0
+						console.log(childTotalScore)
+						if(i.score !== childTotalScore || hasNoScoreChild){
+							this.$Message.warning(`第 ${ index + 1 } 题综合题的配分异常,请检查后重试!`)
+							flag = false
+						}
+					}else{
+						flag = true
+					}
+				})
+				
+				return flag
+			},
+			
 			getPaperFiles(path){
 				return new Promise(async (r,j) => {
 					// 获取初始化Blob需要的数据
@@ -569,11 +653,11 @@
 						periodId: this.isSchool ? this.schoolInfo.period[this.evaluationInfo.paperPeriod].id : null,
 						name: this.evaluationInfo.name,
 						points:this.getPaperPoints(this.evaluationInfo.item),
-						scoring: this.getAnswers(this.evaluationInfo.item),
+						scoring: this.getAnswers(list),
 						score: this.evaluationInfo.score,
 						multipleRule:multipleRule
 					}
-					let blobPaper = await this.$evTools.createBlobPaper(paperItem,res.urls)
+					let blobPaper = await this.$evTools.createBlobPaper(paperItem,res.slides)
 					// 首先保存新题目的JSON文件到Blob 然后返回URL链接
 					let paperFile = new File([JSON.stringify(blobPaper)], "index.json");
 					// 获取初始化Blob需要的数据

+ 1 - 0
TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.less

@@ -27,6 +27,7 @@
 
     .paper-title {
         font-size: 30px;
+		margin: 30px 0;
         font-weight: bold;
         vertical-align: middle;
         text-align: center;

+ 6 - 2
TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue

@@ -13,7 +13,7 @@
 				</div> -->
 				<div class="paper-body">
 					<!-- 试卷基础信息 -->
-					<div class="paper-base-info">
+					<div class="paper-base-info" v-show="!isPreview">
 						<div style="display: flex;">
 							<div class="paper-info-items">
 								<span class="base-info-item">试卷总分:<span class="analysis-info" style="cursor: pointer;" @click="isSetScore = !isSetScore">{{ paperInfo.score }}</span>分</span>
@@ -42,7 +42,7 @@
 					<ExamPaperAnalysis :testPaper="paperInfo" v-show="isShowAnalysis"></ExamPaperAnalysis>
 
 					<!-- 题目类型及列表 -->
-					<BaseExerciseList :paper="paperInfo" @dataUpdate="onListUpdate" v-show="!isShowAnalysis" ref="exList" :isShowTools="isShowTools"
+					<BaseExerciseList :paper="paperInfo" @dataUpdate="onListUpdate" v-show="!isShowAnalysis" ref="exList" :isShowTools="!isPreview"
 					 @toggleChange="onToggleChange" @scoreUpdate="scoreUpdate"></BaseExerciseList>
 				</div>
 			</div>
@@ -88,6 +88,10 @@
 				type: Boolean,
 				default: true
 			},
+			isPreview: {
+				type: Boolean,
+				default: false
+			},
 			isShowBaseInfo: {
 				type: Boolean,
 				default: true

+ 0 - 174
TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaperList.css

@@ -1,174 +0,0 @@
-.ev-list-container {
-    user-select:none !important;
-}
-    .ev-list-container .ev-header {
-        background:#fff;
-        padding:10px;
-    }
-    .ev-list-container .ev-title {
-        font-size: 20px;
-        font-weight: bold;
-        margin-left: 5px;
-        vertical-align: middle;
-    }
-
-    .ev-list-container .ev-length {
-        font-size: 12px;
-        font-weight: bold;
-        margin-left: 30px;
-        display:inline-block;
-        margin-top:5px;
-        vertical-align:text-top;
-    }
-
-    .ev-list-container .ivu-divider-horizontal {
-        margin:15px 0;
-    }
-
-.ev-content {
-    background:none;
-}
-.content-wrap {
-    width: 100%;
-    height: auto;
-    display: flex;
-    flex-direction: column;
-    margin-top: 10px;
-    background: #fff;
-}
-
-    .content-wrap .exercise-item {
-        width: 100%;
-        height: 60px;
-        padding: 60px 30px;
-        font-size: 14px;
-        display: flex;
-        flex-direction: row;
-        align-items: center;
-        border-bottom: 1px solid rgba(230, 230, 230, 0.67);
-        cursor:pointer;
-    }
-
-        .content-wrap .exercise-item .icon-paper {
-            width: 50px;
-            height:50px;
-        }
-
-.exercise-item .paper-content {
-    width: 70%;
-    padding-left: 20px;
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-}
-
-    .exercise-item .paper-content .paper-title {
-        font-size:18px;
-        font-weight:bold;
-    }
-
-    .exercise-item .paper-content .paper-info {
-        font-size: 12px;
-        margin-top: 10px;
-        display: flex;
-        flex-direction: row;
-        align-items: center;
-    }
-
-        .exercise-item .paper-content .paper-info span {
-            margin-left: 25px;
-            color: rgb(144, 144, 144);
-            display:flex;
-            flex-direction:row;
-            justify-content:center;
-            align-items:center;
-        }
-
-        .exercise-item .paper-content .paper-info .ivu-icon {
-            font-size:16px;
-            margin-right:5px;
-        }
-
-
-.exercise-item .paper-operation .ivu-icon {
-    /*font-size:16px;*/
-}
-
-
-.complete-line {
-    margin-left: 5px;
-    padding: 0 45px;
-    border-bottom: 2px solid rgb(128, 128, 128);
-}
-
-.ev-list-container h1, h2, h3, h4, h5, h6 {
-    display:inline-block;
-}
-
-.filter-wrap {
-    width: 100%;
-    padding: 20px;
-    background: #fff;
-}
-
-    .filter-wrap .filter-item:first-child {
-        margin-top:0px;
-    }
-
-    .filter-wrap .filter-item {
-        margin-top:10px;
-    }
-
-    .filter-wrap .filter-title {
-          font-size:14px;
-          font-weight:bold;
-          margin-right:10px;
-    }
-
-    .filter-wrap .ivu-radio-group {
-        padding-bottom:4px;
-    }
-
-    .filter-wrap .ivu-radio-wrapper {
-        padding:0 10px;
-        margin-left: 15px;
-        height:28px;
-        line-height:28px;
-        box-shadow: none !important;
-        border: none !important;
-        font-size: 14px;
-        border-radius: 0 !important;
-    }
-
-    .filter-wrap .ivu-radio-wrapper .ivu-icon {
-        margin-bottom:2px;
-    }
-
-        .filter-wrap .ivu-radio-wrapper:after, .ivu-radio-group-button .ivu-radio-wrapper:before {
-            content:none;
-        }
-
-    .filter-wrap .ivu-radio-group-item {
-        border-radius: 5px !important;
-    }
-
-    .filter-wrap .ivu-radio-wrapper-checked {
-        background: rgb(16, 171, 231);
-        box-shadow: none !important;
-        color: white;
-    }
-
-    .filter-wrap .ivu-radio-group-button .ivu-radio-wrapper-checked:hover {
-        color:#fff !important;
-    }
-
-
-.ev-list-container .ivu-page {
-    display:flex;
-    flex-direction:row;
-    justify-content:center;
-    margin:20px 0;
-}
-
-
-

+ 0 - 25
TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaperList.vue

@@ -1,25 +0,0 @@
-<template>
-
-</template>
-<script>
-
-  export default {
-
-    data() {
-      return {
-
-      }
-    },
-    created() {
-
-    },
-    methods: {
-
-    },
-    mounted() {
-
-    }
-  }
-</script>
-<style src="../index/TestPaperList.css" scoped>
-</style>

+ 1 - 1
TEAMModelOS/ClientApp/src/view/evaluation/types/BaseSingle.vue

@@ -237,7 +237,7 @@
 			let stemEditor = new E(this.$refs.singleEditor)
 			stemEditor.config.onchange = (html) => {
 				this.stemContent = html
-			}
+			} 
 			stemEditor.config.uploadImgShowBase64 = true;
 			this.$editorTools.addVideoUpload(this, stemEditor)
 			this.$editorTools.addAudio(this, stemEditor)

+ 0 - 298
TEAMModelOS/ClientApp/src/view/evaluation/types/BaseSingleNew.vue

@@ -1,298 +0,0 @@
-<template>
-	<div>
-		<div class="exersices-content">
-			<IconText :text="'题目'" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
-			<div @click="onRichTextClick($event)">
-				<div ref="singleEditor" style="text-align:left"></div>
-			</div>
-		</div>
-		<div class="exersices-option">
-			<IconText :text="'单选选项'" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
-			<div v-for="(item,index) in options" :key="index" :ref="'optionBox' + index" :class="'editor-wrap-'+item" style="margin-top:10px;display:flex"
-			 @click="onRichTextClick($event)">
-				<span class="fl-center option-delete" @click="deleteOption(index,item)">
-					<Icon type="md-remove-circle" /></span>
-				<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 :ref="'singleOption'+index" style="text-align:left" class="option-editor" @click="optionClick(item)"></div>
-				<span :class="['fl-center', 'option-setting', optionTrueIndex === index ? 'option-true':'']" @click="settingAnswer(index)">{{ optionTrueIndex === index ? '正确答案' :'设为答案' }}</span>
-
-			</div>
-			<p class="option-add"><span @click="addOption()">+ 添加选项 </span><span style="color:rgb(60,196,82);margin-left:15px;font-weight:bold">正确答案:{{ renderIndex(trueIndex) }}</span></p>
-		</div>
-	</div>
-</template>
-<script>
-	import E from 'wangeditor'
-	import IconText from '@/components/evaluation/IconText.vue'
-	export default {
-		components: {
-			IconText
-		},
-		props: ['editInfo'],
-		data() {
-			return {
-				options: [...new Array(4).keys()], // 默认四个选项
-				existOptions: [...new Array(4).keys()],
-				initFlag: true,
-				trueIndex: 0,
-				optionTrueIndex: 0,
-				editSingleInfo: {},
-				stemEditor: null,
-				stemContent: '',
-				optionsContent: [],
-				optionEditors: [],
-				defaultConfig: {
-					uploadImgShowBase64: true,
-					menus: this.$tools.wangEditorMenu
-				}
-			}
-		},
-		created() {},
-		methods: {
-			initEditors() {
-				// Editor默认配置
-				if (this.options.length > 0) {
-					this.options.forEach((item, i) => {
-						let that = this
-						let editor = new E(that.$refs['singleOption' + i][0])
-
-						// 选项编辑器失焦隐藏工具栏
-						editor.config.onblur = function() {
-							let allToolbars = document.getElementsByClassName('option-editor')
-							for (let i = 0; i < allToolbars.length; i++) {
-								if (allToolbars[i].children.length) {
-									allToolbars[i].children[0].style.visibility = 'hidden'
-								}
-							}
-						}
-
-						editor.config.uploadImgShowBase64 = true;
-						this.$editorTools.addVideoUpload(this, editor)
-						this.$editorTools.addAudio(this, editor)
-
-						// 选项编辑器内容发生变化时
-						editor.config.onchange = (html) => {
-							let key = String.fromCharCode(64 + parseInt(i + 1))
-							let codeArr = this.optionsContent.map(item => item.code)
-							// 如果已经编辑过则 修改选项内容
-							if (codeArr.indexOf(key) !== -1) {
-								this.optionsContent[codeArr.indexOf(key)].value = html
-							} else { // 否则创建新选项
-								let option = {
-									code: key,
-									value: html
-								}
-								this.optionsContent.push(option)
-							}
-						}
-						editor.create()
-						this.optionEditors.push(editor)
-						that.$refs["singleOption" + i][0].dataset.editorId = editor.id
-
-						// 如果是编辑状态 则将选项内容回显
-						if (Object.keys(this.editSingleInfo).length > 0) {
-							editor.txt.html(this.editSingleInfo.option[i].value)
-						}
-					})
-				}
-			},
-
-			onRichTextClick(e) {
-				this.$parent.onRichTextClick(e)
-			},
-			/* 添加选项 */
-			addOption() {
-				let that = this
-				let wraps = document.getElementsByClassName('option-editor')
-				let optionsLength = wraps.length;
-				let newIndex = parseInt(this.options[this.options.length - 1]) + 1
-				// let optionsLength = this.options.length
-				if (optionsLength < 10) {
-					this.options.push(newIndex)
-					this.existOptions.push(newIndex)
-					this.$nextTick(() => {
-						let editor = new E(that.$refs['singleOption' + newIndex][0])
-						editor.customConfig = this.defaultConfig
-
-						editor.customConfig.onchange = (html) => {
-							let key = String.fromCharCode(64 + parseInt(newIndex + 1))
-							let codeArr = this.optionsContent.map(item => item.code)
-							// 如果已经编辑过则 修改选项内容
-							if (codeArr.indexOf(key) !== -1) {
-								this.optionsContent[codeArr.indexOf(key)].value = html
-								console.log(this.optionsContent.map(item => item.code))
-								console.log(this.optionsContent.map(item => item.value))
-							} else { // 否则创建新选项
-								let option = {
-									code: key,
-									value: html
-								}
-								this.optionsContent.push(option)
-							}
-						}
-						editor.create()
-						this.optionEditors.push(editor);
-						this.$refs["singleOption" + newIndex][0].dataset.editorId = editor.id
-						this.refreshOrder()
-					})
-				} else {
-					this.$Message.warning('最多只有10个选项!')
-				}
-			},
-
-
-			/* 设置正确答案 */
-			settingAnswer(index) {
-				console.log(index)
-				this.optionTrueIndex = index
-				let wraps = document.getElementsByClassName('option-order')
-				for (let i = 0; i < wraps.length; i++) {
-					let item = wraps[i]
-					if (+index === +item.dataset.index) {
-						this.trueIndex = item.innerText.charCodeAt() - 65
-						break;
-					}
-				}
-				// wraps.forEach((item, orderIndex) => {
-				// 	console.log(item)
-				// 	console.log(item.dataset.index)
-
-				// })
-			},
-
-			/* 根据页面上的选项DOM数量,刷新每个选项的Order显示 */
-			refreshOrder() {
-				let wraps = document.getElementsByClassName('option-order')
-				wraps.forEach((item, index) => {
-					item.innerHTML = this.renderIndex(index)
-				})
-			},
-
-			/* 根据下标渲染对应的字母顺序 */
-			renderIndex(index) {
-				return String.fromCharCode(64 + parseInt(index + 1))
-			},
-
-
-
-			/* 删除选项 */
-			deleteOption(index, item) {
-				// 如果删除的是正确答案前面的选项 则正确答案需要往前排一位
-				if (index < this.optionTrueIndex) {
-					this.trueIndex--
-				}
-				// 拿到所有选项
-				if (this.existOptions.length > 2) {
-					// 确保当前存在的options保持同步
-					this.existOptions.splice(this.existOptions.indexOf(item), 1)
-					this.optionsContent.splice(index, 1)
-					// 删除操作 移除选项DOM
-					let textWrap = this.$refs['optionBox' + index][0]
-					textWrap.remove()
-					// 如果删除的是正确答案 则重置正确答案
-					if (index === this.optionTrueIndex) {
-						this.optionTrueIndex = this.existOptions[0]
-						this.trueIndex = 0
-					}
-					// 刷新选项序号显示
-					this.refreshOrder()
-				} else {
-					this.$Message.warning('至少保留两个选项!')
-				}
-			},
-
-			/* 保存试题 获取最新选项数据 */
-			doSave() {
-				// 拿到当前还剩的选项DOM
-				let wraps = document.getElementsByClassName('option-editor')
-				let arr = []
-				wraps.forEach((item, index) => {
-					// 遍历选项 找到对应的 Editor 然后获取编辑器里面的内容
-					let id = item.dataset.editorId
-					let curEditor = this.optionEditors.filter(i => i.id === id)[0]
-					if (curEditor) {
-						// 生成新的选项对象
-						let obj = {
-							code: String.fromCharCode(64 + parseInt(index + 1)),
-							value: curEditor.txt.html()
-						}
-						arr.push(obj)
-					}
-
-				})
-				this.optionsContent = arr
-			},
-
-			/* 模拟选项聚焦事件 */
-			optionClick(index) {
-				let allToolbars = document.getElementsByClassName('option-editor')
-				let that = this
-				for (let i = 0; i < allToolbars.length; i++) {
-					allToolbars[i].children[0].style.visibility = 'hidden'
-				}
-				setTimeout(function() {
-					let currentToolBar = that.$refs['singleOption' + index][0].children[0]
-					currentToolBar.style.visibility = 'visible'
-				}, 100)
-			}
-		},
-		mounted() {
-			let stemEditor = new E(this.$refs.singleEditor)
-			// stemEditor.config = this.defaultConfig
-			stemEditor.config.onchange = (html) => {
-				this.stemContent = html
-			}
-			stemEditor.config.uploadImgShowBase64 = true;
-			this.$editorTools.addVideoUpload(this, stemEditor)
-			this.$editorTools.addAudio(this, stemEditor)
-
-			stemEditor.create()
-			this.stemEditor = stemEditor
-			this.initEditors()
-
-			if (this.editInfo && this.editInfo.question) {
-				console.log('进入单选题Mounted编辑')
-				this.editSingleInfo = this.editInfo
-				this.stemContent = this.editSingleInfo.question
-				this.optionsContent = this.editSingleInfo.option
-				this.trueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
-				this.optionTrueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
-				this.options = this.editSingleInfo.option.map((item, index) => index)
-				this.existOptions = this.editSingleInfo.option.map((item, index) => index)
-				this.$nextTick(() => {
-					this.initEditors()
-					this.stemEditor.txt.html(this.editSingleInfo.question)
-				})
-			}
-		},
-		watch: {
-			editInfo: {
-				handler(newValue, oldValue) {
-					if (newValue) {
-						console.log('单选接收到的数据')
-						console.log(newValue)
-						this.editSingleInfo = newValue
-						if (Object.keys(newValue).length > 0) {
-							this.stemContent = this.editSingleInfo.question
-							this.optionsContent = this.editSingleInfo.option
-							this.trueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
-							this.optionTrueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
-							this.options = this.editSingleInfo.option.map((item, index) => index)
-							this.existOptions = this.editSingleInfo.option.map((item, index) => index)
-							this.$nextTick(() => {
-								this.initEditors()
-								this.stemEditor.txt.html(this.editSingleInfo.question)
-							})
-						}
-					}
-				},
-				// immediate:true
-			},
-
-		}
-	}
-</script>
-<style lang="less" scoped>
-	@import"../index/CreateExercises.less";
-</style>

+ 0 - 1
TEAMModelOS/ClientApp/src/view/learnactivity/ExamPaperAnalysis.vue

@@ -333,7 +333,6 @@
             },
             drawDiffChart() {
                 this.option.series[0].data = this.examAnalysisData.difficult.count
-				console.log(this.examAnalysisData)
                 this.myPie.setOption(this.option)
             },
             getQuestionLabel(key) {

+ 19 - 18
TEAMModelOS/Controllers/Exam/ImportExerciseController.cs

@@ -32,7 +32,7 @@ using TEAMModelOS.Servicess.PowerPoint.Implement;
 
 namespace TEAMModelOS.Controllers
 {
-    [Route("api/[controller]")]
+    [Route("api/import")]
     [ApiController]
     public class ImportExerciseController : BaseController
     {
@@ -66,7 +66,7 @@ namespace TEAMModelOS.Controllers
         /// </summary>
         /// <param name="request"></param>
         /// <returns></returns>
-        [HttpPost("LoadDoc")]
+        [HttpPost("load-doc")]
         [RequestSizeLimit(102_400_000_00)] //最大10000m左右
         public async Task<BaseResponse> LoadDoc([FromForm] IFormFile file)
         {
@@ -82,9 +82,9 @@ namespace TEAMModelOS.Controllers
         /// </summary>
         /// <param name="request"></param>
         /// <returns></returns>
-        [HttpPost("analysis-pptx")]
+        [HttpPost("parse-pptx")]
         [RequestSizeLimit(102_400_000_00)] //最大10000m左右
-        public async Task<IActionResult> AnalysisPPTX(JsonElement request)
+        public async Task<IActionResult> ParsePPTX(JsonElement request)
         {
             //string id_token = HttpContext.GetXAuth("IdToken");
             //if (string.IsNullOrEmpty(id_token)) return BadRequest();
@@ -98,13 +98,13 @@ namespace TEAMModelOS.Controllers
             string ContainerName = a.Item1;
             string BlobName = a.Item2;
             bool flg = IsBlobName(BlobName);
-            var codes = code.ToString().Split("/");
-            var FileName = codes[codes.Length - 1];
+            var codes = azureBlobSAS.Split("/");
+            var FileName = codes[codes.Length - 1].Split(".")[0];
             if (flg)
             {
                 //TODO 需驗證
                 BlobAuth blobAuth = _azureStorage.GetBlobSasUriRead(ContainerName, BlobName);
-                var response = await _clientFactory.CreateClient().GetAsync(new Uri(blobAuth.url + blobAuth.sas));
+                var response = await _clientFactory.CreateClient().GetAsync(new Uri(blobAuth.url));
                 response.EnsureSuccessStatusCode();
                 Stream stream=  await response.Content.ReadAsStreamAsync();
                 string  index = await PPTXTranslator(ContainerName, FileName, stream);
@@ -144,7 +144,7 @@ namespace TEAMModelOS.Controllers
             var id = jwt.Payload.Sub;
             if (FileType.GetExtention(file.FileName).ToLower().Equals("pptx") || FileType.GetExtention(file.FileName).ToLower().Equals("xml"))
             {
-                string FileName = file.FileName;
+                string FileName = file.FileName.Split(".")[0];
                 Stream streamFile = file.OpenReadStream();
                 string index = await PPTXTranslator(id, FileName, streamFile);
                 return Ok(new { index = index });
@@ -160,10 +160,12 @@ namespace TEAMModelOS.Controllers
         /// docUrl
         /// folder
         /// shaCode
+        /// 
+        /// UploadWord
         /// </summary>
         /// <param name="request"></param>
         /// <returns></returns>
-        [HttpPost("UploadWord")]
+        [HttpPost("upload-word")]
         [RequestSizeLimit(102_400_000_00)] //最大10000m左右
         public async Task<IActionResult> UploadWord([FromForm] IFormFile file)
         {
@@ -177,11 +179,11 @@ namespace TEAMModelOS.Controllers
         }
 
         /// <summary>
-        /// htmlString 
+        /// htmlString AnalyzeHtml
         /// </summary>
         /// <param name="request"></param>
         /// <returns></returns>
-        [HttpPost("AnalyzeHtml")]
+        [HttpPost("parse-html")]
         public  IActionResult  AnalyzeHtml(JsonElement request)
         {
             //ResponseBuilder builder = ResponseBuilder.custom();
@@ -211,7 +213,7 @@ namespace TEAMModelOS.Controllers
         /// <param name="request"></param>
         /// <returns></returns>
         [HttpPost("HtmlToHtex")]
-        public async Task<BaseResponse> HtmlToHtex(JsonElement request)
+        public async Task<IActionResult> HtmlToHtex(JsonElement request)
         {
             ResponseBuilder builder = ResponseBuilder.custom();
 /*            Dictionary<string, object> dict = new Dictionary<string, object>();
@@ -227,30 +229,29 @@ namespace TEAMModelOS.Controllers
                 LangConfig langConfig = langConfigs.Where(x => x.Lang == lang.ToString()).FirstOrDefault();
                 HtmlAnalyzeService htmlAnalyzeService = new HtmlAnalyzeService(langConfig);
                 Models.PowerPoint.Htex exercises = await HtexService.AnalyzeHtmlToHtex(_azureStorage, htmlString.ToString(), lang.ToString(), htmlAnalyzeService);
-                return builder.Data(exercises).build();
+                return Ok(exercises);
             }
             else
             {
-                return builder.Data(null).build();
+                return BadRequest("语言设置错误");
             }
         }
 
         private async Task<string> PPTXTranslator(string id, string FileName, Stream streamFile)
         {
-            //var day = new DateTimeOffset(DateTime.UtcNow).ToString("yyyyMMdd", DateTimeFormatInfo.InvariantInfo);
-            string shaCode = ShaHashHelper.GetSHA1(streamFile);
+            var status = await _azureStorage.GetBlobServiceClient().DelectBlobs(id, $"res/{FileName}");
+            string shaCode = Guid.NewGuid().ToString("N");
             HTEXLib.Htex htex = htexGenerator.Generator(streamFile);
             htex.name = FileName;
             var slides = htex.slides;
             List<Task> tasks = new List<Task>();
             HTEX hTEX = new HTEX() { name = FileName, size = htex.size, thumbnail = htex.thumbnail, id = shaCode };
-
             Dictionary<string, string> texts = new Dictionary<string, string>();
             List<string> shas = new List<string>();
             foreach (var slide in slides)
             {
                 string text = JsonHelper.ToJson(slide, ignoreNullValue: false);
-                string sha = ShaHashHelper.GetSHA1(text);
+                string sha = Guid.NewGuid().ToString("N");
                 texts.Add(sha, text);
                 shas.Add(sha);
             }

+ 4 - 0
TEAMModelOS/Controllers/Syllabus/ItemInfoController.cs

@@ -340,6 +340,10 @@ namespace TEAMModelOS.Controllers
                 {
                     dict.Add("gradeIds[*]", gradeIds);
                 }
+                if (requert.TryGetProperty("pId", out JsonElement pId))
+                {
+                    dict.Add("pId", pId);
+                }
                 AzureCosmosQuery cosmosDbQuery = SQLHelper.GetSQL(dict, sql);
                 List<object> items = new List<object>();
                 if (scope.ToString().Equals("private"))

+ 16 - 0
TEAMModelOS/Controllers/xTest/BlobController.cs

@@ -100,6 +100,9 @@ namespace TEAMModelOS.Controllers.Core
             return responseBuilder.Data(await _azureStorage.UploadFileByContainer("hbcn", request.ToJsonString(), "exam", _snowflakeId.NextId() + ".json")).build();
 
         }
+      
+
+        
         /// <summary>
         /// 获取文件内容
         /// </summary>
@@ -131,6 +134,19 @@ namespace TEAMModelOS.Controllers.Core
 
         }
 
+        /// <summary>
+        /// 测试单个文本内容的上传
+        /// </summary>
+        /// <param name="azureBlobSASDto"></param>
+        /// <returns></returns>
+        [HttpPost("deleteBlob")]
+        public async Task<BaseResponse> DeleteBlob(JsonElement request)
+        {
+            ResponseBuilder responseBuilder = new ResponseBuilder();
+            var client = _azureStorage.GetBlobBatchClient().DeleteBlobs(new Uri[] {new Uri("https://teammodelstorage.blob.core.chinacloudapi.cn/ydzt/%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9%2FAnimation.xml") });
+            return responseBuilder.Data(client[0].Status).build();
+
+        }
         /// <summary>
         /// 测试单个文本内容的上传
         /// </summary>

+ 1 - 0
TEAMModelOS/Models/CommonInfo/ItemInfo.cs

@@ -89,6 +89,7 @@ namespace TEAMModelOS.Models.CommonInfo
         public string examCode { get; set; }
         public string blob { get; set; }
         public string scope { get; set; }
+        public string pId { get; set; }
     }