Kaynağa Gözat

feat: 培养方案管理

zhangjie 1 yıl önce
ebeveyn
işleme
715f399530

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "teachcloud-platform-web",
-  "version": "3.3.0",
+  "version": "3.3.4",
   "scripts": {
     "start": "vue-cli-service serve",
     "serve": "vue-cli-service serve",

+ 68 - 0
src/components/base/ProfessionalSelect.vue

@@ -0,0 +1,68 @@
+<template>
+  <el-select
+    v-model="selected"
+    class="professional-select"
+    :placeholder="placeholder"
+    filterable
+    :clearable="clearable"
+    :disabled="disabled"
+    @change="select"
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.id"
+      :value="item.id"
+      :label="item.name"
+    >
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { conditionProfessionalClazz } from "../../modules/base/api";
+
+export default {
+  name: "professional-select",
+  props: {
+    disabled: { type: Boolean, default: false },
+    placeholder: { type: String, default: "请选择" },
+    value: { type: [Number, String], default: "" },
+    clearable: { type: Boolean, default: true },
+    orgId: { type: String, default: "" },
+  },
+  data() {
+    return {
+      optionList: [],
+      selected: "",
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+    orgId(val, oldval) {
+      if (val !== oldval) this.search();
+    },
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    async search() {
+      this.optionList = [];
+      const res = await conditionProfessionalClazz({ orgId: this.orgId });
+      this.optionList = res;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find((item) => item.id === this.selected)
+      );
+    },
+  },
+};
+</script>

+ 18 - 0
src/modules/base/api.js

@@ -63,6 +63,13 @@ export const conditionListClazz = ({ semesterId, examId, courseCode }) => {
     courseCode,
   });
 };
+// professional
+export const conditionProfessionalClazz = ({ orgId, name }) => {
+  return $postParam("/api/admin/basic/professional/list", {
+    orgId,
+    name,
+  });
+};
 
 // user --------------------------------->
 // user-manage
@@ -614,3 +621,14 @@ export const exportExamStudent = (datas) => {
     responseType: "blob",
   });
 };
+
+// 专业管理 ------------------->
+export const professionalListPage = (datas) => {
+  return $postParam("/api/admin/basic/professional/page", datas);
+};
+export const deleteProfessional = (id) => {
+  return $postParam("/api/admin/basic/professional/remove", { id });
+};
+export const updateProfessional = (datas) => {
+  return $post("/api/admin/basic/professional/save", datas);
+};

+ 117 - 0
src/modules/base/components/ModifyProfessional.vue

