Ver código fonte

完成教师查看基本显示

dusenyao 2 anos atrás
pai
commit
4c66189d65
26 arquivos alterados com 25042 adições e 70 exclusões
  1. 3 3
      .vscode/launch.json
  2. 5 2
      .vscode/settings.json
  3. 4 1
      jsconfig.json
  4. 24201 1
      package-lock.json
  5. 5 5
      package.json
  6. 0 8
      src/common/data.js
  7. 2 0
      src/store/data.js
  8. 4 1
      src/store/getters.js
  9. 4 1
      src/views/login/index.vue
  10. 3 2
      src/views/main/components/TaskList.vue
  11. 134 0
      src/views/new_task_view/components/TaskExplain.vue
  12. 72 0
      src/views/new_task_view/components/data/TaskType.js
  13. 177 0
      src/views/new_task_view/components/layouts/LeftSidebar.vue
  14. 23 0
      src/views/new_task_view/components/layouts/RightSidebar.vue
  15. 7 0
      src/views/new_task_view/components/teacher/TaskItem.vue
  16. 9 0
      src/views/new_task_view/components/teacher/index.vue
  17. 29 0
      src/views/new_task_view/index.js
  18. 182 8
      src/views/new_task_view/index.vue
  19. 3 2
      src/views/teacher/create_course/step_table/CourseInfo.vue
  20. 5 6
      src/views/teacher/create_course/step_table/create_task/components/layouts/LeftSidebar.vue
  21. 2 2
      src/views/teacher/create_course/step_table/create_task/components/layouts/RightSidebar.vue
  22. 3 2
      src/views/teacher/create_course/step_table/create_task/components/layouts/TaskEditor.vue
  23. 136 0
      src/views/teacher/create_course/step_table/create_task/components/preview/TaskExplain.vue
  24. 4 2
      src/views/teacher/create_course/step_table/create_task/components/preview/index.vue
  25. 23 20
      src/views/teacher/create_course/step_table/create_task/index.js
  26. 2 4
      src/views/teacher/create_course/step_table/create_task/index.vue

+ 3 - 3
.vscode/launch.json

@@ -5,9 +5,9 @@
   "version": "0.2.0",
   "configurations": [
     {
+      "name": "vuejs: chrome",
       "type": "chrome",
       "request": "launch",
-      "name": "vuejs: chrome",
       "url": "http://localhost:7878",
       "webRoot": "${workspaceFolder}"
     },
@@ -16,8 +16,8 @@
       "type": "chrome",
       "request": "attach",
       "port": 9222,
-      "webRoot": "${workspaceFolder}",
-      "url": "http://localhost:7878"
+      "url": "http://localhost:7878",
+      "webRoot": "${workspaceFolder}"
     }
   ]
 }

+ 5 - 2
.vscode/settings.json

@@ -1,5 +1,5 @@
 {
-  "cSpell.words": ["AILP", "GCLS", "KHPJ", "NNPE", "XYZP"],
+  "cSpell.words": ["AILP", "csitem", "GCLS", "KHPJ", "NNPE", "XYZP"],
   "workbench.colorCustomizations": {
     "titleBar.activeBackground": "#42b883",
     "titleBar.activeForeground": "#15202b",
@@ -13,5 +13,8 @@
   "i18n-ally.keystyle": "nested",
   "i18n-ally.sourceLanguage": "ZH",
   "i18n-ally.displayLanguage": "ZH",
-  "svg.preview.background": "transparent"
+  "svg.preview.background": "transparent",
+  "path-intellisense.mappings": {
+    "@": "${workspaceFolder}/src"
+  }
 }

+ 4 - 1
jsconfig.json

@@ -5,5 +5,8 @@
       "@/*": ["src/*"]
     }
   },
-  "exclude": ["node_modules", "dist"]
+  "exclude": ["node_modules", "dist"],
+  "vueCompilerOptions": {
+    "target": 2.7
+  }
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 24201 - 1
package-lock.json


+ 5 - 5
package.json

@@ -23,7 +23,7 @@
     "element-ui": "^2.15.12",
     "gcls-book-question-ui": "file:../gcls-book-question-ui-0.1.0.tgz",
     "jquery": "^3.6.1",
-    "js-base64": "^3.7.2",
+    "js-base64": "^3.7.3",
     "js-cookie": "^3.0.1",
     "jsplumb": "^2.15.6",
     "md5": "^2.3.0",
@@ -49,7 +49,7 @@
     "@vue/cli-service": "~5.0.8",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/preload-webpack-plugin": "^2.0.0",
-    "@vue/test-utils": "^1.3.2",
+    "@vue/test-utils": "^1.3.3",
     "babel-jest": "^27.5.1",
     "babel-plugin-dynamic-import-node": "^2.3.3",
     "compression-webpack-plugin": "^6.1.1",
