zhangjie 2 лет назад
Родитель
Сommit
585305e950

+ 74 - 0
src/components/selection/CourseSelect.vue

@@ -0,0 +1,74 @@
+<template>
+  <el-select
+    v-model="selected"
+    :remote-method="search"
+    :loading="loading"
+    remote
+    filterable
+    :clearable="clearable"
+    :disabled="disabled"
+    :placeholder="placeholder"
+    @clear="clear"
+    @change="select"
+  >
+    <el-option
+      v-for="item in optionList"
+      :key="item.id"
+      :label="item.name + ' - ' + item.code"
+      :value="item.id"
+    ></el-option>
+  </el-select>
+</template>
+
+<script>
+import { courseQueryApi } from "@/modules/question/api";
+
+export default {
+  name: "CourseSelect",
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+    options: { type: Array, default: () => [] },
+    disabled: { type: Boolean, default: false },
+    placeholder: { type: String, default: "请选择课程" },
+    clearable: { type: Boolean, default: true },
+  },
+  data() {
+    return {
+      optionList: this.options,
+      selected: "",
+      loading: false,
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    async search(query) {
+      this.loading = true;
+      const res = await courseQueryApi(query);
+      this.optionList = res.data || [];
+      this.loading = false;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find((item) => item.id === this.selected)
+      );
+    },
+    clear() {
+      this.search("");
+      this.selected = "";
+      this.select();
+    },
+  },
+};
+</script>

+ 89 - 0
src/components/selection/PropertySelect.vue

@@ -0,0 +1,89 @@
+<template>
+  <el-select
+    v-model="selected"
+    remote
+    filterable
+    :remote-method="search"
+    :loading="loading"
+    :clearable="clearable"
+    :disabled="disabled"
+    :placeholder="placeholder"
+    @clear="clear"
+    @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 { propertyNameQueryApi } from "@/modules/question/api";
+
+export default {
+  name: "PropertyNameSelect",
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+    options: { type: Array, default: () => [] },
+    disabled: { type: Boolean, default: false },
+    placeholder: { type: String, default: "请选择属性名" },
+    clearable: { type: Boolean, default: true },
+    courseId: {
+      type: [String, Number],
+      default: "",
+    },
+  },
+  data() {
+    return {
+      optionList: this.options,
+      selected: "",
+      loading: false,
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+    courseId(val, oldval) {
+      if (val !== oldval) {
+        this.search();
+        this.selected = "";
+        this.select();
+      }
+    },
+  },
+  methods: {
+    async search(query) {
+      if (!this.courseId) {
+        this.optionList = [];
+        return;
+      }
+      this.loading = true;
+      const res = await propertyNameQueryApi(this.courseId, query);
+      this.optionList = res.data || [];
+      this.loading = false;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find((item) => item.id === this.selected)
+      );
+    },
+    clear() {
+      this.search("");
+      this.selected = "";
+      this.select();
+    },
+  },
+};
+</script>

+ 92 - 0
src/components/selection/PropertySubSelect.vue

@@ -0,0 +1,92 @@
+<template>
+  <el-select
+    v-model="selected"
+    filterable
+    :clearable="clearable"
+    :disabled="disabled"
+    :placeholder="placeholder"
+    @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 {
+  propertyFirstQueryApi,
+  propertySecondQueryApi,
+} from "@/modules/question/api";
+
+export default {
+  name: "PropertyNameSelect",
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+    options: { type: Array, default: () => [] },
+    disabled: { type: Boolean, default: false },
+    placeholder: { type: String, default: "请选择属性名" },
+    clearable: { type: Boolean, default: true },
+    parentId: {
+      type: [String, Number],
+      default: "",
+    },
+    dataType: {
+      type: String,
+      default: "first",
+    },
+  },
+  data() {
+    return {
+      optionList: this.options,
+      selected: "",
+      loading: false,
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+    parentId(val, oldval) {
+      if (val !== oldval) {
+        this.search();
+        this.selected = "";
+        this.select();
+      }
+    },
+  },
+  methods: {
+    async search() {
+      if (!this.parentId) {
+        this.optionList = [];
+        return;
+      }
+
+      this.loading = true;
+      const func =
+        this.dataType === "first"
+          ? propertyFirstQueryApi
+          : propertySecondQueryApi;
+      const res = await func(this.parentId);
+      this.optionList = res.data || [];
+      this.loading = false;
+    },
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit(
+        "change",
+        this.optionList.find((item) => item.id === this.selected)
+      );
+    },
+  },
+};
+</script>

+ 53 - 0
src/components/selection/QuestionTypeSelect.vue

@@ -0,0 +1,53 @@
+<template>
+  <el-select
+    v-model="selected"
+    :clearable="clearable"
+    :disabled="disabled"
+    :placeholder="placeholder"
+    @change="select"
+  >
+    <el-option
+      v-for="(val, key) in QUESTION_TYPES"
+      :key="key"
+      :label="val"
+      :value="key"
+    ></el-option>
+  </el-select>
+</template>
+
+<script>
+import { QUESTION_TYPES } from "@/constants/constants";
+
+export default {
+  name: "QuestionTypeSelect",
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+    disabled: { type: Boolean, default: false },
+    placeholder: { type: String, default: "请选择题型" },
+    clearable: { type: Boolean, default: true },
+  },
+  data() {
+    return {
+      QUESTION_TYPES,
+      selected: "",
+    };
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selected = val;
+      },
+    },
+  },
+  methods: {
+    select() {
+      this.$emit("input", this.selected);
+      this.$emit("change", this.selected);
+    },
+  },
+};
+</script>

+ 14 - 0
src/constants/constants.js

@@ -133,3 +133,17 @@ export const DIFFICULTY_LEVEL_ENUM = {
   MEDIUM: "中",
   EASY: "易",
 };
+
+// question
+export const QUESTION_TYPES = [
+  { value: "SINGLE_ANSWER_QUESTION", label: "单选" },
+  { value: "MULTIPLE_ANSWER_QUESTION", label: "多选" },
+  { value: "BOOL_ANSWER_QUESTION", label: "判断" },
+  { value: "FILL_BLANK_QUESTION", label: "填空" },
+  { value: "TEXT_ANSWER_QUESTION", label: "问答" },
+  { value: "READING_COMPREHENSION", label: "阅读理解" },
+  { value: "LISTENING_QUESTION", label: "听力" },
+  { value: "CLOZE", label: "完形填空" },
+  { value: "PARAGRAPH_MATCHING", label: "段落匹配" },
+  { value: "BANKED_CLOZE", label: "选词填空" },
+];

+ 35 - 0
src/modules/question/api.js

@@ -0,0 +1,35 @@
+import { $httpWithMsg } from "../../plugins/axios";
+import { QUESTION_API } from "@/constants/constants";
+
+// common select
+export const courseQueryApi = (name, enable) => {
+  return $httpWithMsg.get(`${QUESTION_API}/course/query`, {
+    params: {
+      name,
+      enable: enable || undefined,
+    },
+  });
+};
+export const propertyNameQueryApi = (courseId, name) => {
+  return $httpWithMsg.get(`${QUESTION_API}/courseProperty/enable`, {
+    params: {
+      courseId,
+      name,
+    },
+  });
+};
+export const propertyFirstQueryApi = (coursePropertyId) => {
+  return $httpWithMsg.get(`${QUESTION_API}/property/first/${coursePropertyId}`);
+};
+export const propertySecondQueryApi = (firstPropertyId) => {
+  return $httpWithMsg.get(`${QUESTION_API}/property/second/${firstPropertyId}`);
+};
+
+// question-manage
+export function questionPageListApi(data, { pageNo, pageSize }) {
+  const url = `${QUESTION_API}/importPaper/${pageNo}/${pageSize}`;
+  return $httpWithMsg.get(url, { params: data });
+}
+export function deleteQuestionApi(questionId) {
+  return $httpWithMsg.get(`${QUESTION_API}/paper/deleteQuestion/${questionId}`);
+}

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

@@ -0,0 +1,9 @@
+import QuestionManage from "./views/QuestionManage";
+
+export default [
+  {
+    path: "/question",
+    name: "QuestionManage",
+    component: QuestionManage,
+  },
+];

+ 290 - 0
src/modules/question/views/QuestionManage.vue