@@ -0,0 +1,117 @@
+<template>
+  <el-dialog
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :key="modalForm.id"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item prop="name" label="专业名称:">
+        <el-input
+          v-model.trim="modalForm.name"
+          placeholder="专业名称"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="orgId" label="所属学院:">
+        <org-select
+          v-model="modalForm.orgId"
+          placeholder="所属学院"
+          :filter-param="{ orgId: instance.orgId, withoutPrintingRoom: true }"
+        ></org-select>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateProfessional } from "../api";
+
+const initModalForm = {
+  id: null,
+  orgId: "",
+  name: "",
+};
+
+export default {
+  name: "ModifyProfessional",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        name: [
+          { required: true, message: "请输入专业名称", trigger: "change" },
+          {
+            message: "专业名称不能超过30个字",
+            max: 30,
+            trigger: "change",
+          },
+        ],
+        orgId: [
+          { required: true, message: "请选择所属学院", trigger: "change" },
+        ],
+      },
+    };
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "专业";
+    },
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = this.$objAssign(initModalForm, this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await updateProfessional(this.modalForm).catch(() => {});
+      this.isSubmit = false;
+
+      if (!data) return;
+
+      this.$message.success("修改成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 6 - 0
src/modules/base/router.js

@@ -22,6 +22,7 @@ import ExamManage from "./views/ExamManage.vue";
 import CourseSimpleManage from "./views/CourseSimpleManage.vue";
 import ClazzSimpleManage from "./views/ClazzSimpleManage.vue";
 import ExamStudentManage from "./views/ExamStudentManage.vue";
+import ProfessionalManage from "./views/ProfessionalManage.vue";
 
 export default [
   {
@@ -139,4 +140,9 @@ export default [
     name: "BasicExamStudentManage",
     component: ExamStudentManage,
   },
+  {
+    path: "/base/professional-manage",
+    name: "ProfessionalManage",
+    component: ProfessionalManage,
+  },
 ];

+ 176 - 0
src/modules/base/views/ProfessionalManage.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="specialty-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <template v-if="checkPrivilege('condition', 'condition')">
+          <el-form-item label="所属学院:">
+            <org-select
+              v-model="filter.orgId"
+              placeholder="所属学院"
+              :filter-param="{ orgId: userOrgId, withoutPrintingRoom: true }"
+            ></org-select>
+          </el-form-item>
+          <el-form-item label="专业名称:">
+            <el-input
+              v-model.trim="filter.name"
+              placeholder="专业名称"
+              clearable
+            ></el-input>
+          </el-form-item>
+        </template>
+        <el-form-item label-width="0px">
+          <el-button
+            v-if="checkPrivilege('button', 'select')"
+            type="primary"
+            @click="search"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <el-button
+          v-if="checkPrivilege('button', 'add')"
+          type="primary"
+          icon="el-icon-add"
+          @click="toAdd"
+          >新增</el-button
+        >
+      </div>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="name" label="专业"> </el-table-column>
+        <el-table-column prop="orgName" label="所属学院"> </el-table-column>
+        <el-table-column prop="createName" label="创建人">
+          <span slot-scope="scope">
+            {{ scope.row.userName }}({{ scope.row.loginName }})
+          </span>
+        </el-table-column>
+        <!-- <el-table-column prop="createTime" label="创建时间">
+          <span slot-scope="scope">
+            {{ scope.row.createTime | timestampFilter }}
+          </span>
+        </el-table-column> -->
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="100"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="checkPrivilege('link', 'edit')"
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'delete')"
+              class="btn-danger"
+              type="text"
+              @click="toDelete(scope.row)"
+              >删除</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :pager-count="5"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+          @size-change="pageSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+    <!-- ModifyProfessional -->
+    <modify-professional
+      v-if="checkPrivilege('button', 'add')"
+      ref="ModifyProfessional"
+      :instance="curRow"
+      @modified="getList"
+    ></modify-professional>
+  </div>
+</template>
+
+<script>
+import { professionalListPage, deleteProfessional } from "../api";
+import ModifyProfessional from "../components/ModifyProfessional.vue";
+
+export default {
+  name: "specialty-manage",
+  components: { ModifyProfessional },
+  data() {
+    return {
+      filter: {
+        orgId: "",
+        name: "",
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      curRow: {},
+      userOrgId: this.$ls.get("orgId", ""),
+    };
+  },
+  mounted() {
+    this.toPage(1);
+  },
+  methods: {
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size,
+      };
+      const data = await professionalListPage(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    search() {
+      this.toPage(1);
+    },
+    toAdd() {
+      this.curRow = {
+        orgId: this.userOrgId,
+      };
+      this.$refs.ModifyProfessional.open();
+    },
+    toEdit(row) {
+      this.curRow = row;
+      this.$refs.ModifyProfessional.open();
+    },
+    toDelete(row) {
+      this.$confirm(`确定要删除专业【${row.name}】吗?`, "提示", {
+        type: "warning",
+      })
+        .then(async () => {
+          await deleteProfessional(row.id);
+          this.$message.success("删除成功!");
+          this.deletePageLastItem();
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>

+ 62 - 0
src/modules/target/api.js

@@ -0,0 +1,62 @@
+import { $postParam, $post } from "@/plugins/axios";
+
+// 培养方案管理 ------------------->
+export const trainingPlanListPage = (datas) => {
+  return $postParam("/admin/obe/culture/program/page", datas);
+};
+export const deleteTrainingPlan = (id) => {
+  return $postParam("/admin/obe/culture/program/remove", { id });
+};
+export const copyTrainingPlan = (id) => {
+  return $postParam("/admin/obe/culture/program/copy", { id });
+};
+export const updateTrainingPlan = (datas) => {
+  return $post("/admin/obe/culture/program/save", datas);
+};
+
+// 培养方案管理-培养目标 ------------------->
+export const trainingPlanTargetListPage = (datas) => {
+  return $postParam("/admin/obe/culture/program/target/list", datas);
+};
+export const deleteTrainingPlanTarget = (id) => {
+  return $postParam("/admin/obe/culture/program/target/remove", { id });
+};
+export const updateTrainingPlanTarget = (datas) => {
+  return $post("/admin/obe/culture/program/target/save", datas);
+};
+
+// 培养方案管理-毕业要求 ------------------->
+export const trainingPlanRequirementListPage = (datas) => {
+  return $postParam("/admin/obe/culture/program/requirement/list", datas);
+};
+export const addTrainingPlanRequirementNode = (id) => {
+  return $postParam("/admin/obe/culture/program/requirement/node_add", { id });
+};
+export const deleteTrainingPlanRequirementNode = (id) => {
+  return $postParam("/admin/obe/culture/program/requirement/remove", { id });
+};
+export const updateTrainingPlanRequirement = (datas) => {
+  return $post("/admin/obe/culture/program/requirement/save", datas);
+};
+export const updateTrainingPlanRequirementPredict = (datas) => {
+  return $post("/api/admin/basic/professional/save", datas);
+};
+
+// 培养方案管理-培养目标与毕业要求关系矩阵 ------------------->
+export const trainingPlanMatrixListPage = (datas) => {
+  return $postParam("/admin/obe/culture/program/target/matrix/get", datas);
+};
+export const updateRrainingPlanMatrix = (datas) => {
+  return $post("/admin/obe/culture/program/target/matrix/save", datas);
+};
+
+// 培养方案管理-课程体系 ------------------->
+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 });
+};
+export const sortRrainingPlanCourse = (datas) => {
+  return $post("/api/admin/obe/culture/program/course/sort", datas);
+};

+ 104 - 0
src/modules/target/components/training-plan/DetailTrainingPlan.vue

@@ -0,0 +1,104 @@
+<template>
+  <el-dialog
+    class="page-dialog"
+    :visible.sync="modalIsShow"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    @close="closeHandle"
+  >
+    <div slot="title">{{ rowData.name }}</div>
+    <div class="mb-4 tab-btns">
+      <el-button
+        v-for="tab in tabs"
+        :key="tab.val"
+        size="medium"
+        :type="curTab == tab.val ? 'primary' : 'default'"
+        @click="selectMenu(tab.val)"
+        >{{ tab.name }}
+      </el-button>
+    </div>
+
+    <div v-if="modalIsShow">
+      <component :is="curTab" :row-data="rowData"></component>
+    </div>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+import TrainingPlanBase from "./TrainingPlanBase.vue";
+import TrainingPlanTarget from "./TrainingPlanTarget.vue";
+import TrainingPlanRequirement from "./TrainingPlanRequirement.vue";
+import TrainingPlanMatrix from "./TrainingPlanMatrix.vue";
+import TrainingPlanCourse from "./TrainingPlanCourse.vue";
+import TrainingPlanCourseMatrix from "./TrainingPlanCourseMatrix.vue";
+
+export default {
+  name: "detail-training-plan",
+  components: {
+    TrainingPlanBase,
+    TrainingPlanTarget,
+    TrainingPlanRequirement,
+    TrainingPlanMatrix,
+    TrainingPlanCourse,
+    TrainingPlanCourseMatrix,
+  },
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      curTab: "ProfessionalCourse",
+      tabs: [
+        {
+          name: "基本信息",
+          val: "TrainingPlanBase",
+        },
+        {
+          name: "培养目标",
+          val: "TrainingPlanTarget",
+        },
+        {
+          name: "毕业要求",
+          val: "TrainingPlanRequirement",
+        },
+        {
+          name: "培养目标与毕业要求关系矩阵",
+          val: "TrainingPlanMatrix",
+        },
+        {
+          name: "课程体系",
+          val: "TrainingPlanCourse",
+        },
+        {
+          name: "课程支撑毕业要求达成矩阵",
+          val: "TrainingPlanCourseMatrix",
+        },
+      ],
+    };
+  },
+  methods: {
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    selectMenu(tab) {
+      this.curTab = tab;
+    },
+    closeHandle() {
+      this.curTab = "TrainingPlanBase";
+    },
+  },
+};
+</script>

+ 118 - 0
src/modules/target/components/training-plan/ModifyTrainingPlan.vue

@@ -0,0 +1,118 @@
+<template>
+  <el-dialog
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :key="modalForm.id"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item prop="name" label="培养方案名称:">
+        <el-input
+          v-model.trim="modalForm.name"
+          placeholder="培养方案名称"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="professionalId" label="所属专业:">
+        <professional-select
+          v-model="modalForm.professionalId"
+          placeholder="所属专业"
+          :org-id="userOrgId"
+        ></professional-select>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateTrainingPlan } from "../../api";
+
+const initModalForm = {
+  id: null,
+  professionalId: "",
+  name: "",
+};
+
+export default {
+  name: "modify-training-plan",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      userOrgId: this.$ls.get("orgId", ""),
+      modalForm: { ...initModalForm },
+      rules: {
+        name: [
+          { required: true, message: "请输入培养方案名称", trigger: "change" },
+          {
+            message: "培养方案名称不能超过30个字",
+            max: 30,
+            trigger: "change",
+          },
+        ],
+        professionalId: [
+          { required: true, message: "请选择专业", trigger: "change" },
+        ],
+      },
+    };
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "培养方案";
+    },
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = this.$objAssign(initModalForm, this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await updateTrainingPlan(this.modalForm).catch(() => {});
+      this.isSubmit = false;
+
+      if (!data) return;
+
+      this.$message.success("修改成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 119 - 0
src/modules/target/components/training-plan/ModifyTrainingPlanRequirement.vue

@@ -0,0 +1,119 @@
+<template>
+  <el-dialog
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :key="modalForm.id"
+      label-width="100px"
+    >
+      <template v-if="isNode">
+        <el-form-item label="毕业要求:">
+          {{ instance.parentName }}
+        </el-form-item>
+        <el-form-item label="指标点:">
+          {{ instance.name }}
+        </el-form-item>
+      </template>
+      <template v-else>
+        <el-form-item label="毕业要求:">
+          {{ instance.name }}
+        </el-form-item>
+      </template>
+
+      <el-form-item label="内容:">
+        <el-input
+          v-model="modalForm.content"
+          type="textarea"
+          :autosize="{ minRows: 4, maxRows: 10 }"
+          resize="none"
+          placeholder="请输入内容"
+          clearable
+          maxlength="999"
+          show-word-limit
+        ></el-input>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateTrainingPlanRequirement } from "../../api";
+
+const initModalForm = {
+  id: null,
+  parentId: "",
+  name: "",
+  content: "",
+};
+
+export default {
+  name: "modify-training-plan-requirement",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+    };
+  },
+  computed: {
+    isNode() {
+      return !!this.instance.parentId;
+    },
+    title() {
+      const typeName = this.isNode ? "指标点" : "毕业要求";
+      return `编辑${typeName}`;
+    },
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = this.$objAssign(initModalForm, this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await updateTrainingPlanRequirement(this.modalForm).catch(
+        () => {}
+      );
+      this.isSubmit = false;
+
+      if (!data) return;
+
+      this.$message.success("修改成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 101 - 0
src/modules/target/components/training-plan/ModifyTrainingPlanRequirementPredict.vue

@@ -0,0 +1,101 @@
+<template>
+  <el-dialog
+    :visible.sync="modalIsShow"
+    title="毕业要求预期值"
+    top="10vh"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :key="modalForm.id"
+      :rules="rules"
+    >
+      <el-form-item prop="name" label="毕业要求预期值:">
+        <el-input-number
+          v-model="modalForm.name"
+          :min="0.01"
+          :max="1"
+          :step="0.01"
+          step-strictly
+          :controls="false"
+        ></el-input-number>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateTrainingPlanRequirementPredict } from "../../api";
+
+const initModalForm = {
+  id: null,
+  name: "",
+};
+
+export default {
+  name: "modify-training-plan-requirement-predict",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        name: [
+          {
+            required: true,
+            message: "请输入毕业要求预期值",
+            trigger: "change",
+          },
+        ],
+      },
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = this.$objAssign(initModalForm, this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await updateTrainingPlanRequirementPredict(
+        this.modalForm
+      ).catch(() => {});
+      this.isSubmit = false;
+
+      if (!data) return;
+
+      this.$message.success("修改成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 125 - 0
src/modules/target/components/training-plan/ModifyTrainingPlanTarget.vue

@@ -0,0 +1,125 @@
+<template>
+  <el-dialog
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :key="modalForm.id"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item prop="name" label="培养目标名称:">
+        <el-input
+          v-model.trim="modalForm.name"
+          placeholder="培养目标名称"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="detail" label="目标分解详情:">
+        <el-input
+          v-model="modalForm.detail"
+          type="textarea"
+          :autosize="{ minRows: 4, maxRows: 10 }"
+          resize="none"
+          placeholder="请输入目标分解详情"
+          clearable
+          maxlength="999"
+          show-word-limit
+        ></el-input>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateTrainingPlanTarget } from "../../api";
+
+const initModalForm = {
+  id: null,
+  cultureProgramId: "",
+  name: "",
+  detail: "",
+};
+
+export default {
+  name: "modify-training-plan-target",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: { ...initModalForm },
+      rules: {
+        name: [
+          { required: true, message: "请输入培养目标名称", trigger: "change" },
+          {
+            message: "培养目标名称不能超过30个字",
+            max: 30,
+            trigger: "change",
+          },
+        ],
+        detail: [
+          { required: true, message: "请输入目标分解详情", trigger: "change" },
+        ],
+      },
+    };
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "培养目标";
+    },
+  },
+  methods: {
+    visibleChange() {
+      this.modalForm = this.$objAssign(initModalForm, this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await updateTrainingPlanTarget(this.modalForm).catch(
+        () => {}
+      );
+      this.isSubmit = false;
+
+      if (!data) return;
+
+      this.$message.success("修改成功!");
+      this.$emit("modified");
+      this.cancel();
+    },
+  },
+};
+</script>

+ 42 - 0
src/modules/target/components/training-plan/TrainingPlanBase.vue

@@ -0,0 +1,42 @@
+<template>
+  <div class="training-plan-base part-box part-box-pad">
+    <el-descriptions title="培养方案" :column="1">
+      <el-descriptions-item label="培养方案名称">{{
+        rowData.name
+      }}</el-descriptions-item>
+      <el-descriptions-item label="专业">{{
+        rowData.professionalName
+      }}</el-descriptions-item>
+      <el-descriptions-item label="培养目标">
+        {{ rowData.targetCount }}个
+      </el-descriptions-item>
+      <el-descriptions-item label="毕业要求">
+        {{ rowData.requirementCount }}项
+      </el-descriptions-item>
+      <el-descriptions-item label="课程体系">
+        {{ rowData.courseCount }}门
+      </el-descriptions-item>
+      <el-descriptions-item label="创建人">
+        {{ rowData.createRealName }}({{ rowData.createLoginName }})
+      </el-descriptions-item>
+    </el-descriptions>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "training-plan-base",
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>

+ 147 - 0
src/modules/target/components/training-plan/TrainingPlanCourse.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="training-plan-matrix">
+    <div class="part-box part-box-pad">
+      <div class="box-justify mb-2">
+        <div></div>
+        <el-button type="primary" icon="el-icon-add" @click="toAdd"
+          >选择课程</el-button
+        >
+      </div>
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column type="index" label="序号" width="70"></el-table-column>
+        <el-table-column label="课程(代码)">
+          <template slot-scope="scope">
+            {{ scope.row.courseName | defaultFieldFilter }}({{
+              scope.row.courseCode | defaultFieldFilter
+            }})
+          </template>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="140"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-danger"
+              type="text"
+              @click="toDelete(scope.row)"
+              >删除</el-button
+            >
+            <el-button
+              v-if="scope.$index !== 0"
+              class="btn-primary"
+              type="text"
+              @click="toMove(scope.$index, 'up')"
+              >上移</el-button
+            >
+            <el-button
+              v-if="scope.$index !== total - 1"
+              class="btn-primary"
+              type="text"
+              @click="toMove(scope.$index, 'down')"
+              >下移</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- AddProfessionalCourse -->
+    <add-professional-course
+      v-if="canEdit"
+      ref="AddProfessionalCourse"
+      :row-data="rowData"
+      @modified="getList"
+    ></add-professional-course>
+  </div>
+</template>
+
+<script>
+import {
+  trainingPlanCourseListPage,
+  deleteRrainingPlanCourse,
+  sortRrainingPlanCourse,
+} from "../../api";
+
+import AddProfessionalCourse from "./AddProfessionalCourse.vue";
+
+export default {
+  name: "professional-course",
+  components: {
+    AddProfessionalCourse,
+  },
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      dataList: [],
+      total: 0,
+    };
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const data = await trainingPlanCourseListPage({
+        cultureProgramId: this.rowData.id,
+      });
+      this.dataList = data || [];
+      this.total = this.dataList.length;
+    },
+    toAdd() {
+      this.$refs.AddProfessionalCourse.open();
+    },
+    exchangeSortNumber(row1, row2) {
+      const sortNum = row1.sortNum;
+      row1.sortNum = row2.sortNum;
+      row2.sortNum = sortNum;
+    },
+    async toMove(rowIndex, type) {
+      const row1 = this.dataList[rowIndex];
+      const row2 =
+        type === "up"
+          ? this.dataList[rowIndex - 1]
+          : this.dataList[rowIndex + 1];
+
+      this.exchangeSortNumber(row1, row2);
+
+      const datas = this.dataList.map((item, index) => {
+        return {
+          id: item.id,
+          sortNum: item.sortNum,
+        };
+      });
+
+      const res = await sortRrainingPlanCourse(datas).catch(() => {
+        this.exchangeSortNumber(row1, row2);
+      });
+      if (!res) return;
+
+      this.dataList.sort((a, b) => a.sortNum - b.sortNum);
+    },
+    async toDelete(row) {
+      const confirm = await this.$confirm(
+        `确定要删除课程【${row.courseName}】吗?`,
+        "提示",
+        {
+          type: "warning",
+        }
+      ).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      await deleteRrainingPlanCourse(row.id);
+      this.$message.success("删除成功!");
+      this.getList();
+    },
+  },
+};
+</script>

+ 0 - 0
src/modules/target/components/training-plan/TrainingPlanCourseMatrix.vue


+ 82 - 0
src/modules/target/components/training-plan/TrainingPlanMatrix.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="training-plan-matrix part-box part-box-pad">
+    <div class="part-box part-box-pad">
+      <el-table v-if="columns.length" :data="dataList" border>
+        <el-table-column
+          label="毕业要求"
+          prop="requirementName"
+          min-width="200"
+          fixed="left"
+        >
+        </el-table-column>
+        <el-table-column label="培养目标" align="center">
+          <el-table-column
+            v-for="(column, cindex) in columns"
+            :key="cindex"
+            :label="column"
+            align="center"
+          >
+            <template slot-scope="scope">
+              <el-checkbox
+                v-model="scope.row.targetList[cindex].enable"
+                @change="matrixChange"
+              ></el-checkbox>
+            </template>
+          </el-table-column>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  trainingPlanMatrixListPage,
+  updateRrainingPlanMatrix,
+} from "../../api";
+
+export default {
+  name: "training-plan-matrix",
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      dataList: [],
+      columns: [],
+    };
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        cultureProgramId: this.rowData.id,
+      };
+      const res = await trainingPlanMatrixListPage(datas);
+      if (!res || !res.length) return;
+
+      this.columns = res[0].targetList.map((item) => item.name);
+    },
+    async matrixChange() {
+      const datas = this.dataList
+        .map((item) => {
+          return item.targetList
+            .filter((elem) => elem.enable)
+            .map((elem) => {
+              return {
+                id: elem.id,
+                enable: elem.enable,
+              };
+            });
+        })
+        .flat();
+
+      await updateRrainingPlanMatrix(datas);
+    },
+  },
+};
+</script>

