Browse Source

增加左侧边栏

dusenyao 2 years ago
parent
commit
51a6e447e7

+ 1 - 2
.eslintrc.js

@@ -302,8 +302,7 @@ module.exports = {
     strict: 2,
     strict: 2,
     'vars-on-top': 2,
     'vars-on-top': 2,
     'wrap-regex': 0,
     'wrap-regex': 0,
-    yoda: [2, 'never'],
-    'prefer-const': 1
+    yoda: [2, 'never']
   },
   },
 
 
   // jest 测试配置
   // jest 测试配置

+ 13 - 0
src/api/course.js

@@ -602,3 +602,16 @@ export function GetCourseBookInfo_Brief(data) {
     data
     data
   });
   });
 }
 }
+
+/**
+ * 得到课节信息
+ * @param { Object } data
+ */
+export function GetCSItemInfo(data) {
+  return request({
+    method: 'post',
+    url: process.env.VUE_APP_LearnWebSI,
+    params: getRequestParams('teaching-cs_item_manager-GetCSItemInfo'),
+    data
+  });
+}

+ 17 - 6
src/components/course/create_course/create_task/RichText.vue → src/components/common/RichText.vue

@@ -2,6 +2,12 @@
   <Editor v-bind="$attrs" class="rich-text" :init="init" v-on="$listeners" @input="handleInput" />
   <Editor v-bind="$attrs" class="rich-text" :init="init" v-on="$listeners" @input="handleInput" />
 </template>
 </template>
 
 
+<script>
+export default {
+  name: 'RichText'
+};
+</script>
+
 <script setup>
 <script setup>
 import { onMounted } from 'vue';
 import { onMounted } from 'vue';
 
 
@@ -26,10 +32,10 @@ import 'tinymce/plugins/lists';
 // import 'tinymce/plugins/preview';
 // import 'tinymce/plugins/preview';
 import 'tinymce/plugins/hr';
 import 'tinymce/plugins/hr';
 
 
-defineProps({
-  disabled: {
-    type: Boolean,
-    default: false
+const props = defineProps({
+  height: {
+    type: [Number, String],
+    default: 148
   }
   }
 });
 });
 
 
@@ -37,7 +43,7 @@ const init = {
   language_url: `/tinymce/langs/zh_CN.js`,
   language_url: `/tinymce/langs/zh_CN.js`,
   language: 'zh_CN',
   language: 'zh_CN',
   skin_url: '/tinymce/skins/ui/oxide',
   skin_url: '/tinymce/skins/ui/oxide',
-  height: 148,
+  height: props.height,
   width: '100%',
   width: '100%',
   plugins: 'link lists image code hr',
   plugins: 'link lists image code hr',
   toolbar:
   toolbar:
@@ -60,4 +66,9 @@ onMounted(() => {
 });
 });
 </script>
 </script>
 
 
-<style lang="scss" scoped></style>
+<style lang="scss">
+.tox.tox-tinymce {
+  border-width: 0;
+  border-radius: 0;
+}
+</style>

+ 164 - 0
src/components/course/create_course/create_task/LeftSidebar.vue