@@ -0,0 +1,290 @@
+<template>
+  <div class="content question-manage">
+    <!-- 正文信息 -->
+    <div class="part-box">
+      <h2 class="part-box-title">题库列表</h2>
+
+      <el-form class="part-filter-form" :inline="true" :model="filter">
+        <el-form-item label="课程名称">
+          <course-select v-model="filter.courseId"> </course-select>
+        </el-form-item>
+        <el-form-item label="题型">
+          <question-type-select v-model="filter.questionType">
+          </question-type-select>
+        </el-form-item>
+        <el-form-item label="题目内容">
+          <el-input v-model="filter.name" placeholder="题目内容"></el-input>
+        </el-form-item>
+        <el-form-item label="属性名">
+          <property-select
+            v-model="filter.coursePropertyId"
+            :course-id="filter.courseId"
+          ></property-select>
+        </el-form-item>
+        <el-form-item label="一级属性">
+          <property-sub-select
+            v-model="filter.firstPropertyId"
+            :parent-id="filter.coursePropertyId"
+            data-type="first"
+          ></property-sub-select>
+        </el-form-item>
+        <el-form-item label="二级属性">
+          <property-sub-select
+            v-model="filter.secondPropertyId"
+            :parent-id="filter.firstPropertyId"
+            data-type="second"
+          ></property-sub-select>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="danger" @click="toPage(1)">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <div>
+          <el-button
+            type="primary"
+            plain
+            icon="el-icon-data-analysis"
+            @click="toStatistics"
+            >试题统计</el-button
+          >
+          <el-button
+            type="danger"
+            plain
+            icon="icon icon-delete"
+            @click="toSafetySet"
+            >安全设置</el-button
+          >
+        </div>
+        <div>
+          <el-button
+            type="primary"
+            plain
+            icon="icon icon-import"
+            @click="toAddDir"
+            >新建文件夹</el-button
+          >
+          <el-button
+            type="primary"
+            plain
+            icon="icon icon-import"
+            @click="toCreateQuestion"
+            >创建试题</el-button
+          >
+          <el-button
+            type="primary"
+            plain
+            icon="icon icon-import"
+            @click="toImportQuestion"
+            >批量导入</el-button
+          >
+        </div>
+      </div>
+    </div>
+
+    <div class="part-box">
+      <el-table
+        v-loading="loading"
+        element-loading-text="加载中"
+        :data="tableData"
+      >
+        <el-table-column
+          type="selection"
+          width="50"
+          align="center"
+        ></el-table-column>
+        <el-table-column label="题干">
+          <template slot-scope="scope">
+            <rich-text
+              class="row-question-body"
+              title="点击查看试题"
+              :text-json="scope.row.quesBody"
+              @click="toView(scope.row)"
+            ></rich-text>
+          </template>
+        </el-table-column>
+        <el-table-column label="课程" width="120">
+          <template slot-scope="scope">
+            <span>{{ scope.row.course.name }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="题型" width="100">
+          <template slot-scope="scope">
+            <span>{{ scope.row.questionType | questionType }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="难度" width="80"> </el-table-column>
+        <el-table-column label="使用量" width="80"> </el-table-column>
+        <el-table-column label="创建人" width="120">
+          <template slot-scope="scope">
+            <span>{{ scope.row.creator }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" width="170" prop="creationTime">
+        </el-table-column>
+        <el-table-column label="操作" width="180" fixed="right">
+          <template slot-scope="scope">
+            <div class="operate_left">
+              <el-button
+                size="mini"
+                type="primary"
+                plain
+                @click="toEdit(scope.row)"
+                >编辑</el-button
+              >
+              <el-dropdown>
+                <el-button type="primary" size="mini" plain>
+                  更多 <i class="el-icon-more el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown" class="action-dropdown">
+                  <el-dropdown-item>
+                    <el-button
+                      size="mini"
+                      type="primary"
+                      plain
+                      @click="toMove(scope.row)"
+                      >移动</el-button
+                    >
+                  </el-dropdown-item>
+                  <el-dropdown-item>
+                    <el-button
+                      size="mini"
+                      type="primary"
+                      plain
+                      @click="toCopy(scope.row)"
+                      >复制</el-button
+                    >
+                  </el-dropdown-item>
+                  <el-dropdown-item>
+                    <el-button
+                      size="mini"
+                      type="danger"
+                      plain
+                      @click="toDelete(scope.row)"
+                      >删除</el-button
+                    >
+                  </el-dropdown-item>
+                  <el-dropdown-item>
+                    <el-button
+                      size="mini"
+                      type="primary"
+                      plain
+                      @click="toLink(scope.row)"
+                      >关联属性</el-button
+                    >
+                  </el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          :current-page="currentPage"
+          :page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100, 200, 300]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @current-change="toPage"
+          @size-change="handleSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { questionPageListApi, deleteQuestionApi } from "./api";
+
+export default {
+  name: "QuestionMamage",
+  data() {
+    return {
+      filter: {
+        courseId: "",
+        questionType: "",
+        name: "",
+        coursePropertyId: "",
+        firstPropertyId: "",
+        secondPropertyId: "",
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 0,
+      loading: false,
+    };
+  },
+  mounted() {
+    this.toPage(1);
+  },
+  methods: {
+    toPage(page) {
+      this.currentPage = page;
+      this.getList();
+    },
+    async getList() {
+      this.loading = true;
+      const res = await questionPageListApi(this.filter, {
+        pageNo: this.currentPage,
+        pageSize: this.pageSize,
+      });
+      this.tableData = res.data.content;
+      this.total = res.data.totalElements;
+      this.loading = false;
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.toPage(1);
+    },
+    toStatistics() {},
+    toSafetySet() {},
+    toAddDir() {},
+    toCreateQuestion() {},
+    toImportQuestion() {},
+    toView(row) {
+      console.log(row);
+    },
+    toEdit(row) {
+      console.log(row);
+      // todo:编辑试题
+    },
+    toMove(row) {
+      console.log(row);
+      // todo:归类试题
+    },
+    toCopy(row) {
+      console.log(row);
+      // todo:编辑试题
+    },
+    async toDelete(row) {
+      const confirm = await this.$confirm("确认删除试题吗?", "提示", {
+        type: "warning",
+      }).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      this.loading = true;
+      const res = await deleteQuestionApi(row.id).catch((error) => {
+        this.$notify({
+          message: error.response.data.desc,
+          type: "error",
+        });
+      });
+
+      if (!res) return;
+
+      this.$notify({
+        message: "删除成功",
+        type: "success",
+      });
+      this.getList();
+    },
+    toLink(row) {
+      console.log(row);
+    },
+  },
+};
+</script>

+ 36 - 24
src/modules/questions/views/PropertyInfo.vue

@@ -96,13 +96,17 @@
 
     <div class="part-box property-box">
       <el-tree
+        ref="PropertyTree"
         class="property-tree"
         :data="treeData"
         node-key="id"
         :props="defaultProps"
         :default-expanded-keys="ids"
         highlight-current
+        show-checkbox
+        check-strictly
         @node-click="handleNodeClick"
+        @check-change="checkChange"
         ><span
           slot-scope="{ data }"
           :class="{ 'node-level-one': !data.parentId }"
@@ -210,6 +214,7 @@ export default {
         coursePropertyId: "",
         remark: "",
       },
+      multipleSelection: [],
       showButton: true,
       showSonButtton: true,
       showMoveButtton: true,
@@ -310,6 +315,9 @@ export default {
       this.showMoveButtton = false;
       this.curProperty = Object.assign({}, object);
     },
+    checkChange() {
+      this.multipleSelection = this.$refs.PropertyTree.getCheckedKeys();
+    },
     //查询所有课程
     getCourses(query) {
       this.courseList = [];
@@ -419,34 +427,38 @@ export default {
       this.showSonButtton = true;
     },
     //删除
-    deleteProperty() {
+    async deleteProperty() {
       this.disAllBtn();
-      this.$confirm("确认删除属性吗?", "提示", {
+
+      const confirm = await this.$confirm("确认删除属性吗?", "提示", {
         type: "warning",
-      }).then(() => {
-        this.loading = true;
-        this.$http
-          .delete(
-            QUESTION_API +
-              "/property/delete/" +
-              this.curProperty.id +
-              "/" +
-              this.curProperty.coursePropertyId
-          )
-          .then(() => {
-            this.$notify({
-              message: "删除成功",
-              type: "success",
-            });
-            this.searchProperty();
-          })
-          .catch((error) => {
-            this.$notify({
-              type: "error",
-              message: error.response.data.desc,
-            });
+      }).catch(() => {});
+      if (confirm !== "confirm") return;
+
+      this.loading = true;
+      const res = await this.$http
+        .delete(
+          QUESTION_API +
+            "/property/delete/" +
+            this.curProperty.id +
+            "/" +
+            this.curProperty.coursePropertyId
+        )
+        .catch((error) => {
+          this.$notify({
+            type: "error",
+            message: error.response.data.desc,
           });
+        });
+
+      if (!res) return;
+
+      this.$notify({
+        message: "删除成功",
+        type: "success",
       });
+
+      this.searchProperty();
       this.showButton = true;
       this.showSonButtton = true;
     },

+ 8 - 0
src/plugins/globalVuePlugins.js

@@ -2,6 +2,10 @@ import { objAssign } from "@/plugins/utils";
 // component
 import VEditor from "../components/vEditor/VEditor";
 import RichText from "../components/RichText";
+import CourseSelect from "../components/selection/CourseSelect";
+import QuestionTypeSelect from "../components/selection/QuestionTypeSelect";
+import PropertySelect from "../components/selection/PropertySelect";
+import PropertySubSelect from "../components/selection/PropertySubSelect";
 // mixins
 import commonMixins from "../mixins/common";
 
@@ -12,6 +16,10 @@ export default {
     // component
     Vue.component("VEditor", VEditor);
     Vue.component("RichText", RichText);
+    Vue.component("CourseSelect", CourseSelect);
+    Vue.component("QuestionTypeSelect", QuestionTypeSelect);
+    Vue.component("PropertySelect", PropertySelect);
+    Vue.component("PropertySubSelect", PropertySubSelect);
     //全局 mixins
     Vue.mixin(commonMixins);
   },