+ 143 - 0
src/modules/target/components/training-plan/TrainingPlanRequirement.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="training-plan-requirement">
+    <div class="part-box part-box-pad">
+      <div class="box-justify mb-2">
+        <div></div>
+        <el-button type="primary" icon="el-icon-add" @click="toEditPredict"
+          >预期值</el-button
+        >
+      </div>
+      <el-table
+        ref="TableList"
+        :data="dataList"
+        row-key="id"
+        default-expand-all
+      >
+        <el-table-column prop="label" label="毕业要求及指标点" width="200">
+        </el-table-column>
+        <el-table-column prop="content" label="内容" min-width="300">
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="180"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              v-if="scope.row.children"
+              class="btn-primary"
+              type="text"
+              @click="toAddNode(scope.row)"
+              >新增指标点</el-button
+            >
+            <el-button
+              v-else
+              class="btn-danger"
+              type="text"
+              @click="toDeleteNode(scope.row)"
+              >删除</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- ModifyTrainingPlanRequirement -->
+    <modify-training-plan-requirement
+      ref="ModifyTrainingPlanRequirement"
+      :instance="curRow"
+      @modified="getList"
+    ></modify-training-plan-requirement>
+    <!-- ModifyTrainingPlanRequirementPredict -->
+    <modify-training-plan-requirement-predict
+      ref="ModifyTrainingPlanRequirementPredict"
+      :row-data="rowData"
+    ></modify-training-plan-requirement-predict>
+  </div>
+</template>
+
+<script>
+import {
+  trainingPlanRequirementListPage,
+  deleteTrainingPlanRequirementNode,
+  addTrainingPlanRequirementNode,
+} from "../../api";
+
+import ModifyTrainingPlanRequirement from "./ModifyTrainingPlanRequirement.vue";
+import ModifyTrainingPlanRequirementPredict from "./ModifyTrainingPlanRequirementPredict.vue";
+
+export default {
+  name: "training-plan-requirement",
+  components: {
+    ModifyTrainingPlanRequirement,
+    ModifyTrainingPlanRequirementPredict,
+  },
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      dataList: [],
+      curRow: {},
+    };
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      const data = await trainingPlanRequirementListPage({
+        cultureProgramId: this.rowData.id,
+      });
+      this.dataList = (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;
+      });
+    },
+    async toAddNode(row) {
+      await addTrainingPlanRequirementNode(row.id);
+      await this.getList();
+    },
+    toEdit(row) {
+      this.curRow = row;
+      this.$refs.ModifyTrainingPlanRequirement.open();
+    },
+    toEditPredict() {
+      this.$refs.ModifyTrainingPlanRequirementPredict.open();
+    },
+    async toDeleteNode(row) {
+      const confirm = await this.$confirm(
+        `确定要删除 ${row.parentName} 【${row.label}】吗?`,
+        "提示",
+        {
+          type: "warning",
+        }
+      ).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      await deleteTrainingPlanRequirementNode(row.id);
+      this.$message.success("删除成功!");
+      this.getList();
+    },
+  },
+};
+</script>