@@ -0,0 +1,164 @@
+<template>
+  <div class="left-sidebar">
+    <template v-for="{ type, name, sidebarListName } in taskTypeArray">
+      <div
+        :key="type"
+        :class="[`${type}-task`, curTaskType === type ? 'active' : '']"
+        @click="handleChangeTaskType(type)"
+      >
+        <span>{{ name }}</span>
+        <span v-if="type !== TASK_EXPLAIN">
+          {{ CSItemInfo ? taskNumAndSubtaskNum(type) : 0 }}
+        </span>
+      </div>
+      <div v-if="type !== TASK_EXPLAIN && CSItemInfo" :key="`${type}-task`" class="task-list">
+        <template v-for="({ id: taskId, name: taskName, child_task_list }, index) in CSItemInfo[sidebarListName]">
+          <div :key="taskId" :class="['task-list-item', curTaskId === taskId ? 'active' : '']">
+            <span class="task-index">{{ index + 1 }}.</span>
+            <span class="task-name" @click="setCurTaskId(taskId)">{{ taskName }}</span>
+          </div>
+          <div
+            v-for="({ id: subtaskId, name: subtaskName }, subtask_index) in child_task_list"
+            :key="subtaskId"
+            :class="['task-list-subtask', curTaskId === subtaskId ? 'active' : '']"
+          >
+            <span class="task-index">{{ `${index + 1}.${subtask_index + 1}` }}</span>
+            <span class="task-name" @click="setCurTaskId(subtaskId)">{{ subtaskName }}</span>
+          </div>
+        </template>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script setup>
+import { ref, inject } from 'vue';
+import { GetCSItemInfo } from '@/api/course';
+import { useTaskType } from './taskType.js';
+
+const id = inject('id');
+defineProps({
+  curTaskType: {
+    type: String,
+    required: true
+  },
+  handleChangeTaskType: {
+    type: Function,
+    required: true
+  },
+  curTaskTypeObj: {
+    type: Object,
+    required: true
+  }
+});
+
+let curTaskId = ref('');
+
+function setCurTaskId(id) {
+  curTaskId.value = id;
+}
+
+let CSItemInfo = ref(null);
+let { taskTypeArray, TASK_EXPLAIN } = useTaskType();
+
+// 任务数量和子任务数量显示名
+function taskNumAndSubtaskNum(type) {
+  if (type === taskTypeArray[1].type) {
+    return `${CSItemInfo.value.pre_task_count}/${CSItemInfo.value.pre_child_task_count}`;
+  }
+  if (type === taskTypeArray[2].type) {
+    return `${CSItemInfo.value.mid_task_count}/${CSItemInfo.value.mid_child_task_count}`;
+  }
+  if (type === taskTypeArray[3].type) {
+    return `${CSItemInfo.value.after_task_count}/${CSItemInfo.value.after_child_task_count}`;
+  }
+}
+
+GetCSItemInfo({
+  id
+}).then((info) => {
+  CSItemInfo.value = info;
+});
+</script>
+
+<style lang="scss" scoped>
+$basic-background-color: #f7f7f7;
+
+.left-sidebar {
+  width: 154px;
+  padding: 16px 0 16px 8px;
+  background-color: $basic-background-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-name {
+        word-break: break-all;
+        cursor: pointer;
+      }
+
+      &.active {
+        color: #0052d9;
+        box-shadow: -1px 0 #0052d9;
+      }
+    }
+
+    &-subtask {
+      @extend %item;
+
+      .task-index {
+        margin-left: 16px;
+      }
+    }
+  }
+}
+</style>

+ 36 - 82
src/components/course/create_course/create_task/TaskEditor.vue

@@ -1,16 +1,10 @@
 <template>
 <template>
   <div class="task-main">
   <div class="task-main">
-    <div class="task-main-left">
-      <div
-        v-for="{ type, name } in taskTypeArray"
-        :key="type"
-        :class="[`${type}-task`, curTaskType === type ? 'active' : '']"
-        @click="handleChangeTaskType(type)"
-      >
-        <span>{{ name }}</span>
-        <span v-if="type !== TASK_EXPLAIN">0</span>
-      </div>
-    </div>
+    <LeftSidebar
+      :handle-change-task-type="handleChangeTaskType"
+      :cur-task-type="curTaskType"
+      :cur-task-type-obj="curTaskTypeObj"
+    />
 
 
     <div ref="center" class="task-main-center" :style="{ cursor: mainCursorDisplayStyle }">
     <div ref="center" class="task-main-center" :style="{ cursor: mainCursorDisplayStyle }">
       <template v-if="!isTaskExplain">
       <template v-if="!isTaskExplain">
@@ -19,7 +13,12 @@
           <span>{{ (scale * 100).toFixed(0) }}%</span>
           <span>{{ (scale * 100).toFixed(0) }}%</span>
           <svg-icon icon-class="add" class-name="zoom-display-add" />
           <svg-icon icon-class="add" class-name="zoom-display-add" />
         </span>
         </span>
-        <div class="task-main-center-container" :style="{ transform: `scale(${scale})` }">
+        <div
+          class="task-main-center-container"
+          :style="{ transform: `scale(${scale})` }"
+          @dragover="dragover"
+          @drop="drop"
+        >
           <div class="create-task">
           <div class="create-task">
             <svg-icon icon-class="add" class-name="create-task-add" />
             <svg-icon icon-class="add" class-name="create-task-add" />
             <span>创建{{ curTaskTypeObj.name }}</span>
             <span>创建{{ curTaskTypeObj.name }}</span>
@@ -32,43 +31,46 @@
     </div>
     </div>
 
 
     <div v-show="!isTaskExplain" class="task-main-right">
     <div v-show="!isTaskExplain" class="task-main-right">
-      <div v-for="{ type, name, image } in taskClassify" :key="type" class="task-classify-item">
+      <div
+        v-for="{ type, name, image } in taskClassify"
+        :key="type"
+        :style="{ cursor: type !== MORE_NAME ? 'pointer' : 'default' }"
+        class="task-classify-item"
+      >
         <span class="task-classify-item-name">{{ name }}</span>
         <span class="task-classify-item-name">{{ name }}</span>
-        <img :src="image" :alt="name" />
+        <img
+          :src="image"
+          :alt="name"
+          :draggable="type !== MORE_NAME"
+          @dragstart="dragstart($event, type)"
+          @dragend="dragend"
+        />
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, onMounted, onUnmounted } from 'vue';
+import { ref } from 'vue';
 import { useTaskType, useTaskClassify } from './taskType.js';
 import { useTaskType, useTaskClassify } from './taskType.js';
-import TaskExplain from './TaskExplain.vue';
 import { mouseEvent } from './mouseEvent.js';
 import { mouseEvent } from './mouseEvent.js';
+import { handleWheel } from './wheel.js';
+import { handleDrag } from './drag.js';
 
 
-const { curTaskType, curTaskTypeObj, taskTypeArray, isTaskExplain, TASK_EXPLAIN, handleChangeTaskType } = useTaskType();
+import TaskExplain from './TaskExplain/index.vue';
+import LeftSidebar from './LeftSidebar.vue';
 
 
-const { taskClassify } = useTaskClassify();
+const center = ref(); // vue3 获取 refs 写法,需要同名,且传空
 
 
-const scale = ref(1); // 缩放比例
-const { mainCursorDisplayStyle, center } = mouseEvent();
+const { curTaskType, curTaskTypeObj, isTaskExplain, handleChangeTaskType } = useTaskType();
 
 
-// 处理 ctrl + 滚轮事件
-function handleCtrlAndWheel(e) {
-  if (e.ctrlKey) {
-    e.preventDefault();
-    scale.value = e.deltaY > 0 ? Math.max(0.5, scale.value - 0.1) : Math.min(2, scale.value + 0.1);
-  }
-}
+const { taskClassify, MORE_NAME } = useTaskClassify();
+
+const { mainCursorDisplayStyle } = mouseEvent(center);
 
 
-onMounted(() => {
-  // ctrl + 滚轮事件
-  center.value.addEventListener('wheel', handleCtrlAndWheel);
-});
+const { scale } = handleWheel(center);
 
 
-onUnmounted(() => {
-  center.value.removeEventListener('wheel', handleCtrlAndWheel);
-});
+const { dragstart, dragover, dragend, drop } = handleDrag();
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -79,54 +81,6 @@ $basic-background-color: #f7f7f7;
   min-height: calc(100% - 98px);
   min-height: calc(100% - 98px);
   letter-spacing: 0.01em;
   letter-spacing: 0.01em;
 
 
