فهرست منبع

题库列表修改

zhangjie 2 سال پیش
والد
کامیت
8310972686

BIN
src/assets/images/icon-files-act.png


BIN
src/assets/images/icon-files.png


+ 10 - 0
src/assets/styles/icons.scss

@@ -175,4 +175,14 @@
     background-image: url(../images/icon-box-check.png);
     @include icon-14;
   }
+  &-files {
+    background-image: url(../images/icon-files.png);
+    width: 14px;
+    height: 12px;
+  }
+  &-files-act {
+    background-image: url(../images/icon-files-act.png);
+    width: 14px;
+    height: 12px;
+  }
 }

+ 42 - 0
src/assets/styles/pages.scss

@@ -807,3 +807,45 @@
   overflow-x: hidden;
   overflow-y: auto;
 }
+
+// folder-tree
+.folder-tree {
+  .node-icon {
+    margin-right: 8px;
+    width: 18px;
+    height: 14px;
+  }
+  .node-cont {
+    display: inline-block;
+    vertical-align: middle;
+    border-radius: 3px;
+    height: 26px;
+    line-height: 26px;
+    padding-left: 5px;
+    padding-right: 5px;
+    &.is-active {
+      background-color: mix(#fff, $--color-primary, 80%);
+    }
+  }
+
+  .el-tree-node__content {
+    color: $--color-text-primary;
+    height: auto;
+    min-height: 30px;
+    background-color: transparent !important;
+  }
+  .node-form {
+    display: inline-block;
+    vertical-align: middle;
+  }
+  .el-form-item {
+    margin-bottom: 0;
+  }
+  .el-input__inner {
+    width: 240px;
+  }
+  .el-button {
+    padding: 5px;
+    font-size: 14px;
+  }
+}

+ 4 - 4
src/components/selection/QuestionTypeSelect.vue

@@ -7,10 +7,10 @@
     @change="select"
   >
     <el-option
-      v-for="(val, key) in QUESTION_TYPES"
-      :key="key"
-      :label="val"
-      :value="key"
+      v-for="item in QUESTION_TYPES"
+      :key="item.code"
+      :label="item.name"
+      :value="item.code"
     ></el-option>
   </el-select>
 </template>

+ 10 - 10
src/constants/constants.js

@@ -136,14 +136,14 @@ export const DIFFICULTY_LEVEL_ENUM = {
 
 // 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: "选词填空" },
+  { code: "SINGLE_ANSWER_QUESTION", name: "单选" },
+  { code: "MULTIPLE_ANSWER_QUESTION", name: "多选" },
+  { code: "BOOL_ANSWER_QUESTION", name: "判断" },
+  { code: "FILL_BLANK_QUESTION", name: "填空" },
+  { code: "TEXT_ANSWER_QUESTION", name: "问答" },
+  { code: "READING_COMPREHENSION", name: "阅读理解" },
+  { code: "LISTENING_QUESTION", name: "听力" },
+  { code: "CLOZE", name: "完形填空" },
+  { code: "PARAGRAPH_MATCHING", name: "段落匹配" },
+  { code: "BANKED_CLOZE", name: "选词填空" },
 ];

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

@@ -33,3 +33,13 @@ export function questionPageListApi(data, { pageNo, pageSize }) {
 export function deleteQuestionApi(questionId) {
   return $httpWithMsg.get(`${QUESTION_API}/paper/deleteQuestion/${questionId}`);
 }
+export function moveQuestionApi(questionId, folderId) {
+  return $httpWithMsg.get(`${QUESTION_API}/paper/moveQuestion/`, {
+    params: { questionId, folderId },
+  });
+}
+export function copyQuestionApi(questionId) {
+  return $httpWithMsg.get(`${QUESTION_API}/paper/copyQuestion/`, {
+    params: { questionId },
+  });
+}

+ 213 - 0
src/modules/question/components/QuestionFolderDialog.vue

@@ -0,0 +1,213 @@
+<template>
+  <el-dialog
+    custom-class="side-dialog"
+    :visible.sync="modalIsShow"
+    :title="title"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-tree
+      class="folder-tree"
+      :data="folderTree"
+      node-key="id"
+      default-expand-all
+      :expand-on-click-node="false"
+      :props="defaultProps"
+      @node-click="nodeClick"
+    >
+      <span slot-scope="{ node, data }">
+        <i class="icon icon-files-act node-icon"></i>
+        <span v-if="data.id === 'none'" class="node-form">
+          <el-form
+            ref="modalFormComp"
+            :model="modalForm"
+            :rules="rules"
+            size="mini"
+            :show-message="false"
+            inline
+          >
+            <el-form-item prop="name">
+              <el-input
+                v-model="modalForm.name"
+                placeholder="请输入文件夹名称"
+                clearable
+              ></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-button
+                type="primary"
+                icon="el-icon-check"
+                @click="toCreateFolder"
+              ></el-button>
+              <el-button
+                type="danger"
+                icon="el-icon-close"
+                @click="toRemoveFolder(node, data)"
+              ></el-button>
+            </el-form-item>
+          </el-form>
+        </span>
+        <span
+          v-else
+          :class="['node-cont', { 'is-active': curNodeData.id === data.id }]"
+          >{{ node.label }}</span
+        >
+      </span>
+    </el-tree>
+
+    <div slot="footer" class="box-justify">
+      <el-button v-if="isEdit" type="primary" @click="toAddFolder"
+        >新建文件夹</el-button
+      >
+      <div>
+        <el-button v-if="!isEdit" type="primary" @click="confirm"
+          >确定</el-button
+        >
+        <el-button @click="cancel">取消</el-button>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "QuestionFolderDialog",
+  props: {
+    folderId: {
+      type: [String, Number],
+      default: "",
+    },
+    isEdit: {
+      type: Boolean,
+      default: true,
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      folderTree: [
+        {
+          id: "1",
+          parent: null,
+          name: "根目录",
+          children: [
+            {
+              id: "101",
+              parent: "1",
+              name: "2021-2022第一学期期末考试用",
+            },
+            {
+              id: "102",
+              parent: "1",
+              name: "2021-2022第二学期期末考试用",
+            },
+          ],
+        },
+      ],
+      defaultProps: {
+        label: "name",
+      },
+      curNodeData: {},
+      modalForm: {
+        name: "",
+      },
+      rules: {
+        name: [
+          {
+            required: true,
+            message: "请输入文件夹名称",
+            trigger: "change",
+          },
+        ],
+      },
+    };
+  },
+  computed: {
+    title() {
+      return this.isEdit ? "新建文件夹" : "选择文件夹";
+    },
+  },
+  methods: {
+    visibleChange() {
+      if (this.folderId && !this.isEdit) {
+        this.curNodeData = this.findNodeById(this.folderId);
+      }
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    nodeClick(data) {
+      if (data.id === "none" || data.id === this.curNodeData.id) return;
+      this.clearPreNewNode();
+      this.$nextTick(() => {
+        this.curNodeData = this.findNodeById(data.id);
+      });
+    },
+    findNodeById(id) {
+      let curNode = null;
+      const findNode = (data) => {
+        if (curNode) return;
+
+        data.forEach((item) => {
+          if (curNode) return;
+
+          if (item.id === id) {
+            curNode = item;
+            return;
+          }
+          if (item.children && item.children.length) findNode(item.children);
+        });
+      };
+      findNode(this.folderTree);
+
+      return curNode || {};
+    },
+    clearPreNewNode() {
+      const removePreNewChild = (data) => {
+        data = data.filter((item) => item.id !== "none");
+        return data.map((item) => {
+          if (item.children && item.children.length)
+            item.children = removePreNewChild(item.children);
+          return item;
+        });
+      };
+      this.folderTree = removePreNewChild(this.folderTree);
+    },
+    toAddFolder() {
+      if (!this.curNodeData.id) {
+        this.$message.error("请先选择文件夹!");
+        return;
+      }
+      const newChild = { id: "none", name: "", parentId: this.curNodeData.id };
+      if (!this.curNodeData.children) {
+        this.$set(this.curNodeData, "children", []);
+      }
+      this.curNodeData.children.push(newChild);
+      this.modalForm = { name: "" };
+    },
+    async toCreateFolder() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+    },
+    toRemoveFolder(node, data) {
+      const parent = node.parent;
+      const children = parent.data.children || parent.data;
+      const index = children.findIndex((d) => d.id === data.id);
+      children.splice(index, 1);
+    },
+    confirm() {
+      if (!this.curNodeData.id) {
+        this.$message.error("请选择文件夹!");
+        return;
+      }
+      this.$emit("selected", this.curNodeData);
+    },
+  },
+};
+</script>

+ 59 - 0
src/modules/question/components/QuestionSafetySetDialog.vue

@@ -0,0 +1,59 @@
+<template>
+  <el-dialog
+    custom-class="side-dialog"
+    :visible.sync="modalIsShow"
+    title="安全设置"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form>
+      <el-form-item label="加密“题库”,“卷库”">
+        <el-switch v-model="modalForm.questionActionIsCrypto"></el-switch>
+        <p class="tips-info">开启后,进入题库、卷库模块,需要进行密码验证!</p>
+      </el-form-item>
+    </el-form>
+
+    <div slot="footer">
+      <el-button type="primary" @click="confirm">确定</el-button>
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { mapState, mapMutations } from "vuex";
+
+export default {
+  name: "QuestionSafetySetDialog",
+  data() {
+    return {
+      modalIsShow: false,
+      modalForm: {
+        questionActionIsCrypto: false,
+      },
+    };
+  },
+  computed: {
+    ...mapState("question", ["questionActionIsCrypto"]),
+  },
+  methods: {
+    ...mapMutations("question", ["setQuestionActionIsCrypto"]),
+    visibleChange() {
+      this.modalForm.questionActionIsCrypto = this.questionActionIsCrypto;
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    confirm() {
+      this.setQuestionActionIsCrypto(this.modalForm.questionActionIsCrypto);
+      this.cancel();
+    },
+  },
+};
+</script>

+ 148 - 0
src/modules/question/components/QuestionStatisticsDialog.vue

@@ -0,0 +1,148 @@
+<template>
+  <el-dialog
+    class="question-statistics-dialog"
+    :visible.sync="modalIsShow"
+    title="试题统计"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    @open="visibleChange"
+  >
+    <div>
+      <el-button
+        v-for="item in types"
+        :key="item.code"
+        :type="item.code === curType ? 'primary' : 'default'"
+        @click="toSwitch(item)"
+        >{{ item.name }}</el-button
+      >
+    </div>
+
+    <div class="part-box">
+      <el-table
+        v-if="curType === 'base'"
+        :data="baseDataList"
+        :span-method="spanMethod"
+      >
+        <el-table-column label="题型">
+          <el-table-column
+            label="题型大类"
+            prop="questionMainTypeName"
+          ></el-table-column>
+          <el-table-column
+            label="题型小类"
+            prop="questionTypeName"
+          ></el-table-column>
+        </el-table-column>
+        <el-table-column
+          label="试题数量"
+          prop="questionCountContent"
+        ></el-table-column>
+      </el-table>
+
+      <el-table
+        v-if="curType === 'blue'"
+        :data="blueDataList"
+        :span-method="spanMethod"
+      >
+        <el-table-column label="蓝图属性" fixed="left">
+          <el-table-column
+            label="一级属性"
+            prop="firstPropertyName"
+          ></el-table-column>
+          <el-table-column
+            label="二级属性"
+            prop="secondPropertyName"
+          ></el-table-column>
+        </el-table-column>
+        <el-table-column label="题型">
+          <el-table-column
+            v-for="item in blueQtypes"
+            :key="item.code"
+            :label="item.name"
+            :prop="item.code"
+          ></el-table-column>
+        </el-table-column>
+      </el-table>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "QuestionStatisticsDialog",
+  data() {
+    return {
+      modalIsShow: false,
+      types: [
+        {
+          name: "基础构成",
+          code: "base",
+        },
+        {
+          name: "蓝图分布",
+          code: "blue",
+        },
+      ],
+      curType: "base",
+      baseDataList: [
+        {
+          id: "1",
+          questionMainTypeName: "基础题型",
+          questionTypeName: "单选",
+          questionCountContent: "48(难:10,中:15,易:23)",
+          rowspan: 3,
+        },
+        {
+          id: "2",
+          questionMainTypeName: "基础题型",
+          questionTypeName: "多选",
+          questionCountContent: "48(难:10,中:15,易:23)",
+        },
+        {
+          id: "3",
+          questionMainTypeName: "基础题型",
+          questionTypeName: "判断",
+          questionCountContent: "48(难:10,中:15,易:23)",
+        },
+        {
+          id: "4",
+          questionMainTypeName: "组合题型",
+          questionTypeName: "阅读理解",
+          questionCountContent: "48(难:10,中:15,易:23)",
+          rowspan: 2,
+        },
+        {
+          id: "5",
+          questionMainTypeName: "组合题型",
+          questionTypeName: "完型填空",
+          questionCountContent: "48(难:10,中:15,易:23)",
+        },
+      ],
+      blueDataList: [],
+      blueQtypes: [],
+    };
+  },
+  methods: {
+    visibleChange() {},
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    toSwitch(item) {
+      this.curType = item.code;
+    },
+    spanMethod({ row, columnIndex }) {
+      if (columnIndex === 0) {
+        return {
+          rowspan: row.rowspan || 0,
+          colspan: 1,
+        };
+      }
+    },
+  },
+};
+</script>

+ 14 - 0
src/modules/question/store.js

@@ -0,0 +1,14 @@
+const questionActionIsCrypto = sessionStorage.getItem("questionActionIsCrypto");
+
+export default {
+  namespaced: true,
+  state: {
+    questionActionIsCrypto: questionActionIsCrypto === "true",
+  },
+  mutations: {
+    setQuestionActionIsCrypto(state, questionActionIsCrypto) {
+      sessionStorage.setItem("questionActionIsCrypto", questionActionIsCrypto);
+      state.questionActionIsCrypto = questionActionIsCrypto;
+    },
+  },
+};

+ 66 - 12
src/modules/question/views/QuestionManage.vue

@@ -52,7 +52,7 @@
           <el-button
             type="danger"
             plain
-            icon="icon icon-delete"
+            icon="el-icon-lock"
             @click="toSafetySet"
             >安全设置</el-button
           >
@@ -61,21 +61,21 @@
           <el-button
             type="primary"
             plain
-            icon="icon icon-import"
+            icon="el-icon-folder-opened"
             @click="toAddDir"
             >新建文件夹</el-button
           >
           <el-button
             type="primary"
             plain
-            icon="icon icon-import"
+            icon="el-icon-circle-plus-outline"
             @click="toCreateQuestion"
             >创建试题</el-button
           >
           <el-button
             type="primary"
             plain
-            icon="icon icon-import"
+            icon="el-icon-upload2"
             @click="toImportQuestion"
             >批量导入</el-button
           >
@@ -193,14 +193,43 @@
         </el-pagination>
       </div>
     </div>
+
+    <!-- QuestionStatisticsDialog -->
+    <question-statistics-dialog
+      ref="QuestionStatisticsDialog"
+    ></question-statistics-dialog>
+    <!-- QuestionSafetySetDialog -->
+    <question-safety-set-dialog
+      ref="QuestionSafetySetDialog"
+    ></question-safety-set-dialog>
+    <!-- QuestionFolderDialog -->
+    <question-folder-dialog
+      ref="QuestionFolderDialog"
+      :is-edit="isEditFolder"
+      :folder-id="curQuestionFolderId"
+      @selected="folderSelected"
+    ></question-folder-dialog>
   </div>
 </template>
 
 <script>
-import { questionPageListApi, deleteQuestionApi } from "./api";
+import {
+  questionPageListApi,
+  deleteQuestionApi,
+  moveQuestionApi,
+  copyQuestionApi,
+} from "../api";
+import QuestionStatisticsDialog from "../components/QuestionStatisticsDialog.vue";
+import QuestionSafetySetDialog from "../components/QuestionSafetySetDialog.vue";
+import QuestionFolderDialog from "../components/QuestionFolderDialog.vue";
 
 export default {
   name: "QuestionMamage",
+  components: {
+    QuestionStatisticsDialog,
+    QuestionSafetySetDialog,
+    QuestionFolderDialog,
+  },
   data() {
     return {
       filter: {
@@ -216,6 +245,8 @@ export default {
       pageSize: 10,
       total: 0,
       loading: false,
+      isEditFolder: true,
+      curQuestionFolderId: null,
     };
   },
   mounted() {
@@ -240,9 +271,17 @@ export default {
       this.pageSize = val;
       this.toPage(1);
     },
-    toStatistics() {},
-    toSafetySet() {},
-    toAddDir() {},
+    toStatistics() {
+      this.$refs.QuestionStatisticsDialog.open();
+    },
+    toSafetySet() {
+      this.$refs.QuestionSafetySetDialog.open();
+    },
+    toAddDir() {
+      this.isEditFolder = true;
+      this.curQuestionFolderId = null;
+      this.$refs.QuestionFolderDialog.open();
+    },
     toCreateQuestion() {},
     toImportQuestion() {},
     toView(row) {
@@ -253,12 +292,27 @@ export default {
       // todo:编辑试题
     },
     toMove(row) {
-      console.log(row);
-      // todo:归类试题
+      this.isEditFolder = false;
+      this.curQuestionFolderId = row.foldId || null;
+      this.$refs.QuestionFolderDialog.open();
     },
-    toCopy(row) {
+    async folderSelected(folder) {
+      console.log(folder);
+      const res = await moveQuestionApi(
+        this.curQuestionFolderId,
+        folder.id
+      ).catch(() => {});
+
+      if (!res) return;
+      this.$message.success("操作成功!");
+      this.getList();
+    },
+    async toCopy(row) {
       console.log(row);
-      // todo:编辑试题
+      const res = await copyQuestionApi(row.id).catch(() => {});
+      if (!res) return;
+      this.$message.success("操作成功!");
+      this.getList();
     },
     async toDelete(row) {
       const confirm = await this.$confirm("确认删除试题吗?", "提示", {

+ 2 - 1
src/modules/questions/routes/routes.js

@@ -7,7 +7,8 @@ import InsertBluePaperStructure from "../views/InsertBluePaperStructure.vue";
 import InsertBluePaperStructureInfo from "../views/InsertBluePaperStructureInfo.vue";
 import CourseProperty from "../views/CourseProperty.vue";
 import PropertyInfo from "../views/PropertyInfo.vue";
-import ImportPaper from "../views/ImportPaper.vue";
+// import ImportPaper from "../views/ImportPaper.vue";
+import ImportPaper from "../../question/views/QuestionManage";
 import GenPaper from "../views/GenPaper.vue";
 import ImportPaperInfo from "../views/ImportPaperInfo.vue";
 import GenPaperDetail from "../views/GenPaperDetail.vue";

+ 2 - 0
src/store/index.js

@@ -4,6 +4,7 @@ import user from "../modules/portal/store/user";
 import currentPaths from "../modules/portal/store/currentPaths";
 import menuList from "../modules/portal/store/menuList";
 import { card } from "../modules/card/store";
+import question from "../modules/question/store";
 
 Vue.use(Vuex);
 
@@ -16,5 +17,6 @@ export default new Vuex.Store({
     currentPaths,
     menuList,
     card,
+    question,
   },
 });