+ 128 - 0
src/modules/target/components/training-plan/TrainingPlanTarget.vue

@@ -0,0 +1,128 @@
+<template>
+  <div class="training-plan-target">
+    <div class="part-box part-box-pad">
+      <el-form>
+        <el-form-item label="总体描述">
+          <el-input
+            v-model="modalForm.description"
+            type="textarea"
+            :autosize="{ minRows: 4, maxRows: 10 }"
+            resize="none"
+            placeholder="请输入总体描述"
+            clearable
+            maxlength="999"
+            show-word-limit
+          ></el-input>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <div class="box-justify mb-2">
+        <div></div>
+        <el-button type="primary" icon="el-icon-add" @click="toAdd"
+          >新增</el-button
+        >
+      </div>
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="name" label="培养目标名称" width="160">
+        </el-table-column>
+        <el-table-column prop="detail" label="目标分解详情" min-width="300">
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="120"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              class="btn-danger"
+              type="text"
+              @click="toDelete(scope.row)"
+              >删除</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- ModifyTrainingPlanTarget -->
+    <modify-training-plan-target
+      ref="ModifyTrainingPlanTarget"
+      :instance="curRow"
+      @modified="getList"
+    ></modify-training-plan-target>
+  </div>
+</template>
+
+<script>
+import {
+  trainingPlanTargetListPage,
+  deleteTrainingPlanTarget,
+} from "../../api";
+import ModifyTrainingPlanTarget from "./ModifyTrainingPlanTarget.vue";
+
+export default {
+  name: "training-plan-target",
+  component: { ModifyTrainingPlanTarget },
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalForm: {
+        description: "",
+      },
+    };
+  },
+  methods: {
+    async getList() {
+      const datas = {
+        cultureProgramId: this.rowData.id,
+      };
+      const data = await trainingPlanTargetListPage(datas);
+      this.dataList = data || [];
+    },
+    toAdd() {
+      this.curRow = { cultureProgramId: this.rowData.id };
+      this.$refs.ModifyTrainingPlanTarget.open();
+    },
+    toEdit(row) {
+      this.curRow = row;
+      this.$refs.ModifyTrainingPlanTarget.open();
+    },
+    async toDelete(row) {
+      const confirm = await this.$confirm(
+        `确定要删除培养目标【${row.name}】吗?`,
+        "提示",
+        {
+          type: "warning",
+        }
+      ).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      await deleteTrainingPlanTarget(row.id);
+      this.$message.success("删除成功!");
+      this.deletePageLastItem();
+    },
+  },
+};
+</script>