-  &-left {
-    width: 154px;
-    padding: 16px 0 16px 8px;
-    background-color: $basic-background-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');
-      }
-    }
-  }
-
   &-center {
   &-center {
     flex: 1;
     flex: 1;
     overflow: auto;
     overflow: auto;

+ 0 - 101
src/components/course/create_course/create_task/TaskExplain.vue

@@ -1,101 +0,0 @@
-<template>
-  <div class="task-explain">
-    <div class="task-title">任务标题</div>
-    <el-input placeholder="点击输入" />
-    <div class="task-explain-main">
-      <div class="sub-title t-center">学科</div>
-      <el-input type="textarea" placeholder="点击输入" resize="none" />
-      <div class="aliquot">
-        <div class="sub-title">班级</div>
-        <div class="sub-title">执教者</div>
-        <div class="sub-title">班型</div>
-      </div>
-      <div class="aliquot">
-        <el-input placeholder="点击输入" />
-        <el-input placeholder="点击输入" />
-        <el-input placeholder="点击输入" />
-      </div>
-      <div class="sub-title">课题</div>
-      <RichText v-model="richText2" />
-      <div class="aliquot">
-        <div class="sub-title">课时</div>
-        <div class="sub-title">节次</div>
-      </div>
-      <div class="aliquot">
-        <el-input placeholder="点击输入" />
-        <el-input placeholder="点击输入" />
-      </div>
-      <div class="sub-title-two">教学目标</div>
-      <RichText v-model="richText" />
-      <div class="sub-title-two">教学重点</div>
-      <RichText v-model="richText3" />
-      <div class="sub-title-two">教学难点</div>
-      <RichText v-model="richText4" />
-      <div class="sub-title-two">教学方法</div>
-      <RichText v-model="richText5" />
-      <div class="sub-title-two">课前准备</div>
-      <RichText v-model="richText6" :disabled="disabled" />
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref } from 'vue';
-import RichText from './RichText.vue';
-
-const disabled = ref(true);
-
-defineProps({});
-
-const richText = ref('');
-const richText2 = ref('');
-const richText3 = ref('');
-const richText4 = ref('');
-const richText5 = ref('');
-const richText6 = ref('');
-</script>
-
-<style lang="scss" scoped>
-.task-explain {
-  width: 804px;
-  padding: 16px;
-  margin: 36px 0 36px max(36px, calc((100vw - 154px - 160px - 804px - 48px) / 2));
-  overflow: auto;
-  line-height: 22px;
-  background: #fff;
-  border-radius: 8px;
-
-  .task-title {
-    margin-bottom: 8px;
-    font-weight: bold;
-    color: #1a1a1a;
-  }
-
-  &-main {
-    margin-top: 8px;
-    border: 1px solid $border-color;
-
-    .sub-title {
-      padding: 8px;
-      font-weight: bold;
-      background-color: #ebebeb;
-      border-bottom: 1px solid #ebebeb;
-    }
-
-    .aliquot {
-      display: flex;
-
-      > .sub-title {
-        flex: 1;
-      }
-    }
-
-    .sub-title-two {
-      padding: 8px;
-      font-weight: bold;
-      background-color: #e4efff;
-      border-bottom: 1px solid #ebebeb;
-    }
-  }
-}
-</style>

+ 56 - 0
src/components/course/create_course/create_task/TaskExplain/ExpandContract.vue

@@ -0,0 +1,56 @@
+<template>
+  <div>
+    <div ref="stretch" :class="['stretch', titleClass, isUnfoldState ? 'unfold' : 'contract']">
+      <span>{{ title }}</span>
+      <div class="stretch-click" @click="handleStretch">
+        {{ isUnfoldState ? '收起' : '展开' }}
+        <i :class="[isUnfoldState ? 'el-icon-arrow-down' : 'el-icon-arrow-up']"></i>
+      </div>
+    </div>
+    <slot></slot>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+
+const props = defineProps({
+  title: {
+    type: String,
+    required: true
+  },
+  titleClass: {
+    type: String,
+    required: true
+  },
+  height: {
+    type: Number,
+    required: true
+  }
+});
+
+const stretch = ref();
+const isUnfoldState = ref(true);
+
+function handleStretch() {
+  isUnfoldState.value = !isUnfoldState.value;
+
+  stretch.value.nextSibling.nextSibling.style.height = isUnfoldState.value ? `${props.height}px` : 0;
+}
+</script>
+
+<style lang="scss" scoped>
+.stretch {
+  display: flex;
+  justify-content: space-between;
+
+  &-click {
+    cursor: pointer;
+  }
+
+  &.contract {
+    background-color: #ebebeb !important;
+    border-bottom: 1px solid #d9d9d9 !important;
+  }
+}
+</style>

+ 112 - 0
src/components/course/create_course/create_task/TaskExplain/index.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="task-explain">
+    <div class="task-title">任务标题</div>
+    <el-input :placeholder="placeholder" :disabled="disabled" />
+    <div class="task-explain-main">
+      <div class="sub-title t-center">学科</div>
+      <el-input type="textarea" :placeholder="placeholder" resize="none" :disabled="disabled" />
+      <div class="aliquot">
+        <div class="sub-title">班级</div>
+        <div class="sub-title">执教者</div>
+        <div class="sub-title">班型</div>
+      </div>
+      <div class="aliquot">
+        <el-input :placeholder="placeholder" :disabled="disabled" />
+        <el-input :placeholder="placeholder" :disabled="disabled" />
+        <el-input :placeholder="placeholder" :disabled="disabled" />
+      </div>
+      <ExpandContract title-class="sub-title" title="课题" :height="richTextHeight">
+        <RichText v-model="richText2" :disabled="disabled" :height="richTextHeight" />
+      </ExpandContract>
+      <div class="aliquot">
+        <div class="sub-title">课时</div>
+        <div class="sub-title">节次</div>
+      </div>
+      <div class="aliquot">
+        <el-input :placeholder="placeholder" :disabled="disabled" />
+        <el-input :placeholder="placeholder" :disabled="disabled" />
+      </div>
+      <ExpandContract title-class="sub-title-two" title="教学目标" :height="richTextHeight">
+        <RichText v-model="richText" :disabled="disabled" :height="richTextHeight" />
+      </ExpandContract>
+      <ExpandContract title-class="sub-title-two" title="教学重点" :height="richTextHeight">
+        <RichText v-model="richText3" :disabled="disabled" :height="richTextHeight" />
+      </ExpandContract>
+      <ExpandContract title-class="sub-title-two" title="教学难点" :height="richTextHeight">
+        <RichText v-model="richText4" :disabled="disabled" :height="richTextHeight" />
+      </ExpandContract>
+      <ExpandContract title-class="sub-title-two" title="教学方法" :height="richTextHeight">
+        <RichText v-model="richText5" :disabled="disabled" :height="richTextHeight" />
+      </ExpandContract>
+      <ExpandContract title-class="sub-title-two" title="课前准备" :height="richTextHeight">
+        <RichText v-model="richText6" :disabled="disabled" :height="richTextHeight" />
+      </ExpandContract>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+
+import RichText from '@/components/common/RichText.vue';
+import ExpandContract from './ExpandContract.vue';
+
+const disabled = ref(false);
+const placeholder = computed(() => (disabled.value ? '' : '点击输入'));
+const richTextHeight = 148;
+
+defineProps({});
+
+const richText = ref('');
+const richText2 = ref('');
+const richText3 = ref('');
+const richText4 = ref('');
+const richText5 = ref('');
+const richText6 = ref('');
+</script>
+
+<style lang="scss" scoped>
+.task-explain {
+  width: 804px;
+  padding: 16px;
+  margin: 36px 0 36px max(36px, calc((100vw - 154px - 160px - 804px - 48px) / 2));
+  overflow: auto;
+  line-height: 22px;
+  background: #fff;
+  border-radius: 8px;
+
+  .task-title {
+    margin-bottom: 8px;
+    font-weight: bold;
+    color: #1a1a1a;
+  }
+
+  &-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;
+      }
+    }
+  }
+}
+</style>

