Przeglądaj źródła

feat: 课程大纲管理

zhangjie 1 rok temu
rodzic
commit
1343853db0

+ 33 - 0
src/assets/styles/common-comp.scss

@@ -480,3 +480,36 @@ $--cc-labels-pre: cc-labels;
     }
   }
 }
+// org-select
+.org-select {
+  width: 220px;
+}
+.org-popover {
+  &.el-popover {
+    width: 400px;
+    padding: 0;
+  }
+
+  &-tree {
+    max-height: 300px;
+    overflow: auto;
+    padding: 5px 0;
+  }
+
+  .custom-tree-node {
+    .el-icon-check {
+      display: none;
+    }
+
+    &.is-select {
+      color: #3a5ae5;
+      .el-icon-check {
+        display: block;
+      }
+    }
+  }
+  .el-tree-node__content {
+    height: auto;
+    min-height: 26px;
+  }
+}

+ 0 - 35
src/components/base/OrgSelect.vue

@@ -272,38 +272,3 @@ export default {
   },
 };
 </script>
-
-<style lang="scss">
-.org-select {
-  width: 220px;
-}
-.org-popover {
-  &.el-popover {
-    width: 400px;
-    padding: 0;
-  }
-
-  &-tree {
-    max-height: 300px;
-    overflow: auto;
-    padding: 5px 0;
-  }
-
-  .custom-tree-node {
-    .el-icon-check {
-      display: none;
-    }
-
-    &.is-select {
-      color: #3a5ae5;
-      .el-icon-check {
-        display: block;
-      }
-    }
-  }
-  .el-tree-node__content {
-    height: auto;
-    min-height: 26px;
-  }
-}
-</style>

+ 23 - 15
src/modules/target/api.js

@@ -62,7 +62,7 @@ export const trainingPlanCourseListPage = (datas) => {
   return $postParam("/api/admin/obe/culture/program/course/list", datas);
 };
 export const deleteRrainingPlanCourse = (id) => {
-  return $post("/api/admin/obe/culture/program/course/remove", { id });
+  return $postParam("/api/admin/obe/culture/program/course/remove", { id });
 };
 export const sortRrainingPlanCourse = (datas) => {
   return $post("/api/admin/obe/culture/program/course/sort", datas);
@@ -99,24 +99,32 @@ export const courseOutlineTargetListPage = (datas) => {
   return $postParam("/api/admin/obe/course_target/list", datas);
 };
 export const deleteCourseOutlineTarget = (id) => {
-  return $postParam("/api/admin/obe/culture/program/target/remove", { id });
+  return $postParam("/api/admin/obe/course_target/remove", { id });
 };
 export const updateCourseOutlineTarget = (datas) => {
-  return $post("/api/admin/obe/culture/program/target/save", datas);
+  return $post("/api/admin/obe/course_target/save", datas);
 };
 export const updateCourseOutlineTargetPredict = (datas) => {
-  return $post("/api/admin/basic/professional/save", datas);
+  return $post("/api/admin/obe/course_target/setting_expect_value", datas);
 };
-// 课程大纲管理-知识点 ------------------->
-export const courseOutlineKnowledgeListPage = (datas) => {
-  return $postParam("/api/admin/obe/culture/program/course/list", datas);
+export const courseOutlineTargetKnowledgeList = (datas) => {
+  return $post("/api/admin/obe/course_target/dimension_tree", datas);
 };
-export const deleteCourseOutlineKnowledge = (id) => {
-  return $post("/api/admin/obe/culture/program/course/remove", { id });
-};
-export const sortCourseOutlineKnowledge = (datas) => {
-  return $post("/api/admin/obe/culture/program/course/sort", datas);
-};
-export const updateCourseOutlineKnowledge = (datas) => {
-  return $post("/api/admin/basic/professional/save", datas);
+export const courseOutlineTargetRequirementKnowledge = (datas) => {
+  return $post("/api/admin/obe/course_target/requirement_dimension", datas);
 };
+
+// 不要了
+// 课程大纲管理-知识点 ------------------->
+// export const courseOutlineKnowledgeListPage = (datas) => {
+//   return $postParam("/api/admin/obe/culture/program/course/list", datas);
+// };
+// export const deleteCourseOutlineKnowledge = (id) => {
+//   return $postParam("/api/admin/obe/culture/program/course/remove", { id });
+// };
+// export const sortCourseOutlineKnowledge = (datas) => {
+//   return $post("/api/admin/obe/culture/program/course/sort", datas);
+// };
+// export const updateCourseOutlineKnowledge = (datas) => {
+//   return $post("/api/admin/basic/professional/save", datas);
+// };

+ 1 - 1
src/modules/target/components/course-outline/CourseOutlineKnowledge.vue

@@ -97,7 +97,7 @@ export default {
   methods: {
     async getList() {
       const data = await courseOutlineKnowledgeListPage({
-        cultureProgramId: this.rowData.id,
+        obeCourseOutlineId: this.rowData.id,
       });
       this.dataList = data || [];
       this.mainOptions = this.dataList.map((item) => {

+ 13 - 12
src/modules/target/components/course-outline/CourseOutlineTarget.vue

@@ -19,18 +19,21 @@
           width="70"
           :index="indexMethod"
         ></el-table-column>
-        <el-table-column prop="name" label="课程目标" width="160">
+        <el-table-column prop="targetName" label="课程目标" width="160">
         </el-table-column>
-        <el-table-column prop="name" label="预期值" width="160">
+        <el-table-column prop="expectValue" label="预期值" width="160">
         </el-table-column>
-        <el-table-column prop="detail" label="内容" min-width="300">
+        <el-table-column prop="degreeRequirement" label="内容" min-width="300">
         </el-table-column>
-        <el-table-column prop="dimensionList" label="指标点">
-          <template slot-scope="scope">
+        <el-table-column
+          prop="obeCultureProgramRequirementContent"
+          label="指标点"
+        >
+          <!-- <template slot-scope="scope">
             <p v-for="item in scope.row.dimensionList" :key="item.id">
               {{ item.name }}({{ item.code }})
             </p>
-          </template>
+          </template> -->
         </el-table-column>
         <el-table-column
           class-name="action-column"
@@ -65,8 +68,7 @@
     <!-- ModifyCourseOutlineTargetPredict -->
     <modify-course-outline-target-predict
       ref="ModifyCourseOutlineTargetPredict"
-      :instance="curRow"
-      @modified="getList"
+      :rowData="rowData"
     ></modify-course-outline-target-predict>
   </div>
 </template>
@@ -105,17 +107,16 @@ export default {
   methods: {
     async getList() {
       const datas = {
-        cultureProgramId: this.rowData.id,
+        obeCourseOutlineId: this.rowData.id,
       };
       const data = await courseOutlineTargetListPage(datas);
       this.dataList = data || [];
     },
     toAdd() {
-      this.curRow = { cultureProgramId: this.rowData.id };
+      this.curRow = { obeCourseOutlineId: this.rowData.id };
       this.$refs.ModifyCourseOutlineTarget.open();
     },
     toPredict() {
-      this.curRow = { cultureProgramId: this.rowData.id };
       this.$refs.ModifyCourseOutlineTargetPredict.open();
     },
     toEdit(row) {
@@ -124,7 +125,7 @@ export default {
     },
     async toDelete(row) {
       const confirm = await this.$confirm(
-        `确定要删除培养目标【${row.name}】吗?`,
+        `确定要删除课程目标【${row.targetName}】吗?`,
         "提示",
         {
           type: "warning",

+ 6 - 6
src/modules/target/components/course-outline/DetailCourseOutline.vue

@@ -31,14 +31,14 @@
 <script>
 import CourseOutlineBase from "./CourseOutlineBase.vue";
 import CourseOutlineTarget from "./CourseOutlineTarget.vue";
-import CourseOutlineKnowledge from "./CourseOutlineKnowledge.vue";
+// import CourseOutlineKnowledge from "./CourseOutlineKnowledge.vue";
 
 export default {
   name: "detail-course-outline",
   components: {
     CourseOutlineBase,
     CourseOutlineTarget,
-    CourseOutlineKnowledge,
+    // CourseOutlineKnowledge,
   },
   props: {
     rowData: {
@@ -61,10 +61,10 @@ export default {
           name: "培养目标",
           val: "CourseOutlineTarget",
         },
-        {
-          name: "知识点",
-          val: "CourseOutlineKnowledge",
-        },
+        // {
+        //   name: "知识点",
+        //   val: "CourseOutlineKnowledge",
+        // },
       ],
     };
   },

+ 47 - 44
src/modules/target/components/course-outline/ModifyCourseOutlineTarget.vue

@@ -25,7 +25,15 @@
             clearable
           ></el-input>
         </el-form-item>
-        <el-form-item prop="dimensionIdList" label="毕业要求指标点:">
+        <el-form-item
+          prop="obeCultureProgramRequirementId"
+          label="毕业要求指标点:"
+        >
+          <requirement-select
+            v-model="modalForm.obeCultureProgramRequirementId"
+            @change="requirementChange"
+          ></requirement-select>
+
           <el-button
             type="text"
             class="btn-act-primary"
@@ -33,34 +41,6 @@
             @click="toSelectDimension"
             >选择知识点</el-button
           >
-          <el-table
-            v-if="dimensionList.length"
-            :data="dimensionList"
-            :show-header="false"
-            border
-          >
-            <el-table-column prop="name" label="知识点">
-              <template slot-scope="scope">
-                {{ scope.row.name }}({{ scope.row.code }})
-              </template>
-            </el-table-column>
-            <el-table-column
-              class-name="action-column"
-              label="操作"
-              width="40"
-              align="center"
-            >
-              <template slot-scope="scope">
-                <el-button
-                  type="text"
-                  class="btn-danger"
-                  icon="el-icon-error"
-                  @click="toDeleteDimension(scope.$index)"
-                >
-                </el-button>
-              </template>
-            </el-table-column>
-          </el-table>
         </el-form-item>
         <el-form-item prop="degreeRequirement" label="目标分解详情:">
           <el-input
@@ -85,7 +65,7 @@
     <!-- SelectDimensionDialog -->
     <select-dimension-dialog
       ref="SelectDimensionDialog"
-      :course="{ teachCourseId: instance.teachCourseId }"
+      :param="{ obeCourseOutlineId: instance.obeCourseOutlineId }"
       :selected-data="modalForm.dimensionIdList"
       :disabled-data="instance.disabledDimensionIds"
       @confirm="dimensionSelected"
@@ -95,20 +75,25 @@
 </template>
 
 <script>
-import { updateCourseOutlineTarget } from "../../api";
-import SelectDimensionDialog from "../../../base/components/course-simple/SelectDimensionDialog.vue";
+import {
+  updateCourseOutlineTarget,
+  courseOutlineTargetRequirementKnowledge,
+} from "../../api";
+import RequirementSelect from "./RequirementSelect.vue";
+import SelectDimensionDialog from "../../SelectDimensionDialog.vue";
 
 const initModalForm = {
   id: null,
-  teachCourseId: "",
+  obeCourseOutlineId: "",
   targetName: "",
   degreeRequirement: "",
+  obeCultureProgramRequirementId: "",
   dimensionIdList: [],
 };
 
 export default {
   name: "modify-course-outline-target",
-  components: { SelectDimensionDialog },
+  components: { SelectDimensionDialog, RequirementSelect },
   props: {
     instance: {
       type: Object,
@@ -130,6 +115,7 @@ export default {
       modalIsShow: false,
       isSubmit: false,
       modalForm: { ...initModalForm },
+      requirementList: [],
       dimensionList: [],
       rules: {
         targetName: [
@@ -144,13 +130,18 @@ export default {
             trigger: "change",
           },
         ],
-        dimensionIdList: [
+        obeCultureProgramRequirementId: [
           {
             validator: (rule, value, callback) => {
-              if (value && value.length) {
-                return callback();
+              if (!value) {
+                return callback(new Error("请选择毕业要求指标点"));
               }
-              return callback(new Error("请选择毕业要求指标点"));
+
+              if (this.modalForm.dimensionIdList.length) {
+                return callback(new Error("请选择知识点"));
+              }
+
+              return callback();
             },
           },
         ],
@@ -183,6 +174,18 @@ export default {
       this.modalIsShow = false;
       this.$emit("enforce-close");
     },
+    async requirementChange() {
+      this.modalForm.dimensionIdList = [];
+      if (!this.modalForm.obeCultureProgramRequirementId) return;
+      const res = await courseOutlineTargetRequirementKnowledge({
+        obeCourseOutlineId: this.instance.obeCourseOutlineId,
+        obeCultureProgramRequirementId:
+          this.modalForm.obeCultureProgramRequirementId,
+      });
+
+      this.modalForm.dimensionIdList = (res || []).map((item) => item.id);
+      this.$refs.modalFormComp.validateField("obeCultureProgramRequirementId");
+    },
     cancel() {
       this.modalIsShow = false;
     },
@@ -190,6 +193,11 @@ export default {
       this.modalIsShow = true;
     },
     toSelectDimension() {
+      if (!this.modalForm.obeCultureProgramRequirementId) {
+        this.$message.error("请选择毕业要求指标点");
+        return;
+      }
+
       this.$refs.SelectDimensionDialog.open();
     },
     updateDimensionIds() {
@@ -200,12 +208,7 @@ export default {
     dimensionSelected(dimensions) {
       this.dimensionList = [...dimensions];
       this.updateDimensionIds();
-      this.$refs.modalFormComp.validateField("dimensionIdList");
-    },
-    toDeleteDimension(index) {
-      this.dimensionList.splice(index, 1);
-      this.updateDimensionIds();
-      this.$refs.modalFormComp.validateField("dimensionIdList");
+      this.$refs.modalFormComp.validateField("obeCultureProgramRequirementId");
     },
     async submit() {
       const valid = await this.$refs.modalFormComp.validate().catch(() => {});

+ 11 - 13
src/modules/target/components/course-outline/ModifyCourseOutlineTargetPredict.vue

@@ -9,15 +9,10 @@
     append-to-body
     @open="visibleChange"
   >
-    <el-form
-      ref="modalFormComp"
-      :model="modalForm"
-      :key="modalForm.id"
-      :rules="rules"
-    >
-      <el-form-item prop="name" label="预期值:">
+    <el-form ref="modalFormComp" :model="modalForm" :rules="rules">
+      <el-form-item prop="expectValue" label="预期值:">
         <el-input-number
-          v-model="modalForm.name"
+          v-model="modalForm.expectValue"
           :min="0.01"
           :max="1"
           :step="0.01"
@@ -39,14 +34,14 @@
 import { updateCourseOutlineTargetPredict } from "../../api";
 
 const initModalForm = {
-  id: null,
-  name: "",
+  obeCourseOutlineId: null,
+  expectValue: "",
 };
 
 export default {
   name: "modify-training-plan-requirement-predict",
   props: {
-    instance: {
+    rowData: {
       type: Object,
       default() {
         return {};
@@ -59,7 +54,7 @@ export default {
       isSubmit: false,
       modalForm: { ...initModalForm },
       rules: {
-        name: [
+        expectValue: [
           {
             required: true,
             message: "请输入课程目标预期值",
@@ -71,7 +66,10 @@ export default {
   },
   methods: {
     visibleChange() {
-      this.modalForm = this.$objAssign(initModalForm, this.instance);
+      this.modalForm = {
+        obeCourseOutlineId: this.rowData.id,
+        expectValue: this.rowData.expectValue,
+      };
     },
     cancel() {
       this.modalIsShow = false;

+ 278 - 0
src/modules/target/components/course-outline/RequirementSelect.vue

@@ -0,0 +1,278 @@
+<template>
+  <div
+    class="org-select el-select major-select el-select--small"
+    @click="switchOpen"
+  >
+    <div v-if="multiple" class="el-select__tags">
+      <el-tag
+        v-for="item in selectedList"
+        :key="item.id"
+        @close="deleteTag($event, item)"
+        >{{ item.name }}</el-tag
+      >
+    </div>
+    <div
+      :class="[
+        'el-input el-input--small el-input--suffix',
+        { 'is-focus': isFocus },
+      ]"
+      @mouseenter="inputHovering = true"
+      @mouseleave="inputHovering = false"
+    >
+      <input
+        ref="inputRef"
+        type="text"
+        autocomplete="off"
+        :placeholder="placeholder"
+        class="el-input__inner"
+        :value="selectedOrg.name"
+        readonly
+      />
+      <span class="el-input__suffix">
+        <span class="el-input__suffix-inner">
+          <i
+            v-show="!showClose"
+            :class="[
+              'el-select__caret',
+              'el-input__icon',
+              'el-icon-arrow-up',
+              { 'is-reverse': visible },
+            ]"
+          ></i>
+          <i
+            v-if="showClose"
+            class="el-select__caret el-input__icon el-icon-circle-close"
+            @click="handleClearClick"
+          ></i>
+        </span>
+      </span>
+    </div>
+
+    <el-popover
+      popper-class="org-popover"
+      placement="bottom-start"
+      trigger="manual"
+      v-model="visible"
+    >
+      <div ref="popoverRef" slot="reference"></div>
+      <div class="org-popover-tree" v-clickoutside="handleClose">
+        <el-tree
+          ref="OrgTree"
+          :data="orgs"
+          default-expand-all
+          node-key="id"
+          :props="defaultProps"
+          check-on-click-node
+          :expand-on-click-node="false"
+          @node-click="nodeClick"
+        >
+          <span
+            :class="['custom-tree-node', { 'is-select': checkSelected(data) }]"
+            slot-scope="{ node, data }"
+          >
+            <span>{{ node.label }}</span>
+            <span>
+              <i class="el-icon-check"></i>
+            </span>
+          </span>
+        </el-tree>
+      </div>
+    </el-popover>
+  </div>
+</template>
+
+<script>
+import { trainingPlanRequirementListPage } from "../../api";
+import Clickoutside from "element-ui/src/utils/clickoutside";
+
+export default {
+  name: "org-select",
+  props: {
+    value: {
+      type: [Array, String],
+    },
+    placeholder: { type: String, default: "请选择" },
+    multiple: {
+      type: Boolean,
+      default: false,
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    clearable: {
+      type: Boolean,
+      default: true,
+    },
+    filterParam: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  directives: { Clickoutside },
+  data() {
+    return {
+      selectedOrg: {},
+      selectedOrgList: [],
+      selectedOrgIds: [],
+      visible: false,
+      isFocus: false,
+      inputHovering: false,
+      orgs: [],
+      defaultProps: {
+        label: "label",
+      },
+    };
+  },
+  computed: {
+    showClose() {
+      let hasValue = this.multiple
+        ? Array.isArray(this.value) && this.value.length > 0
+        : this.value !== undefined && this.value !== null && this.value !== "";
+      let criteria = this.clearable && this.inputHovering && hasValue;
+      return criteria;
+    },
+  },
+  watch: {
+    value(val, oldval) {
+      if (val !== oldval) this.initSelected(val);
+    },
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const data = await trainingPlanRequirementListPage({
+        cultureProgramId: this.instance.cultureProgramId,
+      });
+      this.orgs = (data || []).map((item) => {
+        item.label = `${item.sortNum}.${item.name}`;
+        item.children = item.subRequirementList.map((elem) => {
+          const nelem = { ...elem };
+          nelem.label = `指标点${item.sortNum}-${elem.sortNum}`;
+          nelem.parentName = item.name;
+          delete nelem.subRequirementList;
+          return nelem;
+        });
+        return item;
+      });
+
+      if (this.value) this.initSelected(this.value);
+    },
+    switchOpen() {
+      if (this.visible) {
+        this.handleClose();
+      } else {
+        this.handleOpen();
+      }
+    },
+    handleOpen() {
+      this.isFocus = true;
+      setTimeout(() => {
+        this.visible = true;
+      }, 200);
+    },
+    handleClose() {
+      this.visible = false;
+      this.isFocus = false;
+    },
+    getSelectedData(selectedIds) {
+      let selectedData = [];
+      if (!selectedIds.length) return [];
+
+      const findTree = (list) => {
+        list.forEach((item) => {
+          if (selectedIds.includes(item.id)) {
+            selectedData.push({ ...item });
+          }
+          if (item.children && item.children.length) {
+            findTree(item.children);
+          }
+        });
+      };
+
+      findTree(this.orgs);
+
+      return selectedData;
+    },
+    initSelected(val) {
+      if (!this.orgs.length) return;
+      if (this.multiple) {
+        const selectedIds = val || [];
+        const selectedData = this.getSelectedData(selectedIds);
+        this.selectedOrg = {};
+        this.selectedOrgList = selectedData;
+      } else {
+        const selectedIds = val ? [val] : [];
+        const selectedData = this.getSelectedData(selectedIds);
+        if (selectedData.length) {
+          this.selectedOrg = { ...selectedData[0] };
+          this.selectedOrgList = selectedData;
+        } else {
+          this.selectedOrg = {};
+          this.selectedOrgList = [];
+        }
+      }
+      this.updateSelectOrgIds();
+      this.emitChange();
+    },
+    nodeClick(data) {
+      if (!this.multiple) {
+        this.selectedOrg = { ...data };
+        this.selectedOrgList = [{ ...data }];
+        this.updateSelectOrgIds();
+        this.visible = false;
+        this.emitChange();
+        return;
+      }
+
+      if (this.selectedOrgIds.includes(data.id)) {
+        this.selectedOrgList = this.selectedOrgList.filter(
+          (item) => item.id !== data.id
+        );
+      } else {
+        this.selectedOrgList.push({ ...data });
+      }
+      this.updateSelectOrgIds();
+      this.emitChange();
+    },
+    updateSelectOrgIds() {
+      this.selectedOrgIds = this.selectedOrgList.map((item) => item.id);
+    },
+    checkSelected(data) {
+      return this.selectedOrgIds.includes(data.id);
+    },
+    deleteTag(event, data) {
+      if (this.selectedOrgIds.includes(data.id)) {
+        this.selectedOrgList = this.selectedOrgList.filter(
+          (item) => item.id !== data.id
+        );
+        this.updateSelectOrgIds();
+        this.emitChange();
+      }
+      event.stopPropagation();
+    },
+    handleClearClick(event) {
+      event.stopPropagation();
+      this.selectedOrgList = [];
+      this.selectedOrg = {};
+      this.updateSelectOrgIds();
+      this.emitChange();
+      this.handleClose();
+    },
+    emitChange() {
+      this.$emit(
+        "input",
+        this.multiple ? this.selectedOrgIds : this.selectedOrgIds[0]
+      );
+      this.$emit(
+        "change",
+        this.multiple ? this.selectedOrgList : this.selectedOrgList[0]
+      );
+    },
+  },
+};
+</script>

+ 187 - 0
src/modules/target/components/course-outline/SelectDimensionDialog.vue

@@ -0,0 +1,187 @@
+<template>
+  <div>
+    <el-dialog
+      class="select-dimension-dialog page-dialog"
+      :visible.sync="modalIsShow"
+      title="选择知识点"
+      top="10px"
+      width="600px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      append-to-body
+      @opened="visibleChange"
+    >
+      <div class="box-justify mb-2">
+        <div></div>
+        <el-button type="success" @click="toImport">导入课程知识点</el-button>
+      </div>
+      <div class="part-box part-box-pad mb-0">
+        <el-tree
+          ref="treeRef"
+          :key="treeKey"
+          :data="treeData"
+          show-checkbox
+          check-on-click-node
+          check-strictly
+          :expand-on-click-node="false"
+          node-key="id"
+          default-expand-all
+          :props="defaultProps"
+        >
+        </el-tree>
+      </div>
+
+      <div slot="footer">
+        <el-button type="primary" @click="submit">确认</el-button>
+        <el-button @click="cancel">取消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- ImportFile -->
+    <import-file
+      ref="ImportFile"
+      title="导入课程知识点"
+      :upload-url="uploadUrl"
+      :upload-data="{
+        obeCourseOutlineId: param.obeCourseOutlineId,
+      }"
+      :format="['xls', 'xlsx']"
+      :download-handle="() => downloadTemplate('courseProperty')"
+      :download-filename="dfilename"
+      :auto-upload="false"
+      @upload-success="dimensionUploaded"
+    ></import-file>
+  </div>
+</template>
+
+<script>
+import { courseOutlineTargetKnowledgeList } from "../../api";
+import ImportFile from "@/components/ImportFile.vue";
+import templateDownload from "@/mixins/templateDownload";
+
+export default {
+  name: "select-dimension-dialog",
+  components: { ImportFile },
+  mixins: [templateDownload],
+  props: {
+    selectedData: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+    disabledData: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+    param: {
+      type: Object,
+      default() {
+        return {
+          obeCourseOutlineId: "",
+        };
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      treeData: [],
+      defaultProps: {
+        children: "children",
+        label: "name",
+      },
+      treeKey: "",
+      // import
+      uploadUrl: "/api/admin/obe/course_target/dimension_import",
+      dfilename: "课程知识点导入模板.xlsx",
+    };
+  },
+  watch: {
+    "param.obeCourseOutlineId": {
+      immediate: true,
+      handler(val, oldVal) {
+        if (val !== oldVal) {
+          this.getTree();
+        }
+      },
+    },
+  },
+  methods: {
+    async dimensionUploaded() {
+      const data = await courseOutlineTargetKnowledgeList({
+        obeCourseOutlineId: this.param.obeCourseOutlineId,
+      });
+      this.treeData = data || [];
+
+      // 已存在课程目标的情况,强制关闭弹窗
+      if (this.disabledData.length) {
+        this.modalIsShow = false;
+        this.$emit("enforce-close");
+        return;
+      }
+
+      this.$nextTick(() => {
+        this.$refs.treeRef.setCheckedKeys([]);
+      });
+    },
+    async getTree() {
+      if (!this.param.obeCourseOutlineId) return;
+
+      const data = await courseOutlineTargetKnowledgeList({
+        obeCourseOutlineId: this.param.obeCourseOutlineId,
+      });
+      this.treeData = data || [];
+      this.updateDisableNode();
+    },
+    updateDisableNode() {
+      const mdTree = (list) => {
+        list.forEach((item) => {
+          item.disabled = this.disabledData.includes(item.id);
+          if (item.children && item.children.length) mdTree(item.children);
+        });
+      };
+      mdTree(this.treeData);
+      this.treeKey = this.$randomCode();
+    },
+    toImport() {
+      this.$refs.ImportFile.open();
+    },
+    visibleChange() {
+      this.updateDisableNode();
+
+      this.$nextTick(() => {
+        this.$refs.treeRef.setCheckedKeys(this.selectedData);
+      });
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    submit() {
+      const selectedNodes = this.$refs.treeRef.getCheckedNodes();
+
+      if (!selectedNodes.length) {
+        this.$message.error("请选择知识点");
+        return;
+      }
+
+      this.$emit(
+        "confirm",
+        selectedNodes.map((item) => {
+          return {
+            id: item.id,
+            name: item.name,
+            code: item.code,
+          };
+        })
+      );
+      this.cancel();
+    },
+  },
+};
+</script>