+ 9 - 0
src/modules/target/router.js

@@ -0,0 +1,9 @@
+import TrainingPlanManage from "./views/TrainingPlanManage.vue";
+
+export default [
+  {
+    path: "/target/training-plan-manage",
+    name: "CultureProgram",
+    component: TrainingPlanManage,
+  },
+];

+ 226 - 0
src/modules/target/views/TrainingPlanManage.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="training-plan-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <template v-if="checkPrivilege('condition', 'condition')">
+          <el-form-item label="专业:">
+            <professional-select
+              v-model="filter.professionalId"
+              placeholder="专业"
+              :org-id="userOrgId"
+            ></professional-select>
+          </el-form-item>
+          <el-form-item label="培养方案名称:">
+            <el-input
+              v-model.trim="filter.name"
+              placeholder="培养方案名称"
+              clearable
+            ></el-input>
+          </el-form-item>
+        </template>
+        <el-form-item label-width="0px">
+          <el-button
+            v-if="checkPrivilege('button', 'select')"
+            type="primary"
+            @click="search"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <el-button
+          v-if="checkPrivilege('button', 'add')"
+          type="primary"
+          icon="el-icon-add"
+          @click="toAdd"
+          >新增</el-button
+        >
+      </div>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="name" label="培养方案名称"> </el-table-column>
+        <el-table-column prop="professionalName" label="专业">
+        </el-table-column>
+        <el-table-column prop="targetCount" label="培养目标">
+          <span slot-scope="scope"> {{ scope.row.targetCount }}个 </span>
+        </el-table-column>
+        <el-table-column prop="requirementCount" label="毕业要求">
+          <span slot-scope="scope"> {{ scope.row.requirementCount }}项 </span>
+        </el-table-column>
+        <el-table-column prop="courseCount" label="课程体系">
+          <span slot-scope="scope"> {{ scope.row.courseCount }}门 </span>
+        </el-table-column>
+        <el-table-column prop="createName" label="创建人">
+          <span slot-scope="scope">
+            {{ scope.row.createRealName }}({{ scope.row.createLoginName }})
+          </span>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="220"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="checkPrivilege('link', 'edit')"
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'copy')"
+              class="btn-primary"
+              type="text"
+              @click="toCopy(scope.row)"
+              >复制</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'delete')"
+              class="btn-danger"
+              type="text"
+              @click="toDelete(scope.row)"
+              >删除</el-button
+            >
+            <el-button
+              v-if="checkPrivilege('link', 'detail')"
+              class="btn-primary"
+              type="text"
+              @click="toDetail(scope.row)"
+              >查看详情</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :pager-count="5"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+          @size-change="pageSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+    <!-- ModifyTrainingPlan -->
+    <modify-training-plan
+      v-if="checkPrivilege('button', 'add')"
+      ref="ModifyTrainingPlan"
+      :instance="curRow"
+      @modified="getList"
+    ></modify-training-plan>
+    <!-- DetailTrainingPlan -->
+    <detail-training-plan
+      v-if="checkPrivilege('button', 'add')"
+      ref="DetailTrainingPlan"
+      :row-data="curRow"
+      @modified="getList"
+    ></detail-training-plan>
+  </div>
+</template>
+
+<script>
+import {
+  trainingPlanListPage,
+  deleteTrainingPlan,
+  copyTrainingPlan,
+} from "../api";
+import ModifyTrainingPlan from "../components/training-plan/ModifyTrainingPlan.vue";
+import DetailTrainingPlan from "../components/training-plan/DetailTrainingPlan.vue";
+
+export default {
+  name: "training-plan-manage",
+  components: { ModifyTrainingPlan, DetailTrainingPlan },
+  data() {
+    return {
+      filter: {
+        professionalId: "",
+        name: "",
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      curRow: {},
+      userOrgId: this.$ls.get("orgId", ""),
+    };
+  },
+  mounted() {
+    this.toPage(1);
+  },
+  methods: {
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size,
+      };
+      const data = await trainingPlanListPage(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    search() {
+      this.toPage(1);
+    },
+    toAdd() {
+      this.curRow = {};
+      this.$refs.ModifyTrainingPlan.open();
+    },
+    toEdit(row) {
+      this.curRow = row;
+      this.$refs.ModifyTrainingPlan.open();
+    },
+    async toDelete(row) {
+      const confirm = await this.$confirm(
+        `确定要删除培养方案【${row.name}】吗?`,
+        "提示",
+        {
+          type: "warning",
+        }
+      ).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      await deleteTrainingPlan(row.id);
+      this.$message.success("删除成功!");
+      this.deletePageLastItem();
+    },
+    async toCopy(row) {
+      const confirm = await this.$confirm(
+        `确定要复制培养方案【${row.name}】吗?`,
+        "提示",
+        {
+          type: "warning",
+        }
+      ).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      await copyTrainingPlan(row.id);
+      this.$message.success("复制成功!");
+      this.getList();
+    },
+    toDetail(row) {
+      this.curRow = row;
+      this.$refs.DetailTrainingPlan.open();
+    },
+  },
+};
+</script>

+ 2 - 0
src/plugins/globalVuePlugins.js

@@ -29,6 +29,7 @@ import ExamSelect from "../components/base/ExamSelect.vue";
 import OrgSelect from "../components/base/OrgSelect.vue";
 import DataTaskDialog from "../components/base/DataTaskDialog.vue";
 import StatusSelect from "../components/base/StatusSelect.vue";
+import ProfessionalSelect from "../components/base/ProfessionalSelect.vue";
 // base
 import BaseCourseSelect from "../components/base/BaseCourseSelect.vue";
 // other
@@ -61,6 +62,7 @@ const components = {
   OrgSelect,
   DataTaskDialog,
   StatusSelect,
+  ProfessionalSelect,
   // base
   BaseCourseSelect,
   // other