@@ -59,9 +59,9 @@
     "html-webpack-plugin": "^5.5.0",
     "postcss": "^8.4.19",
     "postcss-html": "^1.5.0",
-    "prettier": "2.7.1",
+    "prettier": "2.8.0",
     "sass": "^1.56.1",
-    "sass-loader": "^10.3.1",
+    "sass-loader": "^10.4.1",
     "script-ext-html-webpack-plugin": "^2.1.5",
     "stylelint": "^14.15.0",
     "stylelint-config-prettier": "^9.0.4",
@@ -72,7 +72,7 @@
     "stylelint-webpack-plugin": "^3.3.0",
     "svg-sprite-loader": "^6.0.11",
     "svgo": "^2.8.0",
-    "vue-loader": "^15.10.0",
+    "vue-loader": "^15.10.1",
     "vue-template-compiler": "^2.7.14",
     "vue-demi": "^0.13.11"
   },

+ 0 - 8
src/common/data.js

@@ -1,7 +1,3 @@
-import store from '@/store';
-
-const user_type = store.state.user.user_type;
-
 export const taskTimeList = [
   {
     constant: -1,
@@ -20,7 +16,3 @@ export const taskTimeList = [
     name: 'Key294'
   }
 ];
-
-export const userTypeList = ['TEACHER', 'STUDENT'];
-
-export const isTeacher = user_type === userTypeList[0]; // 用户类型是否教师

+ 2 - 0
src/store/data.js

@@ -1 +1,3 @@
 export let taskModeList = ['Simple', 'Multilayer'];
+
+export const userTypeList = ['TEACHER', 'STUDENT'];

+ 4 - 1
src/store/getters.js

@@ -1,8 +1,11 @@
+import { userTypeList } from './data';
+
 const getters = {
   language_type: (state) => state.user.language_type,
   showProgress: (state) => state.app.showProgress,
   percentage: (state) => state.app.percentage,
-  sys_type: (state) => state.app.config.sys_type
+  sys_type: (state) => state.app.config.sys_type,
+  isTeacher: (state) => state.user.user_type === userTypeList[0]
 };
 
 export default getters;

+ 4 - 1
src/views/login/index.vue

@@ -99,7 +99,10 @@ export default {
         user_type: '',
         is_password_md5: 'true',
         verification_code_image_id: '',
-        verification_code_image_text: ''
+        verification_code_image_text: '',
+        dynamic_verification_type: 'EMAIL',
+        phone_or_email: '',
+        dynamic_verification_code: '1234567a'
       },
       loginRules: {
         user_name: [{ trigger: 'blur', validator: validateUsername }],

+ 3 - 2
src/views/main/components/TaskList.vue

@@ -11,7 +11,9 @@
         <div class="task-item-top">
           <svg-icon icon-class="arrival" class-name="arrival" :style="getItemIconStyle(course_id)" />
           <span class="cs-item-name">
-            <span @click="$router.push(`/task_view/${cs_item_id}`)">{{ cs_item_name }}</span>
+            <span @click="$router.push(`/task_view/${cs_item_id}?task_time_type=${task_time_type}`)">
+              {{ cs_item_name }}
+            </span>
             <span
               v-if="is_live_task_group === 'true'"
               class="enter-live"
@@ -63,7 +65,6 @@ defineProps({
 
 const router = useRouter();
 let { taskLink } = useTaskLink(router);
-
 let { getItemIconStyle } = useTaskItem();
 
 let changeTab = inject('changeTab');

+ 134 - 0
src/views/new_task_view/components/TaskExplain.vue

@@ -0,0 +1,134 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="task-explain">
+    <div class="task-explain-top">
+      <div class="csitem-name">{{ taskData.cs_item_name }}</div>
+      <div class="title">{{ task_intro.title }}</div>
+    </div>
+    <div class="task-explain-main">
+      <div class="aliquot">
+        <div class="sub-title">班级</div>
+        <div class="sub-title">执教者</div>
+        <div class="sub-title">班型</div>
+      </div>
+      <div class="aliquot">
+        <span class="content">{{ task_intro.class_name }}</span>
+        <span class="content">{{ task_intro.teacher }}</span>
+        <span class="content">{{ task_intro.class_mode }}</span>
+      </div>
+      <div class="aliquot">
+        <div class="sub-title">课题</div>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.topic"></span>
+      </div>
+      <div class="aliquot">
+        <div class="sub-title">课时</div>
+        <div class="sub-title">节次</div>
+      </div>
+      <div class="aliquot">
+        <span class="content">{{ task_intro.cs_hour }}</span>
+        <span class="content">{{ task_intro.cs_item_count }}</span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学目标</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_objective"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学重点</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_focus"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学难点</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_difficulty"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学方法</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_method"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>课前准备</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.pre_class_prepare"></span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed, inject } from 'vue';
+
+let taskData = inject('taskData');
+
+let task_intro = computed(() => taskData.value.task_intro);
+</script>
+
+<style lang="scss" scoped>
+.task-explain {
+  width: 804px;
+  padding: 16px;
+  margin: 12px auto;
+  overflow: auto;
+  background: #fff;
+  border-radius: 8px;
+
+  &-top {
+    display: flex;
+    flex-direction: column;
+    row-gap: 16px;
+    align-items: center;
+
+    font: {
+      weight: bold;
+      size: 28px;
+    }
+  }
+
+  &-main {
+    margin-top: 8px;
+    border: 1px solid $border-color;
+
+    %sub-title,
+    :deep .sub-title {
+      padding: 8px;
+      font-weight: bold;
+      background-color: #ebebeb;
+      border-top: 1px solid #ebebeb;
+      border-bottom: 1px solid #ebebeb;
+    }
+
+    :deep .sub-title-two {
+      @extend %sub-title;
+
+      background-color: #e4efff;
+    }
+
+    .aliquot {
+      display: flex;
+
+      > .sub-title {
+        flex: 1;
+      }
+
+      > .content {
+        flex: 1;
+        min-height: 36px;
+        padding: 8px;
+
+        &:not(:last-child) {
+          border-right: 1px solid $border-color;
+        }
+      }
+    }
+  }
+}
+</style>

+ 72 - 0
src/views/new_task_view/components/data/TaskType.js

@@ -0,0 +1,72 @@
+import { ref, computed, provide } from 'vue';
+import { useRoute } from 'vue-router/composables';
+
+export const TASK_EXPLAIN = 'explain'; // 任务说明类型
+export const taskTypeArray = [
+  {
+    type: TASK_EXPLAIN,
+    name: '任务说明',
+    active_color: '#676767',
+    background_color: '#eeeeee'
+  },
+  {
+    type: 'pre',
+    name: '课前任务',
+    time_type: 0, // 任务时间类型
+    active_color: '#2a76e8', // 激活时左侧按钮颜色
+    background_color: '#eaf2ff', // 主背景色
+    button_background_color: '#dbe8fe', // 中央按钮背景色
+    button_border_color: '#cddffd', // 中央按钮边框色
+    color: '#2A76E8', // 中央按钮文字颜色
+    sidebarListName: 'pre_task_list' // 左侧边栏任务和任务、子任务列表对象类名
+  },
+  {
+    type: 'mid',
+    name: '课中任务',
+    time_type: 1,
+    active_color: '#df5c5c',
+    background_color: '#f5ebeb',
+    button_background_color: '#f4e0e0',
+    button_border_color: '#f3d5d5',
+    color: '#DF5C5C',
+    sidebarListName: 'mid_task_list'
+  },
+  {
+    type: 'after',
+    name: '课后任务',
+    time_type: 2,
+    active_color: '#59c057',
+    background_color: '#eaf3ea',
+    button_background_color: '#dfefdf',
+    button_border_color: '#d5ebd5',
+    color: '#59C057',
+    sidebarListName: 'after_task_list'
+  }
+];
+
+/**
+ * 任务类型
+ */
+export function useTaskType() {
+  const route = useRoute();
+  const task_time_type = Number(route.query.task_time_type);
+
+  const taskType = taskTypeArray.find(({ time_type }) => time_type === task_time_type).type;
+
+  let curTaskType = ref(taskType);
+  // 切换任务类型
+  function changeTaskType(type) {
+    curTaskType.value = type;
+  }
+
+  // 当前任务类型对象
+  let curTaskTypeObj = computed(() => taskTypeArray.find(({ type }) => type === curTaskType.value));
+  provide('curTaskTypeObj', curTaskTypeObj);
+
+  return {
+    curTaskType,
+    taskType,
+    curTaskTypeObj,
+    changeTaskType
+  };
+}

+ 177 - 0
src/views/new_task_view/components/layouts/LeftSidebar.vue

@@ -0,0 +1,177 @@
+<template>
+  <nav class="left-sidebar">
+    <template v-for="{ type, name, sidebarListName } in taskTypeArray">
+      <div
+        v-if="[TASK_EXPLAIN, taskType].includes(type)"
+        :key="type"
+        :class="[`${type}-task`, curTaskType === type ? 'active' : '']"
+        @click="changeTaskType(type)"
+      >
+        <span>{{ name }}</span>
+        <span v-if="type !== TASK_EXPLAIN">
+          {{ taskNumAndSubtaskNum(type) }}
+        </span>
+      </div>
+      <!-- 导航栏任务列表 -->
+      <div v-if="[TASK_EXPLAIN, taskType].includes(type)" :key="`${type}-task`" class="task-list">
+        <template v-for="({ name: taskName, child_task_list }, index) in taskData[sidebarListName]">
+          <div
+            :key="`sidebar-task-${index}`"
+            :class="['task-list-item', curSelectMark === `${type}-${index}` ? 'active' : '']"
+          >
+            <span class="task-index" @click="setSelectMark(type, index)">{{ index + 1 }}.</span>
+            <span class="task-name" @click="setSelectMark(type, index)">{{ taskName }}</span>
+          </div>
+          <div
+            v-for="({ name: subtaskName }, subtask_index) in child_task_list"
+            :key="`sidebar-subtask-${subtask_index}`"
+            :class="['task-list-subtask', curSelectMark === `${type}-${index}-${subtask_index}` ? 'active' : '']"
+          >
+            <span class="task-index" @click="setSelectMark(type, index, subtask_index)">
+              {{ `${index + 1}.${subtask_index + 1}` }}
+            </span>
+            <span class="task-name" @click="setSelectMark(type, index, subtask_index)">{{ subtaskName }}</span>
+          </div>
+        </template>
+      </div>
+    </template>
+  </nav>
+</template>
+
+<script>
+export default {
+  name: 'LeftSidebar'
+};
+</script>
+
+<script setup>
+import { ref, nextTick, inject } from 'vue';
+import { TASK_EXPLAIN, taskTypeArray } from '../data/TaskType';
+
+defineProps({
+  curTaskType: {
+    type: String,
+    required: true
+  },
+  changeTaskType: {
+    type: Function,
+    required: true
+  },
+  taskType: {
+    type: String,
+    required: true
+  }
+});
+
+/**
+ * 处理选中任务导航事件
+ */
+let curSelectMark = ref(''); // 当前选中任务标记
+function setSelectMark(type, index, subtask_index = -1) {
+  curSelectMark.value = `${type}-${index}${subtask_index >= 0 ? `-${subtask_index}` : ''}`;
+  nextTick(() => {
+    document.querySelector(`.${curSelectMark.value}`)?.scrollIntoView({ behavior: 'smooth' });
+  });
+}
+
+let curTaskTypeObj = inject('curTaskTypeObj');
+let taskData = inject('taskData');
+// 任务数量和子任务数量显示名
+function taskNumAndSubtaskNum(taskType) {
+  const sidebarList = taskData.value[taskTypeArray.find(({ type }) => type === taskType)['sidebarListName']];
+  if (!sidebarList) return;
+  return sidebarList.length > 0
+    ? `${sidebarList.length}/${sidebarList.reduce((pre, { child_task_list }) => pre + child_task_list.length, 0)}`
+    : 0;
+}
+</script>
+
+<style lang="scss" scoped>
+$basic-background-color: #f7f7f7;
+
+.left-sidebar {
+  width: 155px;
+  padding: 16px 0 16px 8px;
+  overflow: auto;
+  font-size: 14px;
+  background-color: $basic-background-color;
+  border-right: 1px solid $border-color;
+
+  .explain-task {
+    width: 138px;
+    height: 28px;
+    padding: 2px 16px;
+    margin-bottom: 14px;
+    font-weight: bold;
+    line-height: 22px;
+    text-align: center;
+    cursor: pointer;
+    background-color: #fff;
+    border: 1px solid $border-color;
+    border-radius: 20px;
+
+    &.active {
+      color: #fff;
+      background-color: v-bind('curTaskTypeObj.active_color');
+    }
+  }
+
+  .pre-task,
+  .mid-task,
+  .after-task {
+    display: flex;
+    justify-content: space-between;
+    width: 146px;
+    height: 28px;
+    padding: 2px 8px;
+    margin-top: 8px;
+    font-weight: bold;
+    line-height: 22px;
+    color: #fff;
+    text-transform: uppercase;
+    cursor: pointer;
+    background-color: #cfcfcf;
+    border: 1px solid $border-color;
+    border-radius: 4px 0 0 4px;
+
+    &.active {
+      background-color: v-bind('curTaskTypeObj.active_color');
+    }
+  }
+
+  .task-list {
+    margin: 6px 0 0 8px;
+    border-left: 1px solid #d9d9d9;
+
+    &-item,
+    %item {
+      display: flex;
+      column-gap: 8px;
+      padding: 3px 16px;
+
+      .task-index {
+        cursor: pointer;
+      }
+
+      .task-name {
+        word-break: break-all;
+        cursor: pointer;
+      }
+
+      &.active {
+        color: #0052d9;
+        box-shadow: -1px 0 #0052d9;
+      }
+    }
+
+    &-subtask {
+      @extend %item;
+
+      .task-index {
+        margin-left: 16px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 23 - 0
src/views/new_task_view/components/layouts/RightSidebar.vue

@@ -0,0 +1,23 @@
+<template>
+  <nav class="right-sidebar">
+    <ul>
+      <li></li>
+    </ul>
+  </nav>
+</template>
+
+<script>
+export default {
+  name: 'RightSidebar'
+};
+</script>
+
+<script setup></script>
+
+<style lang="scss" scoped>
+.right-sidebar {
+  width: 180px;
+  background-color: #f7f7f7;
+  border-left: 1px solid $border-color;
+}
+</style>

+ 7 - 0
src/views/new_task_view/components/teacher/TaskItem.vue

@@ -0,0 +1,7 @@
+<template>
+  <div></div>
+</template>
+
+<script setup></script>
+
+<style lang="scss" scoped></style>

+ 9 - 0
src/views/new_task_view/components/teacher/index.vue

@@ -0,0 +1,9 @@
+<template>
+  <div></div>
+</template>
+
+<script setup>
+import ShowFile from '@/common/show_file/index.vue';
+</script>
+
+<style lang="scss" scoped></style>

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

@@ -0,0 +1,29 @@
+import { ref, provide } from 'vue';
+import { useRoute } from 'vue-router/composables';
+
+import { GetCSItemTaskList } from '@/api/course';
+import store from '@/store';
+
+export function useTask() {
+  const route = useRoute();
+  const id = route.params.id;
+  const task_time_type = Number(route.query.task_time_type);
+  const isTeacher = store.getters.isTeacher;
+
+  // 得到课节任务列表
+  let taskData = ref({});
+  provide('taskData', taskData);
+  GetCSItemTaskList({
+    cs_item_id: id,
+    task_time_type,
+    data_use: isTeacher ? 'teacher_see_student_task' : 'student_do_task',
+    student_id: isTeacher ? '' : ''
+  }).then(({ status, ...data }) => {
+    if (status !== 1) return;
+    taskData.value = data;
+  });
+
+  return {
+    taskData
+  };
+}

+ 182 - 8
src/views/new_task_view/index.vue

@@ -1,17 +1,191 @@
 <template>
-  <div></div>
+  <div class="task-view">
+    <div class="task-view-top">
+      <div class="back" @click="$router.push('/main')">
+        <svg-icon icon-class="back-black" class-name="back-black" />返回
+      </div>
+      <div class="csitem-name">
+        <svg-icon icon-class="class" class-name="class" />
+        <span class="class-item-name nowrap-ellipsis">{{ taskData.cs_item_name }}</span>
+      </div>
+    </div>
+
+    <div class="title">
+      <div class="csitem-name">{{ taskData.cs_item_name }}</div>
+      <div class="title-button primary" @click="saveCSItem(cs_item_id)">
+        <svg-icon icon-class="preserve" /><span class="button-name">{{ isTeacher ? '保存' : '提交任务' }}</span>
+      </div>
+    </div>
+
+    <div class="task-view-wrap">
+      <LeftSidebar :cur-task-type="curTaskType" :task-type="taskType" :change-task-type="changeTaskType" />
+      <main ref="center" class="task-view-main" :style="centerStyle">
+        <span class="zoom-display" :style="{ right: isTeacher ? '216px' : '20px' }">
+          <svg-icon icon-class="minus" class-name="zoom-display-minus" @click="changeScale(false)" />
+          <span>{{ (scale * 100).toFixed(0) }}%</span>
+          <svg-icon icon-class="add" class-name="zoom-display-add" @click="changeScale(true)" />
+        </span>
+        <div class="task-view-main-container" :style="{ transform: `scale(${scale})` }">
+          <TaskExplain v-if="curTaskType === TASK_EXPLAIN" />
+          <TeacherView v-else-if="isTeacher" />
+          <StudentView v-else />
+        </div>
+      </main>
+      <RightSidebar v-if="isTeacher" />
+    </div>
+  </div>
 </template>
 
+<script>
+export default {
+  name: 'TaskView'
+};
+</script>
+
 <script setup>
-import { useRoute } from 'vue-router/composables';
+import { ref } from 'vue';
+import store from '@/store';
+import { useTask } from './index';
+import { useTaskType, TASK_EXPLAIN } from './components/data/TaskType';
+import { useScale, useWheel, scale } from '@/views/teacher/create_course/step_table/create_task/components/utils/wheel';
+import { useMouseEvent } from '@/views/teacher/create_course/step_table/create_task/components/utils/mouseEvent';
+
+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';
+
+const isTeacher = store.getters.isTeacher;
+let center = ref();
 
-import { GetCSItemTaskList } from '@/api/course';
-import { isTeacher } from '@/common/data';
+let { taskData } = useTask();
+let { curTaskType, taskType, curTaskTypeObj, changeTaskType } = useTaskType();
 
-const route = useRoute();
-const id = route.params.id;
+const { centerStyle } = useMouseEvent(center);
 
-GetCSItemTaskList().then(() => {});
+useWheel(center);
+const { changeScale } = useScale();
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+$basic-background-color: #f7f7f7;
+
+.task-view {
+  height: 100%;
+
+  &-top {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    height: 48px;
+    font-size: 14px;
+    background-color: #fff;
+    border-bottom: 1px solid $border-color;
+
+    .back {
+      width: 74px;
+      padding: 12px;
+      cursor: pointer;
+
+      .svg-icon.back-black {
+        margin-right: 6px;
+      }
+    }
+
+    .csitem-name {
+      display: flex;
+      column-gap: 8px;
+      align-items: center;
+      min-width: 40px;
+      max-width: 144px;
+      padding: 12px;
+      line-height: 24px;
+      color: #000;
+      background-color: $basic-background-color;
+      border-right: 1px solid $border-color;
+      border-bottom: 1px solid $border-color;
+      border-bottom-width: 0;
+      border-left: 1px solid $border-color;
+      box-shadow: 0 1px $basic-background-color;
+
+      .svg-icon.class {
+        width: 20px;
+        height: 20px;
+      }
+    }
+  }
+
+  .title {
+    display: flex;
+    align-items: center;
+    height: 40px;
+    padding: 0 8px;
+    border-bottom: 1px solid $border-color;
+
+    .csitem-name {
+      flex: 1;
+      text-align: center;
+    }
+
+    &-button {
+      display: flex;
+      column-gap: 6px;
+      align-items: center;
+      min-width: 69px;
+      height: 32px;
+      padding: 4px 8px;
+      font-size: 16px;
+      font-weight: bold;
+      line-height: 1.5;
+      white-space: nowrap;
+      cursor: pointer;
+      background-color: #fff;
+      border: 1px solid $border-color;
+      border-radius: 4px;
+
+      &.primary {
+        color: #fff;
+        background-color: #5498ff;
+      }
+    }
+  }
+
+  &-wrap {
+    display: flex;
+    height: calc(100% - 88px);
+    background-color: #fff;
+
+    .task-view-main {
+      flex: 1;
+      overflow: auto;
+      background-color: v-bind('curTaskTypeObj.background_color');
+
+      &-container {
+        padding: 24px;
+        transform-origin: 0 0;
+      }
+
+      .zoom-display {
+        position: fixed;
+        bottom: 16px;
+        padding: 8px;
+        line-height: 22px;
+        user-select: none;
+        background-color: #fff;
+        border-radius: 20px;
+
+        &-minus {
+          margin-right: 12px;
+          cursor: pointer;
+        }
+
+        &-add {
+          margin-left: 12px;
+          cursor: pointer;
+        }
+      }
+    }
+  }
+}
+</style>

+ 3 - 2
src/views/teacher/create_course/step_table/CourseInfo.vue

@@ -120,14 +120,15 @@
 </template>
 
 <script>
-import StepBar from '@/components/StepBar.vue';
-import SelectTeacher from '@/components/select/SelectTeacher.vue';
 import { fileUpload } from '@/api/app';
 import { CreateCourse, GetCourseInfo_ContainCSItem, UpdateCourse } from '@/api/course';
 import { twoDecimal } from '@/utils/validate';
 import { getMyOrgList } from '@/api/list';
 import { GetUserListByIDList } from '@/api/user';
 
+import StepBar from '@/components/StepBar.vue';
+import SelectTeacher from '@/components/select/SelectTeacher.vue';
+
 export default {
   name: 'CourseInfo',
   components: { StepBar, SelectTeacher },

+ 5 - 6
src/views/teacher/create_course/step_table/create_task/components/layouts/LeftSidebar.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="left-sidebar">
+  <nav class="left-sidebar">
     <template v-for="{ type, name, sidebarListName } in taskTypeArray">
       <div :key="type" :class="[`${type}-task`, curTaskType === type ? 'active' : '']" @click="changeTaskType(type)">
         <span>{{ name }}</span>
@@ -30,7 +30,7 @@
         </template>
       </div>
     </template>
-  </div>
+  </nav>
 </template>
 
 <script>
@@ -75,8 +75,7 @@ function setSelectMark(type, index, subtask_index = -1) {
 
 // 任务数量和子任务数量显示名
 function taskNumAndSubtaskNum(taskType) {
-  const sidebarList =
-    taskData.value[taskTypeArray[taskTypeArray.findIndex(({ type }) => type === taskType)]['sidebarListName']];
+  const sidebarList = taskData.value[taskTypeArray.find(({ type }) => type === taskType)['sidebarListName']];
   return sidebarList.length > 0
     ? `${sidebarList.length}/${sidebarList.reduce((pre, { child_task_list }) => pre + child_task_list.length, 0)}`
     : 0;
@@ -87,7 +86,7 @@ function taskNumAndSubtaskNum(taskType) {
 $basic-background-color: #f7f7f7;
 
 .left-sidebar {
-  width: 180px;
+  width: 154px;
   padding: 16px 0 16px 8px;
   overflow: auto;
   background-color: $basic-background-color;
@@ -116,7 +115,7 @@ $basic-background-color: #f7f7f7;
   .after-task {
     display: flex;
     justify-content: space-between;
-    width: 172px;
+    width: 146px;
     height: 28px;
     padding: 2px 8px;
     margin-top: 8px;

+ 2 - 2
src/views/teacher/create_course/step_table/create_task/components/layouts/RightSidebar.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="task-main-right">
+  <nav class="task-main-right">
     <div
       v-for="{ type, name, image, teaching_type } in taskClassify"
       :key="type"
@@ -15,7 +15,7 @@
         @dragend="onDragend"
       />
     </div>
-  </div>
+  </nav>
 </template>
 
 <script>

+ 3 - 2
src/views/teacher/create_course/step_table/create_task/components/layouts/TaskEditor.vue

@@ -3,7 +3,7 @@
   <div class="task-main">
     <LeftSidebar :change-task-type="changeTaskType" :cur-task-type="curTaskType" :cur-task-type-obj="curTaskTypeObj" />
 
-    <div ref="center" class="task-main-center" :style="centerStyle">
+    <main ref="center" class="task-main-center" :style="centerStyle">
       <span
         v-if="curPageType !== mainPageTypeList[0]"
         class="zoom-display"
@@ -18,7 +18,7 @@
           <component :is="curPageType" />
         </keep-alive>
       </div>
-    </div>
+    </main>
 
     <RightSidebar v-show="rightShow" />
 
@@ -119,6 +119,7 @@ $basic-background-color: #f7f7f7;
       bottom: 16px;
       padding: 8px;
       line-height: 22px;
+      user-select: none;
       background-color: #fff;
       border-radius: 20px;
 

+ 136 - 0
src/views/teacher/create_course/step_table/create_task/components/preview/TaskExplain.vue

@@ -0,0 +1,136 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="task-explain">
+    <div class="task-explain-top">
+      <div class="csitem-name">{{ curCSItemName }}</div>
+      <div class="title">{{ task_intro.title }}</div>
+    </div>
+    <div class="task-explain-main">
+      <div class="aliquot">
+        <div class="sub-title">班级</div>
+        <div class="sub-title">执教者</div>
+        <div class="sub-title">班型</div>
+      </div>
+      <div class="aliquot">
+        <span class="content">{{ task_intro.class_name }}</span>
+        <span class="content">{{ task_intro.teacher }}</span>
+        <span class="content">{{ task_intro.class_mode }}</span>
+      </div>
+      <div class="aliquot">
+        <div class="sub-title">课题</div>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.topic"></span>
+      </div>
+      <div class="aliquot">
+        <div class="sub-title">课时</div>
+        <div class="sub-title">节次</div>
+      </div>
+      <div class="aliquot">
+        <span class="content">{{ task_intro.cs_hour }}</span>
+        <span class="content">{{ task_intro.cs_item_count }}</span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学目标</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_objective"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学重点</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_focus"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学难点</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_difficulty"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>教学方法</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.teaching_method"></span>
+      </div>
+      <div class="sub-title-two">
+        <span>课前准备</span>
+      </div>
+      <div class="aliquot">
+        <span class="content" v-html="task_intro.pre_class_prepare"></span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed, inject } from 'vue';
+
+import { taskData } from '../data/TaskData';
+
+const curCSItemName = inject('curCSItemName');
+
+let task_intro = computed(() => taskData.value.task_intro);
+</script>
+
+<style lang="scss" scoped>
+.task-explain {
+  width: 804px;
+  padding: 16px;
+  margin: 12px auto;
+  overflow: auto;
+  background: #fff;
+  border-radius: 8px;
+
+  &-top {
+    display: flex;
+    flex-direction: column;
+    row-gap: 16px;
+    align-items: center;
+
+    font: {
+      weight: bold;
+      size: 28px;
+    }
+  }
+
+  &-main {
+    margin-top: 8px;
+    border: 1px solid $border-color;
+
+    %sub-title,
+    :deep .sub-title {
+      padding: 8px;
+      font-weight: bold;
+      background-color: #ebebeb;
+      border-top: 1px solid #ebebeb;
+      border-bottom: 1px solid #ebebeb;
+    }
+
+    :deep .sub-title-two {
+      @extend %sub-title;
+
+      background-color: #e4efff;
+    }
+
+    .aliquot {
+      display: flex;
+
+      > .sub-title {
+        flex: 1;
+      }
+
+      > .content {
+        flex: 1;
+        min-height: 36px;
+        padding: 8px;
+
+        &:not(:last-child) {
+          border-right: 1px solid $border-color;
+        }
+      }
+    }
+  }
+}
+</style>

+ 4 - 2
src/views/teacher/create_course/step_table/create_task/components/preview/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
-    <template v-if="isMindMapping">
+    <TaskExplain v-if="TASK_EXPLAIN === curTaskTypeObj.type" />
+    <template v-else-if="isMindMapping">
       <MindMapping :cur-task-data-list="curTaskDataList" />
     </template>
     <template v-else>
@@ -16,10 +17,11 @@
 
 <script setup>
 import { inject } from 'vue';
-import { isMindMapping } from '../data/TaskType';
+import { isMindMapping, TASK_EXPLAIN } from '../data/TaskType';
 
 import MindMapping from './mind_mapping/index.vue';
 import TaskPreview from './task_preview/index.vue';
+import TaskExplain from './TaskExplain.vue';
 
 const curTaskDataList = inject('curTaskDataList');
 const curTaskTypeObj = inject('curTaskTypeObj');

+ 23 - 20
src/views/teacher/create_course/step_table/create_task/index.js

@@ -20,25 +20,6 @@ export function useCSItem(content) {
   const id = route.params.id; // 课程 id
   provide('id', id);
 
-  let beginDate = ref('');
-  let endDate = ref('');
-  provide('CSItemDate', {
-    beginDate,
-    endDate
-  });
-  GetCourseInfo_ContainCSItem({ id }).then(({ begin_date, end_date }) => {
-    beginDate.value = begin_date;
-    endDate.value = end_date;
-  });
-
-  const query = route.query;
-  const is_template = 'is_template' in query ? query.is_template === 'true' : false;
-  const closeLink = is_template
-    ? '/create_course'
-    : `/create_course_step_table/select_book/${id}?is_template=${is_template}`;
-
-  let isEditorClassName = ref(false); // 是否编辑课节标题中
-
   let csItemList = ref(null); // 课节列表
   let curCSItemIndex = ref(0); // 当前课节索引
 
@@ -132,6 +113,7 @@ export function useCSItem(content) {
   );
 
   // 编辑课节名称
+  let isEditorClassName = ref(false); // 是否编辑课节标题中
   function editorClassName(isEditor) {
     isEditorClassName.value = isEditor;
     if (isEditor) {
@@ -183,7 +165,6 @@ export function useCSItem(content) {
   }
 
   return {
-    closeLink,
     isCreateClassSection,
     isEditorClassName,
     csItemList,
@@ -228,4 +209,26 @@ export function initData() {
     durationList.push({ name: `${i}小时`, value: i * 60 * 60 });
   }
   provide('durationList', durationList);
+
+  // 课节的开始结束时间
+  let beginDate = ref('');
+  let endDate = ref('');
+  GetCourseInfo_ContainCSItem({ id }).then(({ begin_date, end_date }) => {
+    beginDate.value = begin_date;
+    endDate.value = end_date;
+  });
+  provide('CSItemDate', {
+    beginDate,
+    endDate
+  });
+
+  const query = route.query;
+  const is_template = 'is_template' in query ? query.is_template === 'true' : false;
+  const closeLink = is_template
+    ? '/create_course'
+    : `/create_course_step_table/select_book/${id}?is_template=${is_template}`;
+
+  return {
+    closeLink
+  };
 }

+ 2 - 4
src/views/teacher/create_course/step_table/create_task/index.vue

@@ -69,9 +69,8 @@ import { curPageState, isMindMapping, changeCurPageState, pageStateList } from '
 import TaskEditor from './components/layouts/TaskEditor.vue';
 import CreateCSitem from './components/layouts/create_csitem/index.vue';
 
-const content = ref(); // refs
+const content = ref();
 const {
-  closeLink,
   isEditorClassName,
   isCreateClassSection,
   csItemList,
@@ -85,7 +84,7 @@ const {
   deleteCSItem
 } = useCSItem(content);
 
-initData();
+const { closeLink } = initData();
 
 let task = ref();
 </script>
@@ -195,7 +194,6 @@ $basic-background-color: #f7f7f7;
       align-items: center;
       padding-right: 8px;
 
-      %title-button,
       .title-button {
         display: flex;
         column-gap: 6px;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff