Sfoglia il codice sorgente

学员提交和教师查看

dusenyao 2 anni fa
parent
commit
8088827538

+ 8 - 13
package-lock.json

@@ -13,7 +13,7 @@
         "awe-dnd": "^0.3.4",
         "axios": "^0.27.2",
         "book-ui": "file:../book-ui-0.3.19.tgz",
-        "core-js": "^3.26.1",
+        "core-js": "^3.27.0",
         "dayjs": "^1.11.7",
         "element-ui": "^2.15.12",
         "gcls-book-question-ui": "file:../gcls-book-question-ui-0.1.0.tgz",
@@ -6956,15 +6956,10 @@
       }
     },
     "node_modules/core-js": {
-      "version": "3.26.1",
-      "resolved": "https://repo.huaweicloud.com/repository/npm/core-js/-/core-js-3.26.1.tgz",
-      "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/core-js"
-      }
+      "version": "3.27.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.27.0.tgz",
+      "integrity": "sha512-wY6cKosevs430KRkHUIsvepDXHGjlXOZO3hYXNyqpD6JvB0X28aXyv0t1Y1vZMwE7SoKmtfa6IASHCPN52FwBQ==",
+      "hasInstallScript": true
     },
     "node_modules/core-js-compat": {
       "version": "3.24.0",
@@ -28499,9 +28494,9 @@
       }
     },
     "core-js": {
-      "version": "3.26.1",
-      "resolved": "https://repo.huaweicloud.com/repository/npm/core-js/-/core-js-3.26.1.tgz",
-      "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA=="
+      "version": "3.27.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.27.0.tgz",
+      "integrity": "sha512-wY6cKosevs430KRkHUIsvepDXHGjlXOZO3hYXNyqpD6JvB0X28aXyv0t1Y1vZMwE7SoKmtfa6IASHCPN52FwBQ=="
     },
     "core-js-compat": {
       "version": "3.24.0",

+ 1 - 1
package.json

@@ -18,7 +18,7 @@
     "awe-dnd": "^0.3.4",
     "axios": "^0.27.2",
     "book-ui": "file:../book-ui-0.3.19.tgz",
-    "core-js": "^3.26.1",
+    "core-js": "^3.27.0",
     "dayjs": "^1.11.7",
     "element-ui": "^2.15.12",
     "gcls-book-question-ui": "file:../gcls-book-question-ui-0.1.0.tgz",

+ 46 - 13
src/components/course/CoursewareView.vue

@@ -1,11 +1,23 @@
 <template>
   <div class="courseware-container">
     <template v-if="category === 'OC' || category.length === 0">
-      <bookquestion :context="context" />
+      <bookreport
+        v-if="isFinished"
+        :context="context"
+        :book-client-width="800"
+        :book-answer-content="coursewareData.exam_answer.content"
+      />
+      <bookquestion v-else :context="context" @handleBookUserAnswer="handleBookUserAnswer" />
     </template>
 
     <template v-else-if="category === 'AILP'">
-      <bookailp :context="context" :ui-type="ui_type" :preview-width="720" :preview-height="405" />
+      <bookailp
+        :context="context"
+        :ui-type="ui_type"
+        :preview-width="720"
+        :preview-height="405"
+        @handleBookUserAnswer="handleBookUserAnswer"
+      />
     </template>
 
     <template v-else-if="category === 'NPC'">
@@ -13,11 +25,13 @@
         v-if="context"
         ref="book"
         :context="context"
-        task-model=""
-        :is-show-save="false"
+        :task-model="isFinished ? 'ANSWER' : ''"
+        :is-show-save="true"
+        :book-answer-content="coursewareData.exam_answer.content"
         :theme-color="themeColor"
         :preview-type="previewType"
         :preview-group-id="previewGroupId"
+        @finishTaskMaterial="saveNPCAnswer"
       />
     </template>
 
@@ -28,10 +42,11 @@
         :context="context"
         :theme-color="themeColor"
         task-model=""
-        :is-show-save="false"
+        :is-show-save="true"
         :is-show-title="true"
         :preview-type="previewType"
         :preview-group-id="previewGroupId"
+        @finishTaskMaterial="saveNPCAnswer"
       />
     </template>
 
@@ -48,22 +63,40 @@ export default {
 </script>
 
 <script setup>
+import { watch } from 'vue';
 import { useShowCourseware } from './courseware';
 
 const props = defineProps({
-  coursewareId: {
-    type: String,
+  coursewareData: {
+    type: Object,
     required: true
   },
-  groupIdSelectedInfo: {
-    type: String,
-    required: true
+  addExamAnswer: {
+    type: Function,
+    default: () => {}
   }
 });
 
-const { context, ui_type, category, themeColor, bookFontSize, previewType, previewGroupId } = useShowCourseware(
-  props.coursewareId,
-  props.groupIdSelectedInfo
+let isFinished = props.coursewareData.is_finished === 'true';
+
+const {
+  context,
+  ui_type,
+  category,
+  themeColor,
+  bookFontSize,
+  previewType,
+  previewGroupId,
+  exam_answer,
+  saveNPCAnswer,
+  handleBookUserAnswer
+} = useShowCourseware(props.coursewareData.courseware_id, props.coursewareData.group_id_selected_info);
+
+watch(
+  () => exam_answer.value,
+  () => {
+    props.addExamAnswer(exam_answer.value);
+  }
 );
 </script>
 

+ 2 - 2
src/views/teacher/create_course/step_three/components/pop-up/VideoRecording.vue → src/components/course/common/VideoRecording.vue

@@ -29,9 +29,9 @@
 <script setup>
 import { ref, watch } from 'vue';
 import { funcLock } from '@/utils/common';
-import { videoRecording } from '../../../../../../components/course/common/recording.js';
+import { videoRecording } from './recording.js';
 import { fileUploadPrimordial } from '@/api/app.js';
-import { messageItem } from '../data/TaskData';
+import { messageItem } from '../../../views/teacher/create_course/step_three/components/data/TaskData';
 import { Message } from 'element-ui';
 
 defineProps({

+ 16 - 1
src/components/course/courseware.js

@@ -5,7 +5,7 @@ const categoryList = ['OC', 'AILP', 'NPC', 'NNPE', 'RLC'];
 
 /**
  * 显示课件
- * @param {String} id 互动课件 id
+ * @param {String} courseId 互动课件 id
  * @param {String} groupId 分组id
  */
 export function useShowCourseware(courseId, groupId = '[]') {
@@ -16,6 +16,7 @@ export function useShowCourseware(courseId, groupId = '[]') {
   let bookFontSize = ref('');
   let previewType = ref('previewCheck');
   let previewGroupId = ref(groupId);
+  let exam_answer = ref('');
 
   function getCoursewareContent_View() {
     GetCoursewareContent_View({ id: unref(courseId) }).then(
@@ -63,6 +64,17 @@ export function useShowCourseware(courseId, groupId = '[]') {
   }
   getCoursewareContent_View();
 
+  function saveNPCAnswer(content, duration) {
+    exam_answer.value = {
+      duration,
+      content
+    };
+  }
+
+  function handleBookUserAnswer(data) {
+    exam_answer.value = data;
+  }
+
   return {
     context,
     ui_type,
@@ -71,6 +83,9 @@ export function useShowCourseware(courseId, groupId = '[]') {
     bookFontSize,
     previewType,
     previewGroupId,
+    exam_answer,
+    saveNPCAnswer,
+    handleBookUserAnswer,
     getCoursewareContent_View
   };
 }

+ 1 - 0
src/views/new_task_view/components/common/FileView.vue

@@ -240,6 +240,7 @@ function uploadHomework(file) {
         width: 112px;
         height: 112px;
         cursor: pointer;
+        border: 1px solid #ccc;
 
         .file-operation {
           position: absolute;

+ 241 - 40
src/views/new_task_view/components/common/MessageView.vue

@@ -1,67 +1,178 @@
 <template>
-  <div class="message">
-    <ul class="message-list">
-      <li
-        v-for="(item, i) in messageList"
-        :key="i"
-        :class="[
-          'message-list-item',
-          { text: item.message_type === messageItem[0].message_type },
-          { audio: item.message_type === messageItem[1].message_type },
-          {
-            video: item.message_type === messageItem[2].message_type
-          }
-        ]"
-      >
-        <template v-if="item.message_type === messageItem[0].message_type">
-          <div class="text">{{ item.text }}</div>
-        </template>
-        <template v-else-if="item.message_type === messageItem[1].message_type">
-          <div class="avatar">T</div>
-          <AudioShow
-            :file-id="item.file_id"
-            :stop-play="stopPlay"
-            @pauseOtherAudio="pauseOtherAudio"
-            @changeStopPlay="changeStopPlay"
-          />
-        </template>
-        <template v-else-if="item.message_type === messageItem[2].message_type">
-          <div class="avatar">T</div>
-          <VideoShow
-            :file-id="item.file_id"
-            :stop-play="stopPlay"
-            @pauseOtherAudio="pauseOtherAudio"
-            @changeStopPlay="changeStopPlay"
-          />
-        </template>
-      </li>
-    </ul>
+  <div>
+    <div class="message-box">
+      <div v-if="messageList.length <= 0" class="message-box-none">暂无内容</div>
+      <template v-else>
+        <ul class="message-list">
+          <li
+            v-for="({ message_type, text, file_id, is_student_reply }, i) in messageList"
+            :key="i"
+            :class="[
+              'message-list-item',
+              { reverser: is_student_reply === 'true' },
+              { text: message_type === messageItem[0].message_type },
+              { audio: message_type === messageItem[1].message_type },
+              {
+                video: message_type === messageItem[2].message_type
+              }
+            ]"
+          >
+            <template v-if="message_type === messageItem[0].message_type">
+              <div class="text">{{ text }}</div>
+            </template>
+            <template v-else-if="message_type === messageItem[1].message_type">
+              <div class="avatar">{{ is_student_reply === 'true' ? 'S' : 'T' }}</div>
+              <AudioShow
+                :file-id="file_id"
+                :stop-play="stopPlay"
+                @pauseOtherAudio="pauseOtherAudio"
+                @changeStopPlay="changeStopPlay"
+              />
+            </template>
+            <template v-else-if="message_type === messageItem[2].message_type">
+              <div class="avatar">{{ is_student_reply === 'true' ? 'S' : 'T' }}</div>
+              <VideoShow
+                :file-id="file_id"
+                :stop-play="stopPlay"
+                @pauseOtherAudio="pauseOtherAudio"
+                @changeStopPlay="changeStopPlay"
+              />
+            </template>
+            <svg-icon
+              v-if="
+                (is_student_reply === 'true' && !isTeacher) || (is_student_reply === 'false' && isTeacher)
+                  ? true
+                  : false
+              "
+              class-name="delete"
+              class="display-none"
+              icon-class="delete-current"
+              @click="deleteMessageItem(i)"
+            />
+          </li>
+        </ul>
+      </template>
+    </div>
+    <el-input
+      v-model="textMsg"
+      type="textarea"
+      class="sound-input"
+      resize="none"
+      placeholder="输入"
+      @keyup.enter.ctrl.native="sendMessage"
+    />
+    <div class="recording-operation">
+      <div class="switch">
+        <span
+          v-for="{ type, name } in audioAndVideo"
+          :key="type"
+          :class="[type === curRecord ? 'active' : '']"
+          @click="selectRecord(type, isRecording, closeRecording)"
+        >
+          {{ name }}
+        </span>
+      </div>
+      <template v-for="{ icon, buttonName, recordingButtonName, type } in audioAndVideo">
+        <div
+          v-if="curRecord === type"
+          :key="icon"
+          :class="['press-sound', icon]"
+          @mousedown="() => curRecord === audioAndVideo[0].type && startRecording()"
+          @click="() => (curRecord === audioAndVideo[1].type ? changVisible_video(true, { fn: getVideoData }) : '')"
+          @mouseup="() => curRecord === audioAndVideo[0].type && closeRecording()"
+        >
+          <svg-icon :icon-class="icon" class-name="button-icon" />
+          {{ isRecording ? recordingButtonName : buttonName }}
+        </div>
+      </template>
+    </div>
   </div>
 </template>
 
 <script setup>
-import { inject } from 'vue';
+import { watch, ref, inject } from 'vue';
 import { messageItem } from '@/views/teacher/create_course/step_three/components/data/TaskData.js';
+import { soundRecording, useRecordingPageData, audioAndVideo } from '@/components/course/common/recording';
+import { fileUploadPrimordial } from '@/api/app.js';
 
 import AudioShow from '@/components/course/common/AudioShow.vue';
 import VideoShow from '@/components/course/common/VideoShow.vue';
 
-defineProps({
+const props = defineProps({
   messageList: {
     type: Array,
     required: true
+  },
+  addMessageItem: {
+    type: Function,
+    default: () => {}
+  },
+  deleteMessageItem: {
+    type: Function,
+    default: () => {}
   }
 });
 
 const { stopPlay, changeStopPlay, pauseOtherAudio } = inject('stopPlay');
+const isTeacher = inject('isTeacher');
+
+// 文本
+let textMsg = ref('');
+function sendMessage() {
+  props.addMessageItem(
+    Object.assign({}, messageItem[0], {
+      text: textMsg.value,
+      is_student_reply: isTeacher ? 'false' : 'true'
+    })
+  );
+  textMsg.value = '';
+}
+
+let { curRecord, selectRecord } = useRecordingPageData();
+
+// 录音
+let { startRecording, closeRecording, isRecording, blob } = soundRecording();
+
+watch(blob, (newVal) => {
+  let formData = new FormData();
+  formData.append('录音.mp3', newVal, '录音.mp3');
+  fileUploadPrimordial('Mid', formData).then(({ file_info_list }) => {
+    if (file_info_list.length > 0) {
+      props.addMessageItem(
+        Object.assign({}, messageItem[1], {
+          file_id: file_info_list[0].file_id,
+          is_student_reply: isTeacher ? 'false' : 'true'
+        })
+      );
+    }
+  });
+});
+
+// 录影
+const { changVisible_video } = inject('visible_video');
+
+function getVideoData(data) {
+  props.addMessageItem({ ...data, is_student_reply: isTeacher ? 'false' : 'true' });
+}
 </script>
 
 <style lang="scss" scoped>
-.message {
+$tip-color: #999;
+
+// 消息框
+.message-box {
+  min-height: 230px;
   padding: 16px 0;
   background-color: #f4f4f4;
   border: 1px solid $border-color;
 
+  &-none {
+    height: 100%;
+    line-height: 212px;
+    text-align: center;
+    vertical-align: middle;
+  }
+
   .message-list {
     width: 100%;
     height: 100%;
@@ -85,11 +196,30 @@ const { stopPlay, changeStopPlay, pauseOtherAudio } = inject('stopPlay');
         width: 38px;
         height: 38px;
         line-height: 38px;
+        color: #2a76e8;
         text-align: center;
         background-color: #e3eeff;
         border-radius: 50%;
       }
 
+      &.reverser {
+        flex-direction: row-reverse;
+
+        .text {
+          margin-right: 54px;
+          margin-left: 0;
+        }
+
+        .avatar {
+          color: #fff;
+          background-color: #3dc45b;
+        }
+
+        :deep .audio-line {
+          background-color: #3dc45b;
+        }
+      }
+
       &.text:not(:first-child) {
         margin-top: 8px;
       }
@@ -100,6 +230,77 @@ const { stopPlay, changeStopPlay, pauseOtherAudio } = inject('stopPlay');
           margin-top: 22px;
         }
       }
+
+      &:hover .delete {
+        display: block;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+// 输入
+.sound-input {
+  :deep .el-textarea__inner {
+    height: 92px;
+    padding: 8px 12px;
+    font-size: 16px;
+    border: 1px solid $border-color;
+    border-top-width: 0;
+  }
+}
+
+// 操作
+.recording-operation {
+  display: flex;
+  justify-content: space-between;
+  padding: 8px 16px;
+  border: 1px solid $border-color;
+  border-top-width: 0;
+
+  .switch {
+    background-color: #e7e7e7;
+    border: 1px solid $border-color;
+    border-radius: 4px;
+
+    > span {
+      display: inline-block;
+      height: 28px;
+      padding: 2px 8px;
+      font-size: 16px;
+      line-height: 24px;
+      color: $tip-color;
+      cursor: pointer;
+      background-color: #e7e7e7;
+      border-radius: 3px;
+
+      &.active {
+        color: #000;
+        background-color: #fff;
+        box-shadow: 0 2px 4px rgba(0, 0, 0, 25%);
+      }
+    }
+  }
+
+  .press-sound {
+    padding: 4px 8px;
+    line-height: 22px;
+    cursor: pointer;
+    border-radius: 40px;
+
+    &.microphone {
+      color: #fff;
+      background-color: #de4444;
+    }
+
+    &.videocamera {
+      color: #de4444;
+      background-color: #fff;
+      border: 1px solid #de4444;
+    }
+
+    .button-icon {
+      margin-right: 4px;
     }
   }
 }

+ 21 - 4
src/views/new_task_view/components/common/SubtaskItem.vue

@@ -34,8 +34,8 @@
           <CoursewareView
             v-for="(data, j) in courseware_list"
             :key="`course-${j}`"
-            :courseware-id="data.courseware_id"
-            :group-id-selected-info="data.group_id_selected_info"
+            :courseware-data="data"
+            :add-exam-answer="addExamAnswer(i, j)"
           />
         </template>
         <FileView
@@ -44,7 +44,12 @@
           :homework-list="homework_list"
           :add-homework="addHomework(i)"
         />
-        <MessageView v-if="info_block_type === taskClassify[3].type" :message-list="message_list" />
+        <MessageView
+          v-if="info_block_type === taskClassify[3].type"
+          :message-list="message_list"
+          :add-message-item="addMessageItem(i)"
+          :delete-message-item="deleteMessageItem(i)"
+        />
       </div>
     </div>
   </div>
@@ -75,7 +80,19 @@ defineProps({
   },
   addHomework: {
     type: Function,
-    required: true
+    default: () => {}
+  },
+  addExamAnswer: {
+    type: Function,
+    default: () => {}
+  },
+  addMessageItem: {
+    type: Function,
+    default: () => {}
+  },
+  deleteMessageItem: {
+    type: Function,
+    default: () => {}
   }
 });
 

+ 40 - 3
src/views/new_task_view/components/student/index.vue

@@ -32,8 +32,8 @@
             <CoursewareView
               v-for="(data, i) in courseware_list"
               :key="`task-${i}`"
-              :courseware-id="data.courseware_id"
-              :group-id-selected-info="data.group_id_selected_info"
+              :courseware-data="data"
+              :add-exam-answer="curry(addExamAnswer)(taskIndex)(i)"
             />
           </template>
 
@@ -46,7 +46,12 @@
             :add-homework="curry(taskAddHomework)(taskIndex)"
           />
 
-          <MessageView v-if="[taskClassify[3].teaching_type].includes(teaching_type)" :message-list="message_list" />
+          <MessageView
+            v-if="[taskClassify[3].teaching_type].includes(teaching_type)"
+            :message-list="message_list"
+            :add-message-item="curry(addMessageItem)(taskIndex)"
+            :delete-message-item="curry(deleteMessageItem)(taskIndex)"
+          />
         </div>
         <!-- 子任务 -->
         <template v-if="child_task_list.length > 0">
@@ -58,6 +63,9 @@
             :class="[`${taskType}-${taskIndex}-${i}`]"
             :subtask-data="data"
             :add-homework="curry(subtaskAddHomework)(taskIndex)(i)"
+            :add-exam-answer="curry(addSubtaskAnswer)(taskIndex)(i)"
+            :add-message-item="curry(addSubtaskMessageItem)(taskIndex)(i)"
+            :delete-message-item="curry(deleteSubtaskMessageItem)(taskIndex)(i)"
           />
         </template>
       </div>
@@ -90,6 +98,7 @@ let { visible, curFileId, curFileName, dialogShowFileClose } = useShowFile();
 let taskList = inject('taskList');
 let taskType = inject('taskType');
 
+// 添加作业
 function taskAddHomework(index, file) {
   taskList.value[index].homework_list.push(file);
 }
@@ -97,6 +106,34 @@ function taskAddHomework(index, file) {
 function subtaskAddHomework(index, subIndex, infoIndex, file) {
   taskList.value[index].child_task_list[subIndex].info_block_list[infoIndex].homework_list.push(file);
 }
+
+// 课件添加答案
+function addExamAnswer(index, courseIndex, answer) {
+  taskList.value[index].courseware_list[courseIndex].exam_answer = answer;
+}
+
+function addSubtaskAnswer(index, subIndex, infoIndex, courseIndex, answer) {
+  taskList.value[index].child_task_list[subIndex].info_block_list[infoIndex].courseware_list[courseIndex].exam_answer =
+    answer;
+}
+
+// 添加消息列表项
+function addMessageItem(index, data) {
+  taskList.value[index].message_list.push(data);
+}
+
+function addSubtaskMessageItem(index, subIndex, infoIndex, data) {
+  taskList.value[index].child_task_list[subIndex].info_block_list[infoIndex].message_list.push(data);
+}
+
+// 删除消息列表项
+function deleteMessageItem(index, i) {
+  taskList.value[index].message_list.splice(i, 1);
+}
+
+function deleteSubtaskMessageItem(index, subIndex, infoIndex, i) {
+  taskList.value[index].child_task_list[subIndex].info_block_list[infoIndex].message_list.splice(i, 1);
+}
 </script>
 
 <style lang="scss" scoped>

+ 1 - 6
src/views/new_task_view/components/teacher/index.vue

@@ -29,12 +29,7 @@
         <div v-html="content"></div>
         <div class="task-content-type">
           <template v-if="[taskClassify[1].teaching_type, taskClassify[4].teaching_type].includes(teaching_type)">
-            <CoursewareView
-              v-for="(data, i) in courseware_list"
-              :key="`task-${i}`"
-              :courseware-id="data.courseware_id"
-              :group-id-selected-info="data.group_id_selected_info"
-            />
+            <CoursewareView v-for="(data, i) in courseware_list" :key="`task-${i}`" :courseware-data="data" />
           </template>
           <hr v-if="taskClassify[4].teaching_type === teaching_type" />
           <FileView

+ 1 - 0
src/views/new_task_view/index.js

@@ -63,6 +63,7 @@ export function useTask() {
 
   return {
     taskData,
+    taskList,
     studentList,
     curStudentId,
     getCSItemTaskList

+ 113 - 2
src/views/new_task_view/index.vue

@@ -12,7 +12,7 @@
 
     <div class="title">
       <div class="csitem-name">{{ taskData.cs_item_name }}</div>
-      <div class="title-button primary" @click="saveCSItem(cs_item_id)">
+      <div class="title-button primary" @click="saveCSItem">
         <svg-icon icon-class="preserve" /><span class="button-name">{{ isTeacher ? '保存' : '提交任务' }}</span>
       </div>
     </div>
@@ -40,6 +40,8 @@
         :student-list="studentList"
       />
     </div>
+
+    <VideoRecording :visible.sync="visible_video" @sendVideo="sendVideo" />
   </div>
 </template>
 
@@ -57,23 +59,132 @@ import { useTaskType, TASK_EXPLAIN } from './components/data/TaskType';
 import { useScale, useWheel, scale } from '@/views/teacher/create_course/step_three/components/utils/wheel';
 import { useMouseEvent } from '@/views/teacher/create_course/step_three/components/utils/mouseEvent';
 import { SubmitTaskHomework_Student } from '@/api/course';
+import { videoRecording } from '@/components/course/common/recording.js';
+import { useOtherPausePlay } from '@/components/course/common/play';
+import { Message } from 'element-ui';
 
 import StudentView from './components/student/index.vue';
 import TeacherView from './components/teacher/index.vue';
 import LeftSidebar from './components/layouts/LeftSidebar.vue';
 import RightSidebar from './components/layouts/RightSidebar.vue';
 import TaskExplain from './components/TaskExplain.vue';
+import VideoRecording from '@/components/course/common/VideoRecording.vue';
 
 const isTeacher = store.getters.isTeacher;
 let center = ref();
 
-let { taskData, studentList, curStudentId, getCSItemTaskList } = useTask();
+let { taskData, taskList, studentList, curStudentId, getCSItemTaskList } = useTask();
 let { curTaskType, taskType, curTaskTypeObj, changeTaskType } = useTaskType();
 
 const { centerStyle } = useMouseEvent(center);
 
 useWheel(center);
 const { changeScale } = useScale();
+
+// 录像页面显示
+const { visible_video, sendVideo } = videoRecording();
+useOtherPausePlay();
+
+function saveCSItem() {
+  isTeacher ? teacherSubmit() : studentSubmit();
+}
+
+function teacherSubmit() {}
+
+function studentSubmit() {
+  let task_id_list = []; // 任务id列表
+  let homework_file_list = []; // 作业文件列表
+  let material_finish_list = []; // 任务完成的资料列表
+  let message_reply_list = []; // 消息应答列表
+  taskList.value.forEach(({ homework_list, courseware_list, message_list, child_task_list, id }) => {
+    task_id_list.push(id);
+
+    if (homework_list.length > 0) {
+      homework_file_list.push({
+        task_id: id,
+        info_block_id: '',
+        file_id_list: homework_list.map(({ file_id }) => file_id)
+      });
+    }
+
+    if (courseware_list.length > 0) {
+      material_finish_list.push({
+        task_id: id,
+        info_block_id: '',
+        material_list: courseware_list
+          .filter(({ exam_answer }) => exam_answer !== undefined)
+          .map(({ courseware_id, exam_answer }) => {
+            return { material_id: courseware_id, material_type: 'COURSEWARE', exam_answer };
+          })
+      });
+    }
+
+    if (message_list.length > 0) {
+      message_reply_list.push({
+        task_id: id,
+        info_block_id: '',
+        message_list: message_list
+          .filter(({ is_student_reply }) => is_student_reply === 'true')
+          .map(({ message_type, text, file_id }) => {
+            if (message_type === 'text') {
+              return { message_type, text };
+            }
+            return { message_type, file_id };
+          })
+      });
+    }
+
+    child_task_list.forEach(({ info_block_list }) => {
+      info_block_list.forEach(
+        ({ id: info_block_id, info_block_type, homework_list, courseware_list, message_list }) => {
+          if (info_block_type === 'file' && homework_list.length > 0) {
+            homework_file_list.push({
+              task_id: id,
+              info_block_id,
+              file_id_list: homework_list.map(({ file_id }) => file_id)
+            });
+          }
+
+          if (info_block_type === 'courseware' && courseware_list.length > 0) {
+            material_finish_list.push({
+              task_id: id,
+              info_block_id,
+              material_list: courseware_list
+                .filter(({ exam_answer }) => exam_answer !== undefined)
+                .map(({ courseware_id, exam_answer }) => {
+                  return { material_id: courseware_id, material_type: 'COURSEWARE', exam_answer };
+                })
+            });
+          }
+
+          if (info_block_type === 'message' && message_list.length > 0) {
+            message_reply_list.push({
+              task_id: id,
+              info_block_id,
+              message_list: message_list
+                .filter(({ is_student_reply }) => is_student_reply === 'true')
+                .map(({ message_type, text, file_id }) => {
+                  if (message_type === 'text') {
+                    return { message_type, text };
+                  }
+                  return { message_type, file_id };
+                })
+            });
+          }
+        }
+      );
+    });
+  });
+  SubmitTaskHomework_Student({
+    cs_item_id: taskData.cs_item_id,
+    task_id_list,
+    material_finish_list,
+    homework_file_list,
+    message_reply_list
+  }).then(() => {
+    Message.success('提交任务成功');
+  });
+}
 </script>
 
 <style lang="scss" scoped>

+ 3 - 3
src/views/teacher/create_course/step_three/components/layouts/TaskEditor.vue

@@ -50,14 +50,14 @@ import { useMouseEvent } from '../utils/mouseEvent.js';
 import { useScale, useWheel, scale } from '../utils/wheel.js';
 import { taskData } from '../data/TaskData';
 import { useSelectCourseware } from '../task_template/components/courseware';
-import { videoRecording } from '../../../../../../components/course/common/recording.js';
-import { useOtherPausePlay } from '../../../../../../components/course/common/play';
+import { videoRecording } from '@/components/course/common/recording.js';
+import { useOtherPausePlay } from '@/components/course/common/play';
 
 import LeftSidebar from './LeftSidebar.vue';
 import RightSidebar from './RightSidebar.vue';
 import SelectTaskClassify from '../pop-up/SelectTaskClassify.vue';
 import SelectCourse from '@/components/select/SelectCourse.vue';
-import VideoRecording from '../pop-up/VideoRecording.vue';
+import VideoRecording from '@/components/course/common/VideoRecording.vue';
 
 const center = ref(); // vue3 获取 refs 写法,需要同名,且传空
 

+ 17 - 21
src/views/teacher/create_course/step_three/components/task_template/components/TemplateRecording.vue

@@ -5,33 +5,33 @@
       <template v-else>
         <ul class="message-list">
           <li
-            v-for="(item, i) in message_list"
+            v-for="({ message_type, text, file_id }, i) in message_list"
             :key="i"
             :class="[
               'message-list-item',
-              { text: item.message_type === messageItem[0].message_type },
-              { audio: item.message_type === messageItem[1].message_type },
+              { text: message_type === messageItem[0].message_type },
+              { audio: message_type === messageItem[1].message_type },
               {
-                video: item.message_type === messageItem[2].message_type
+                video: message_type === messageItem[2].message_type
               }
             ]"
           >
-            <template v-if="item.message_type === messageItem[0].message_type">
-              <div class="text">{{ item.text }}</div>
+            <template v-if="message_type === messageItem[0].message_type">
+              <div class="text">{{ text }}</div>
             </template>
-            <template v-else-if="item.message_type === messageItem[1].message_type">
+            <template v-else-if="message_type === messageItem[1].message_type">
               <div class="avatar">T</div>
               <AudioShow
-                :file-id="item.file_id"
+                :file-id="file_id"
                 :stop-play="stopPlay"
                 @pauseOtherAudio="pauseOtherAudio"
                 @changeStopPlay="changeStopPlay"
               />
             </template>
-            <template v-else-if="item.message_type === messageItem[2].message_type">
+            <template v-else-if="message_type === messageItem[2].message_type">
               <div class="avatar">T</div>
               <VideoShow
-                :file-id="item.file_id"
+                :file-id="file_id"
                 :stop-play="stopPlay"
                 @pauseOtherAudio="pauseOtherAudio"
                 @changeStopPlay="changeStopPlay"
@@ -48,7 +48,7 @@
       </template>
     </div>
     <el-input
-      v-model="text"
+      v-model="textMsg"
       type="textarea"
       class="sound-input"
       resize="none"
@@ -85,16 +85,12 @@
 
 <script setup>
 import { inject, ref, watch } from 'vue';
-import {
-  soundRecording,
-  useRecordingPageData,
-  audioAndVideo
-} from '../../../../../../../components/course/common/recording';
+import { soundRecording, useRecordingPageData, audioAndVideo } from '@/components/course/common/recording';
 import { taskData, messageItem } from '../../data/TaskData';
 import { fileUploadPrimordial } from '@/api/app.js';
 
-import AudioShow from '../../../../../../../components/course/common/AudioShow.vue';
-import VideoShow from '../../../../../../../components/course/common/VideoShow.vue';
+import AudioShow from '@/components/course/common/AudioShow.vue';
+import VideoShow from '@/components/course/common/VideoShow.vue';
 
 const props = defineProps({
   listName: {
@@ -125,10 +121,10 @@ let curTemplateData =
 let message_list = ref(curTemplateData.message_list);
 
 // 文本
-let text = ref('');
+let textMsg = ref('');
 function sendMessage() {
-  message_list.value.push(Object.assign({}, messageItem[0], { text: text.value }));
-  text.value = '';
+  message_list.value.push(Object.assign({}, messageItem[0], { text: textMsg.value }));
+  textMsg.value = '';
 }
 
 let { curRecord, selectRecord } = useRecordingPageData();