Browse Source

feat: 操作日志,试题导出

zhangjie 7 tháng trước cách đây
mục cha
commit
29abcbb658

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

@@ -89,6 +89,13 @@ export function importQuestionApi(data, headData) {
     headers: headData,
   });
 }
+export const exportQuestionApi = (data) => {
+  return $httpWithMsg.post(`${QUESTION_API}/export/paper`, {
+    params: data,
+    responseType: "blob",
+  });
+};
+
 export function updateQuestionApi(data) {
   return $httpWithMsg.post(`${QUESTION_API}/question/save`, data, {
     params: { courseId: data.courseId },

+ 16 - 10
src/modules/question/components/QuestionPreviewDialog.vue

@@ -15,21 +15,23 @@
           <rich-text :text-json="question.quesBody"></rich-text>
         </div>
         <div class="edit-cont-body">
-          <div
-            v-for="(quesOption, optionIndex) in question.quesOptions"
-            :key="optionIndex"
-            class="paper-option"
-          >
-            <span>{{ optionIndex | optionOrderWordFilter }}. </span>
-            <rich-text :text-json="quesOption.optionBody"></rich-text>
-          </div>
+          <template v-if="question.quesOptions">
+            <div
+              v-for="(quesOption, optionIndex) in question.quesOptions"
+              :key="optionIndex"
+              class="paper-option"
+            >
+              <span>{{ optionIndex | optionOrderWordFilter }}. </span>
+              <rich-text :text-json="quesOption.optionBody"></rich-text>
+            </div>
+          </template>
           <div v-if="!isNested(question.questionType)" class="paper-answer">
             <span>答案:</span>
             <question-answer :data="question"></question-answer>
           </div>
         </div>
         <div
-          v-if="!isNested(question.questionType)"
+          v-if="!isNested(question.questionType) && question.quesProperties"
           class="edit-cont-props"
           style="margin-top: 10px"
         >
@@ -85,7 +87,11 @@
                   <question-answer :data="subQuestion"></question-answer>
                 </div>
               </div>
-              <div class="edit-cont-props" style="margin-top: 10px">
+              <div
+                v-if="subQuestion.quesProperties"
+                class="edit-cont-props"
+                style="margin-top: 10px"
+              >
                 <el-tag
                   v-for="(content, propIndex) in subQuestion.quesProperties"
                   :key="propIndex"

+ 23 - 11
src/modules/question/views/QuestionManage.vue

@@ -195,6 +195,12 @@
             <svg-btn name="shanchu" @click="toBatchDelete">删除</svg-btn>
           </div>
           <div>
+            <svg-btn
+              name="daochu"
+              :disabled="downloading"
+              @click="toExportQuestion"
+              >导出</svg-btn
+            >
             <svg-btn name="shititongji" @click="toStatistics">试题统计</svg-btn>
             <svg-btn name="tixingguanli" @click="toSourceDetailManage"
               >题型管理</svg-btn
@@ -362,6 +368,7 @@ import {
   deleteQuestionApi,
   moveQuestionApi,
   copyQuestionApi,
+  exportQuestionApi,
   checkGptQuestionEnableApi,
   aiQuestionConfirmApi,
   classifyQuestionPageListApi,
@@ -381,6 +388,7 @@ import GptQuestionDialog from "../components/GptQuestionDialog.vue";
 import { mapActions, mapGetters, mapMutations } from "vuex";
 import { USER_SIGNIN } from "../../portal/store/user";
 import QuestionFolder from "@/modules/question/components/QuestionFolder.vue";
+import { downloadByApi } from "@/plugins/download";
 
 export default {
   name: "QuestionMamage",
@@ -432,6 +440,7 @@ export default {
       gptQuestionEnable: false,
       curActionQids: [],
       aiWarningMsg: "",
+      downloading: false,
     };
   },
   computed: {
@@ -578,6 +587,20 @@ export default {
       // };
       // this.$refs.QuestionImportEdit.open();
     },
+    async toExportQuestion() {
+      if (this.downloading) return;
+      this.downloading = true;
+
+      const res = await downloadByApi(() => {
+        return exportQuestionApi(this.filter);
+      }).catch((e) => {
+        this.$message.error(e || "导出失败,请重新尝试!");
+      });
+      this.downloading = false;
+
+      if (!res) return;
+      this.$message.success("导出成功!");
+    },
     toViewQuestion(row) {
       this.curQuestion = row;
       this.curQuestionIndex = this.questionList.findIndex(
@@ -776,14 +799,3 @@ export default {
   }
 }
 </style>
-<style lang="scss">
-@media screen and (max-width: 1366px) {
-  .question-manage {
-    .part-filter-form {
-      .el-form-item {
-        // width: 180px !important;
-      }
-    }
-  }
-}
-</style>

+ 12 - 0
src/modules/questions/views/OrgProperty.vue

@@ -202,6 +202,17 @@
               >*选取超过阈值的试题在查重页面显示</span
             >
           </el-form-item>
+          <el-form-item label="试题字数数量">
+            <el-input-number
+              v-model="form.properties.CHECK_LIMIT_CHAR_COUNT"
+              :precision="0"
+              :min="0"
+              :max="1000"
+            ></el-input-number>
+            <span class="tips-info margin-left-10"
+              >*低于所设字数,不进行查重计算</span
+            >
+          </el-form-item>
         </template>
       </el-form>
 
@@ -410,6 +421,7 @@ export default {
           CHECK_OPTION_DUPLICATE_THRESHOLD: "false",
           CHECK_DUPLICATE_THRESHOLD: 80,
           OPTION_DUPLICATE_THRESHOLD: 94,
+          CHECK_LIMIT_CHAR_COUNT: 0,
           CHECK_DUPLICATE_COUNT: 5,
           // 综合组卷
           // PAPER_BUILD_SYNTHESIS: "false",

+ 7 - 0
src/modules/statistics/api.js

@@ -11,3 +11,10 @@ export const statisticsExportApi = (data) => {
     responseType: "blob",
   });
 };
+
+export const logQueryApi = (data) => {
+  return $httpWithMsg.post(`${QUESTION_API}/log/page/question/count`, data);
+};
+export const logEnumsApi = () => {
+  return $httpWithMsg.post(`${QUESTION_API}/log/page/question/count`, {});
+};

+ 6 - 0
src/modules/statistics/router/index.js

@@ -7,4 +7,10 @@ export const menuRoutes = [
         /* webpackChunkName: "statistics" */ "../views/StatisticsManage.vue"
       ),
   },
+  {
+    path: "/log/manage",
+    name: "LogManage",
+    component: () =>
+      import(/* webpackChunkName: "statistics" */ "../views/LogManage.vue"),
+  },
 ];

+ 155 - 0
src/modules/statistics/views/LogManage.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="content action-log-manage">
+    <div class="part-box">
+      <el-form ref="FilterForm" label-position="left" inline>
+        <el-form-item label="操作类型:">
+          <el-select
+            v-model="filter.operationType"
+            placeholder="操作类型"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in OPERATION_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="操作人账号:">
+          <el-input
+            v-model.trim="filter.operatorName"
+            placeholder="操作人账号"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="操作时间:">
+          <el-date-picker
+            v-model="createTime"
+            type="datetimerange"
+            :picker-options="pickerOptions"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            value-format="timestamp"
+            align="right"
+            unlink-panels
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label-width="0px">
+          <el-button type="primary" @click="handleCurrentChange(1)"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box">
+      <el-table ref="table" :data="tableData">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="operatorName" label="操作人" width="180">
+          <span slot-scope="scope">
+            {{ scope.row.loginName }}({{ scope.row.realName }})
+          </span>
+        </el-table-column>
+        <el-table-column prop="operationTypeName" label="操作类型" width="100">
+        </el-table-column>
+        <el-table-column prop="createTime" label="操作时间" width="170">
+          <span slot-scope="scope">
+            {{ scope.row.createTime | timestampFilter }}
+          </span>
+        </el-table-column>
+        <el-table-column prop="detail" label="日志内容"> </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          :current-page.sync="currentPage"
+          :page-size.sync="pageSize"
+          :page-sizes="[10, 20, 50, 100, 200, 300]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { logQueryApi, logEnumsApi } from "../api";
+
+import pickerOptions from "@/constants/datePickerOptions";
+
+export default {
+  name: "LogManage",
+  data() {
+    return {
+      filter: {
+        operationType: "",
+        operatorName: "",
+        startTime: "",
+        endTime: "",
+      },
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+      tableData: [],
+      OPERATION_TYPE: {},
+      loading: false,
+      pickerOptions,
+      createTime: [],
+    };
+  },
+  async created() {
+    await this.getLogTypes();
+    await this.getList();
+  },
+  methods: {
+    async getLogTypes() {
+      const res = await logEnumsApi();
+      const data = res || [];
+      this.OPERATION_TYPE = {};
+      data.forEach((item) => {
+        this.OPERATION_TYPE[item.code] = item.name;
+      });
+    },
+    async search() {
+      if (this.loading) return;
+      this.loading = true;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.currentPage,
+        pageSize: this.pageSize,
+      };
+      if (this.createTime) {
+        datas.startTime = this.createTime[0];
+        datas.endTime = this.createTime[1];
+      }
+      const res = await logQueryApi(datas).catch(() => {});
+
+      this.loading = false;
+      if (!res) return;
+
+      this.tableData = res.data.content;
+      this.total = res.data.totalElements;
+    },
+    handleSizeChange(val) {
+      this.currentPage = 1;
+      this.pageSize = val;
+      this.search();
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.search();
+    },
+  },
+};
+</script>