+ 28 - 0
src/components/course/create_course/create_task/drag.js

@@ -0,0 +1,28 @@
+/**
+ * 处理拖拽事件
+ */
+export function handleDrag() {
+  function dragstart(e, type) {
+    e.dataTransfer.setData('type', type); // 设置拖拽数据
+    e.target.style.opacity = 0.5; // 使其半透明
+  }
+
+  function dragover(e) {
+    e.preventDefault();
+  }
+
+  function dragend(e) {
+    e.target.style.opacity = ''; // 恢复透明度
+  }
+
+  function drop(e) {
+    e.dataTransfer.getData('type'); // 获取数据
+  }
+
+  return {
+    dragstart,
+    dragover,
+    dragend,
+    drop
+  };
+}

+ 23 - 10
src/components/course/create_course/create_task/mouseEvent.js

@@ -1,26 +1,37 @@
 // 鼠标事件
 // 鼠标事件
 import { onMounted, ref } from 'vue';
 import { onMounted, ref } from 'vue';
 
 
-export function mouseEvent() {
-  const center = ref(); // vue3 获取 refs 写法,需要同名,且传空
+/**
+ * 处理鼠标点击事件
+ * @param {Element} center
+ */
+export function mouseEvent(center) {
   const isMouseLeftPress = ref(false); // 鼠标左键是否按下
   const isMouseLeftPress = ref(false); // 鼠标左键是否按下
   const mouseOnMain = ref(false); // 鼠标是否在主要位置
   const mouseOnMain = ref(false); // 鼠标是否在主要位置
 
 
   const mainCursorDisplayStyle = ref('default'); // 鼠标指针样式
   const mainCursorDisplayStyle = ref('default'); // 鼠标指针样式
 
 
+  const preOffsetX = ref(0);
+  const preOffsetY = ref(0);
+
   // 按下空格时与鼠标左键时,移动鼠标移动主内容位置
   // 按下空格时与鼠标左键时,移动鼠标移动主内容位置
   function moveMainPosition() {
   function moveMainPosition() {
     const timer = null;
     const timer = null;
     let startTime = Date.now();
     let startTime = Date.now();
-    const duration = 100;
+    const duration = 17;
 
 
-    return (e) => {
+    return ({ offsetX, offsetY }) => {
       const curTime = Date.now();
       const curTime = Date.now();
       const remaining = curTime - startTime;
       const remaining = curTime - startTime;
       clearTimeout(timer);
       clearTimeout(timer);
       if (remaining > duration) {
       if (remaining > duration) {
         if (isMouseLeftPress.value) {
         if (isMouseLeftPress.value) {
-          console.log(e.offsetX, e.offsetY);
+          const diffX = offsetX - preOffsetX.value;
+          const diffY = offsetY - preOffsetY.value;
+          preOffsetX.value = offsetX;
+          preOffsetY.value = offsetY;
+          center.value.scrollLeft += diffX;
+          center.value.scrollTop += diffY;
         }
         }
         startTime = Date.now();
         startTime = Date.now();
       }
       }
@@ -28,9 +39,11 @@ export function mouseEvent() {
   }
   }
 
 
   // 鼠标左键按下/放开事件
   // 鼠标左键按下/放开事件
-  function centerMousedownLeft({ button }) {
+  function centerMousedownLeft({ button, offsetX, offsetY }) {
     if (button === 0) {
     if (button === 0) {
       isMouseLeftPress.value = true;
       isMouseLeftPress.value = true;
+      preOffsetX.value = offsetX;
+      preOffsetY.value = offsetY;
     }
     }
   }
   }
   function centerMouseupLeft({ button }) {
   function centerMouseupLeft({ button }) {
@@ -42,14 +55,14 @@ export function mouseEvent() {
   // 按下空格时添加鼠标事件
   // 按下空格时添加鼠标事件
   const throttleFn = moveMainPosition(); // 节流防抖函数需要这样处理才能被 removeEventListener 移除掉
   const throttleFn = moveMainPosition(); // 节流防抖函数需要这样处理才能被 removeEventListener 移除掉
   function addHandleMouseEvent() {
   function addHandleMouseEvent() {
-    center.value.addEventListener('mousedown', centerMousedownLeft);
-    center.value.addEventListener('mouseup', centerMouseupLeft);
+    document.addEventListener('mousedown', centerMousedownLeft);
+    document.addEventListener('mouseup', centerMouseupLeft);
     center.value.addEventListener('mousemove', throttleFn);
     center.value.addEventListener('mousemove', throttleFn);
   }
   }
   // 放开空格时移除鼠标事件
   // 放开空格时移除鼠标事件
   function removeHandleMouseEvent() {
   function removeHandleMouseEvent() {
-    center.value.removeEventListener('mousedown', centerMousedownLeft);
-    center.value.removeEventListener('mouseup', centerMouseupLeft);
+    document.removeEventListener('mousedown', centerMousedownLeft);
+    document.removeEventListener('mouseup', centerMouseupLeft);
     center.value.removeEventListener('mousemove', throttleFn);
     center.value.removeEventListener('mousemove', throttleFn);
   }
   }
 
 

+ 21 - 13
src/components/course/create_course/create_task/taskType.js

@@ -1,6 +1,8 @@
 import { computed, ref } from 'vue';
 import { computed, ref } from 'vue';
 
 
-// 任务类型相关
+/**
+ * 任务类型相关
+ */
 export function useTaskType() {
 export function useTaskType() {
   const TASK_EXPLAIN = 'explain';
   const TASK_EXPLAIN = 'explain';
 
 
@@ -18,7 +20,8 @@ export function useTaskType() {
       background_color: '#eaf2ff', // 主背景色
       background_color: '#eaf2ff', // 主背景色
       button_background_color: '#dbe8fe', // 中央按钮背景色
       button_background_color: '#dbe8fe', // 中央按钮背景色
       button_border_color: '#cddffd', // 中央按钮边框色
       button_border_color: '#cddffd', // 中央按钮边框色
-      color: '#2A76E8' // 中央按钮文字颜色
+      color: '#2A76E8', // 中央按钮文字颜色
+      sidebarListName: 'pre_task_list' // 左侧边栏任务和子任务列表对象类名
     },
     },
     {
     {
       type: 'mid',
       type: 'mid',
@@ -27,7 +30,8 @@ export function useTaskType() {
       background_color: '#f5ebeb',
       background_color: '#f5ebeb',
       button_background_color: '#f4e0e0',
       button_background_color: '#f4e0e0',
       button_border_color: '#f3d5d5',
       button_border_color: '#f3d5d5',
-      color: '#DF5C5C'
+      color: '#DF5C5C',
+      sidebarListName: 'mid_task_list'
     },
     },
     {
     {
       type: 'after',
       type: 'after',
@@ -36,21 +40,21 @@ export function useTaskType() {
       background_color: '#eaf3ea',
       background_color: '#eaf3ea',
       button_background_color: '#dfefdf',
       button_background_color: '#dfefdf',
       button_border_color: '#d5ebd5',
       button_border_color: '#d5ebd5',
-      color: '#59C057'
+      color: '#59C057',
+      sidebarListName: 'after_task_list'
     }
     }
   ];
   ];
 
 
-  const isTaskExplain = ref(false); // 当前是否任务说明
-  const _selectTaskType = ref('pre'); // 选中的任务类型
+  let isTaskExplain = ref(false); // 当前是否任务说明
+  let _selectTaskType = ref('pre'); // 选中的任务类型
 
 
-  const curTaskType = computed(() => {
+  let curTaskType = computed(() => {
     if (isTaskExplain.value) return TASK_EXPLAIN;
     if (isTaskExplain.value) return TASK_EXPLAIN;
     return _selectTaskType.value;
     return _selectTaskType.value;
-  });
-  // 当前任务类型
+  }); // 当前任务类型
 
 
   // 当前任务类型对象
   // 当前任务类型对象
-  const curTaskTypeObj = computed(() => taskTypeArray.find(({ type }) => type === curTaskType.value));
+  let curTaskTypeObj = computed(() => taskTypeArray.find(({ type }) => type === curTaskType.value));
 
 
   // 切换任务类型
   // 切换任务类型
   function handleChangeTaskType(type) {
   function handleChangeTaskType(type) {
@@ -65,8 +69,12 @@ export function useTaskType() {
   return { curTaskType, curTaskTypeObj, taskTypeArray, isTaskExplain, TASK_EXPLAIN, handleChangeTaskType };
   return { curTaskType, curTaskTypeObj, taskTypeArray, isTaskExplain, TASK_EXPLAIN, handleChangeTaskType };
 }
 }
 
 
-// 任务分类
+/**
+ * 任务分类
+ */
 export function useTaskClassify() {
 export function useTaskClassify() {
+  const MORE_NAME = 'more';
+
   const taskClassify = [
   const taskClassify = [
     {
     {
       type: 'basic',
       type: 'basic',
@@ -94,11 +102,11 @@ export function useTaskClassify() {
       image: require('@/assets/course/create_task/live.png')
       image: require('@/assets/course/create_task/live.png')
     },
     },
     {
     {
-      type: 'more',
+      type: MORE_NAME,
       name: '更多功能正在赶来...',
       name: '更多功能正在赶来...',
       image: require('@/assets/course/create_task/more.png')
       image: require('@/assets/course/create_task/more.png')
     }
     }
   ];
   ];
 
 
-  return { taskClassify };
+  return { taskClassify, MORE_NAME };
 }
 }

+ 28 - 0
src/components/course/create_course/create_task/wheel.js

@@ -0,0 +1,28 @@
+import { ref, onMounted, onUnmounted } from 'vue';
+
+/**
+ * 处理滚轮事件
+ * @param {Element} center
+ */
+export function handleWheel(center) {
+  const scale = ref(1); // 缩放比例
+
+  // 处理 ctrl + 滚轮事件
+  function handleCtrlAndWheel(e) {
+    if (e.ctrlKey) {
+      e.preventDefault();
+      scale.value = e.deltaY > 0 ? Math.max(0.5, scale.value - 0.1) : Math.min(2, scale.value + 0.1);
+    }
+  }
+
+  onMounted(() => {
+    // ctrl + 滚轮事件
+    center.value.addEventListener('wheel', handleCtrlAndWheel);
+  });
+
+  onUnmounted(() => {
+    center.value.removeEventListener('wheel', handleCtrlAndWheel);
+  });
+
+  return { scale };
+}

+ 5 - 0
src/views/teacher/create_course/step_table/create_task/index.vue

@@ -45,6 +45,11 @@ export default {
     TaskEditor,
     TaskEditor,
     CreateClassSection
     CreateClassSection
   },
   },
+  provide() {
+    return {
+      id: this.$route.params.id
+    };
+  },
   data() {
   data() {
     const query = this.$route.query;
     const query = this.$route.query;
     const is_template = 'is_template' in query ? query.is_template === 'true' : false;
     const is_template = 'is_template' in query ? query.is_template === 'true' : false;