Browse Source

Merge branch 'master' of http://60.205.254.193:3000/GCLS/GCLS_Page_Exercise

natasha 1 year ago
parent
commit
571782898f

File diff suppressed because it is too large
+ 15462 - 1
package-lock.json


+ 7 - 7
package.json

@@ -10,9 +10,9 @@
   },
   "dependencies": {
     "@tinymce/tinymce-vue": "^3.2.8",
-    "axios": "^1.6.7",
-    "core-js": "^3.36.0",
-    "dompurify": "^3.0.8",
+    "axios": "^1.6.8",
+    "core-js": "^3.36.1",
+    "dompurify": "^3.0.9",
     "element-ui": "^2.15.14",
     "hanzi-writer": "^3.6.1",
     "js-audio-recorder": "^1.0.7",
@@ -27,7 +27,7 @@
     "vuex": "^3.6.2"
   },
   "devDependencies": {
-    "@babel/core": "^7.23.9",
+    "@babel/core": "^7.24.0",
     "@babel/eslint-parser": "^7.23.10",
     "@rushstack/eslint-patch": "^1.7.2",
     "@types/md5": "^2.3.5",
@@ -38,13 +38,13 @@
     "@vue/preload-webpack-plugin": "^2.0.0",
     "babel-plugin-dynamic-import-node": "^2.3.3",
     "compression-webpack-plugin": "^6.1.2",
-    "eslint": "^8.56.0",
+    "eslint": "^8.57.0",
     "eslint-plugin-prettier": "^5.1.3",
-    "eslint-plugin-vue": "^9.21.1",
+    "eslint-plugin-vue": "^9.23.0",
     "patch-package": "^8.0.0",
     "postcss-html": "^1.6.0",
     "prettier": "^3.2.5",
-    "sass": "^1.71.0",
+    "sass": "^1.72.0",
     "sass-loader": "^13.3.3",
     "stylelint": "^15.11.0",
     "stylelint-config-recess-order": "^4.6.0",

+ 3 - 1
src/components/common/RichText.vue

@@ -162,6 +162,8 @@ export default {
           let content = args.content;
           // 使用正则表达式去掉 style 中的 background 属性
           content = content.replace(/background(-color)?:[^;]+;/g, '');
+          // 去掉 p 标签 style 中的 line-height 属性
+          content = content.replace(/<p[^>]+>/g, (match) => match.replace(/line-height:[^;]+;/g, ''));
           args.content = content;
         },
         // 指定在 WebKit 中粘贴时要保留的样式
@@ -421,7 +423,7 @@ export default {
     // 隐藏工具栏抽屉
     hideToolbarDrawer() {
       let editor = tinymce.get(this.id);
-      if (editor.queryCommandState('ToggleToolbarDrawer')) {
+      if (editor?.queryCommandState('ToggleToolbarDrawer')) {
         editor.execCommand('ToggleToolbarDrawer');
       }
     },

+ 7 - 9
src/utils/auth.js

@@ -1,9 +1,7 @@
-import Cookies from 'js-cookie';
-
 const TokenKey = 'GCLS_Token';
 
 export function getSessionID() {
-  const token = Cookies.get(TokenKey);
+  const token = localStorage.getItem(TokenKey);
   const _token = token ? JSON.parse(token) : null;
   return _token ? _token.session_id ?? '' : '';
 }
@@ -13,7 +11,7 @@ export function getSessionID() {
  * @returns {object | null}
  */
 export function getToken() {
-  const token = Cookies.get(TokenKey);
+  const token = localStorage.getItem(TokenKey);
   return token ? JSON.parse(token) : null;
 }
 
@@ -23,29 +21,29 @@ export function getToken() {
  */
 export function setToken(token) {
   const _token = typeof token === 'object' ? JSON.stringify(token) : '';
-  Cookies.set(TokenKey, _token);
+  localStorage.setItem(TokenKey, _token);
 }
 
 /**
  * 删除 token
  */
 export function removeToken() {
-  Cookies.remove(TokenKey);
+  localStorage.removeItem(TokenKey);
 }
 
 // 系统信息
 const ConfigKey = 'GCLS_Config';
 
 export function getConfig() {
-  const config = Cookies.get(ConfigKey);
+  const config = localStorage.getItem(ConfigKey);
   return config ? JSON.parse(config) : null;
 }
 
 export function setConfig(value) {
   let _val = typeof value === 'object' ? JSON.stringify(value) : '';
-  Cookies.set(ConfigKey, _val);
+  localStorage.setItem(ConfigKey, _val);
 }
 
 export function removeConfig() {
-  Cookies.remove(ConfigKey);
+  localStorage.removeItem(ConfigKey);
 }

+ 1 - 1
src/views/exercise_questions/answer/index.vue

@@ -181,7 +181,7 @@
 
       <div v-if="isAnnotations" class="score_type">
         本题分数:{{
-          question.score_type === scoreTypeList[0].value
+          question.score_type === scoreTypeList[0].value || currentQuestion.type === 'read'
             ? `总分${question.score}分`
             : `总分${question.score}分 每小题${question.score_item}分`
         }}

+ 4 - 0
src/views/exercise_questions/create/components/common/QuestionMixin.js

@@ -24,6 +24,7 @@ const mixin = {
       isEnable,
       questionNumberTypeList,
       computedQuestionNumber,
+      isGetInfo: false, // 是否获取题目信息
     };
   },
   provide: ['refreshPreviewData'],
@@ -51,6 +52,7 @@ const mixin = {
     if (this.isChild) {
       if (this.isChange) {
         this.$emit('update:isChange', false);
+        this.isGetInfo = true;
         return;
       }
       GetQuestionInfo({ question_id: this.questionId })
@@ -58,6 +60,7 @@ const mixin = {
           if (!question.content) return;
           this.data = JSON.parse(question.content);
           this.$emit('loaded');
+          this.isGetInfo = true;
           this.refreshPreviewData();
         })
         .catch(() => {});
@@ -83,6 +86,7 @@ const mixin = {
   },
   methods: {
     saveChildQuestion() {
+      if (!this.isGetInfo) return;
       SaveQuestion({
         question_id: this.questionId,
         type: this.data.type,

+ 26 - 1
src/views/exercise_questions/create/components/exercises/JudgeQuestion.vue

@@ -14,7 +14,8 @@
       <div class="content">
         <ul>
           <li v-for="(item, i) in data.option_list" :key="i" class="content-item">
-            <span class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
+            <el-input v-if="isEnable(isEnableManualModify)" v-model="item.custom_number" class="manual-modify" />
+            <span v-else class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
               {{ computedQuestionNumber(i, data.option_number_show_mode) }}
             </span>
             <div class="option-content">
@@ -148,6 +149,12 @@ import {
 export default {
   name: 'JudgeQuestion',
   mixins: [QuestionMixin],
+  props: {
+    isEnableManualModify: {
+      type: String,
+      default: 'false',
+    },
+  },
   data() {
     return {
       option_type_list,
@@ -179,6 +186,16 @@ export default {
       },
     },
   },
+  watch: {
+    isEnableManualModify: {
+      handler(val) {
+        if (val === 'false') return;
+        this.data.option_list = this.data.option_list.map((item, i) => {
+          return { ...item, custom_number: `${i + 1}` };
+        });
+      },
+    },
+  },
   methods: {
     addOption() {
       this.data.option_list.push(getOption());
@@ -213,6 +230,14 @@ export default {
 <style lang="scss" scoped>
 .content {
   &-item {
+    .manual-modify {
+      width: 55px;
+
+      :deep .el-input__inner {
+        height: 36px;
+      }
+    }
+
     .option-type {
       display: flex;
       column-gap: 8px;

+ 36 - 1
src/views/exercise_questions/create/components/exercises/MatchingQuestion.vue

@@ -15,7 +15,17 @@
         <ul>
           <li v-for="(item, i) in data.option_list" :key="i" class="content-item">
             <div v-for="(li, j) in item" :key="li.mark" class="item-cell">
-              <span v-if="j === 0" class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
+              <el-input
+                v-if="isEnable(isEnableManualModify) && j === 0"
+                v-model="li.custom_number"
+                class="manual-modify"
+              />
+              <span
+                v-else-if="!isEnable(isEnableManualModify) && j === 0"
+                class="question-number"
+                title="双击切换序号类型"
+                @dblclick="changeOptionType(data)"
+              >
                 {{ computedQuestionNumber(i, data.option_number_show_mode) }}
               </span>
               <RichText v-model="li.content" placeholder="输入内容" :inline="true" />
@@ -135,6 +145,12 @@ import {
 export default {
   name: 'MatchingQuestion',
   mixins: [QuestionMixin],
+  props: {
+    isEnableManualModify: {
+      type: String,
+      default: 'false',
+    },
+  },
   data() {
     return {
       changeOptionType,
@@ -154,6 +170,17 @@ export default {
       },
       deep: true,
     },
+    isEnableManualModify: {
+      handler(val) {
+        if (val === 'false') return;
+        this.data.option_list = this.data.option_list.map((item, i) => {
+          return item.map((li, j) => {
+            li.custom_number = `${j === 0 ? i + 1 : ''}`;
+            return li;
+          });
+        });
+      },
+    },
   },
   methods: {
     /**
@@ -202,6 +229,14 @@ export default {
       column-gap: 4px;
       align-items: center;
 
+      .manual-modify {
+        width: 55px;
+
+        :deep .el-input__inner {
+          height: 36px;
+        }
+      }
+
       .rich-wrapper {
         flex: 1;
         min-width: 100px;

+ 11 - 0
src/views/exercise_questions/create/components/exercises/ReadQuestion.vue

@@ -31,6 +31,7 @@
             :is-child="true"
             :is-change.sync="change"
             :question-id="item.id"
+            :is-enable-manual-modify="data.property.is_enable_manual_modify"
             @updatePreviewData="updatePreviewData(i, $event)"
             @loaded="loaded"
           />
@@ -74,6 +75,16 @@
             {{ label }}
           </el-radio>
         </el-form-item>
+        <el-form-item label="手动修改">
+          <el-radio
+            v-for="{ value, label } in switchOption"
+            :key="value"
+            v-model="data.property.is_enable_manual_modify"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
       </el-form>
     </template>
   </QuestionBase>

+ 36 - 7
src/views/exercise_questions/create/components/exercises/SelectQuestion.vue

@@ -15,7 +15,8 @@
       <div class="content">
         <ul>
           <li v-for="(item, i) in data.option_list" :key="i" class="content-item">
-            <span class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
+            <el-input v-if="isEnable(isEnableManualModify)" v-model="item.custom_number" class="manual-modify" />
+            <span v-else class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
               {{ computedQuestionNumber(i, data.option_number_show_mode) }}
             </span>
             <div v-if="isEnable(data.property.is_option_subdivision)" class="option-list">
@@ -178,6 +179,12 @@ import {
 export default {
   name: 'SelectQuestion',
   mixins: [QuestionMixin],
+  props: {
+    isEnableManualModify: {
+      type: String,
+      default: 'false',
+    },
+  },
   data() {
     return {
       selectTypeList,
@@ -192,6 +199,14 @@ export default {
       },
       immediate: true,
     },
+    isEnableManualModify: {
+      handler(val) {
+        if (val === 'false') return;
+        this.data.option_list = this.data.option_list.map((item, i) => {
+          return { ...item, custom_number: `${i + 1}` };
+        });
+      },
+    },
   },
   methods: {
     /**
@@ -277,12 +292,12 @@ export default {
       if (isEnable(val)) {
         // 创建与当前 option_list 相同数量的选项,选项内的细分数量与 property.option_number 数量一样
         this.data.answer.answer_list = [];
-        this.data.option_list = Array.from({ length: this.data.option_list.length }, () => {
-          return getSubdivisionOption(this.data.property.option_number);
+        this.data.option_list = Array.from({ length: this.data.option_list.length }, (el, i) => {
+          return getSubdivisionOption(this.data.property.option_number, i);
         });
       } else {
         this.data.answer.answer_list = [];
-        this.data.option_list = Array.from({ length: this.data.option_list.length }, getOption);
+        this.data.option_list = Array.from({ length: this.data.option_list.length }, (el, i) => getOption('', true, i));
       }
     },
     /**
@@ -291,11 +306,15 @@ export default {
      */
     changeOptionNumber(val) {
       this.data.answer.answer_list = [];
-      this.data.option_list = this.data.option_list.map(({ data_list, mark }) => {
+      this.data.option_list = this.data.option_list.map(({ data_list, mark, ...item }) => {
         if (data_list.length < val) {
-          return { mark, data_list: [...data_list, ...Array.from({ length: val - data_list.length }, getOption)] };
+          return {
+            mark,
+            data_list: [...data_list, ...Array.from({ length: val - data_list.length }, getOption)],
+            ...item,
+          };
         }
-        return { mark, data_list: data_list.slice(0, val) };
+        return { mark, data_list: data_list.slice(0, val), ...item };
       });
     },
   },
@@ -304,6 +323,16 @@ export default {
 
 <style lang="scss" scoped>
 .content {
+  &-item {
+    .manual-modify {
+      width: 55px;
+
+      :deep .el-input__inner {
+        height: 36px;
+      }
+    }
+  }
+
   .option-list {
     display: flex;
     flex: 1;

+ 1 - 1
src/views/exercise_questions/data/judge.js

@@ -18,7 +18,7 @@ export const option_type_list = [
 export const option_type_value_list = option_type_list.map(({ value }) => value);
 
 export function getOption(content = '') {
-  return { content, mark: getRandomNumber() };
+  return { content, mark: getRandomNumber(), custom_number: '' };
 }
 
 /**

+ 1 - 1
src/views/exercise_questions/data/matching.js

@@ -16,7 +16,7 @@ export const columnNumberList = [
 ];
 
 export function getOption(content = '') {
-  return { content, mark: getRandomNumber() };
+  return { content, mark: getRandomNumber(), custom_number: '' };
 }
 
 /**

+ 1 - 0
src/views/exercise_questions/data/read.js

@@ -40,6 +40,7 @@ export const readData = {
     question_number: '1', // 题号
     stem_question_number_font_size: fontSizeList[6], // 题干题号
     is_enable_description: switchOption[1].value, // 描述
+    is_enable_manual_modify: switchOption[1].value, // 手动修改
   },
   // 其他属性
   other: {

+ 7 - 3
src/views/exercise_questions/data/select.js

@@ -9,17 +9,21 @@ import {
 } from './common';
 import { getRandomNumber } from '@/utils/index';
 
-export function getOption(content = '') {
+export function getOption(content = '', isCustom = true, index) {
+  // 如果有 isCustom 参数,则返回参数中有 custom_number 字段,否则返回参数没有 custom_number 字段
+  if (isCustom) return { content, custom_number: `${index === undefined ? '' : index + 1}`, mark: getRandomNumber() };
   return { content, mark: getRandomNumber() };
 }
 
 /**
  * 获取选项细分数据项
  * @param {number} number 选项数
+ * @param {number} index 序号
  */
-export function getSubdivisionOption(number = 2) {
+export function getSubdivisionOption(number = 2, index) {
   return {
-    mark: getRandomNumber(),
+    custom_number: `${index === undefined ? '' : index + 1}`,
+    mark: getRandomNumber('', false),
     data_list: Array.from({ length: number }, () => getOption()),
   };
 }

+ 16 - 2
src/views/exercise_questions/preview/JudgePreview.vue

@@ -16,13 +16,17 @@
 
     <ul class="option-list">
       <li
-        v-for="({ content, mark }, i) in data.option_list"
+        v-for="({ content, mark, custom_number }, i) in data.option_list"
         :key="mark"
         :class="['option-item', { active: isAnswer(mark) }]"
       >
         <div :class="['option-content', computedIsJudgeRight(mark)]">
           <span class="serial-number" :style="{ fontSize: data.property.option_question_number_font_size }">
-            {{ computedQuestionNumber(i, data.option_number_show_mode) }}
+            {{
+              isEnable(isEnableManualModify)
+                ? `${custom_number}.`
+                : computedQuestionNumber(i, data.option_number_show_mode)
+            }}
           </span>
           <div class="rich-text" v-html="sanitizeHTML(content)"></div>
         </div>
@@ -64,6 +68,12 @@ import PreviewMixin from './components/PreviewMixin';
 export default {
   name: 'JudgePreview',
   mixins: [PreviewMixin],
+  props: {
+    isEnableManualModify: {
+      type: String,
+      default: 'false',
+    },
+  },
   data() {
     return {
       computedQuestionNumber,
@@ -162,6 +172,10 @@ export default {
           font-size: 16pt;
           color: #000;
         }
+
+        .rich-text {
+          flex: 1;
+        }
       }
 
       .option-type {

+ 4 - 0
src/views/exercise_questions/preview/ListenJudgePreview.vue

@@ -157,6 +157,10 @@ export default {
           font-size: 16pt;
           color: #000;
         }
+
+        .rich-text {
+          flex: 1;
+        }
       }
 
       .option-type {

+ 11 - 1
src/views/exercise_questions/preview/MatchingPreview.vue

@@ -16,7 +16,11 @@
     <ul ref="list" class="option-list">
       <li v-for="(item, i) in optionList" :key="i" class="list-item">
         <span class="serial-number" :style="{ fontSize: data.property.option_question_number_font_size }">
-          {{ computeOptionMethods[data.option_number_show_mode](i) }}
+          {{
+            isEnable(isEnableManualModify)
+              ? `${item[0].custom_number}.`
+              : computeOptionMethods[data.option_number_show_mode](i)
+          }}
         </span>
         <div
           v-for="({ content, mark }, j) in item"
@@ -58,6 +62,12 @@ import PreviewMixin from './components/PreviewMixin';
 export default {
   name: 'MatchingPreview',
   mixins: [PreviewMixin],
+  props: {
+    isEnableManualModify: {
+      type: String,
+      default: 'false',
+    },
+  },
   data() {
     return {
       computeOptionMethods,

+ 2 - 0
src/views/exercise_questions/preview/ReadPreview.vue

@@ -24,6 +24,7 @@
             ref="preview"
             class="preview"
             :data="item"
+            :is-enable-manual-modify="data.property.is_enable_manual_modify"
             @change="changeAnswer(i, item.type, $event)"
           />
         </template>
@@ -34,6 +35,7 @@
             :key="i"
             class="preview"
             :data="item"
+            :is-enable-manual-modify="data.property.is_enable_manual_modify"
             @change="changeAnswer(i, item.type, $event)"
           />
         </template>

+ 15 - 3
src/views/exercise_questions/preview/SelectPreview.vue

@@ -16,7 +16,11 @@
     <div v-if="isEnable(data.property.is_option_subdivision)" class="option-subdivision">
       <ul v-for="(item, i) in data.option_list" :key="item.mark" class="option-subdivision-list">
         <span class="serial-number" :style="{ fontSize: data.property.option_question_number_font_size }">
-          {{ computeOptionMethods[data.option_number_show_mode](i) }}
+          {{
+            isEnable(isEnableManualModify)
+              ? `${item.custom_number}.`
+              : computeOptionMethods[data.option_number_show_mode](i)
+          }}
         </span>
         <li
           v-for="{ content, mark } in item.data_list"
@@ -36,7 +40,7 @@
 
     <ul v-else class="option-list">
       <li
-        v-for="({ content, mark }, i) in data.option_list"
+        v-for="({ content, mark, custom_number }, i) in data.option_list"
         :key="mark"
         :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
         :class="['option-item', { active: isAnswer(mark) }, ...computedAnswerClass(mark)]"
@@ -48,7 +52,9 @@
           <SvgIcon icon-class="check-mark" width="10" height="7" />
         </span>
         <span class="serial-number" :style="{ fontSize: data.property.option_question_number_font_size }">
-          {{ computeOptionMethods[data.option_number_show_mode](i) }}
+          {{
+            isEnable(isEnableManualModify) ? `${custom_number}.` : computeOptionMethods[data.option_number_show_mode](i)
+          }}
         </span>
         <span class="content rich-text" v-html="sanitizeHTML(content)"></span>
       </li>
@@ -69,6 +75,12 @@ import PreviewMixin from './components/PreviewMixin';
 export default {
   name: 'SelectPreview',
   mixins: [PreviewMixin],
+  props: {
+    isEnableManualModify: {
+      type: String,
+      default: 'false',
+    },
+  },
   data() {
     return {
       selectTypeList,

+ 2 - 2
src/views/exercise_questions/preview/components/PreviewMixin.js

@@ -54,12 +54,12 @@ const PreviewMixin = {
       if (userAnswer) this.answer = userAnswer;
     },
     /**
-     * 题号是否有括号,如果没有则加上 '.'
+     * 题号是否有括号,如果没有则再判断是否有中文数字,如果有则加上 '、' 没有加上 '.'
      * @param {string} question_number 题目序号
      * @returns {string} 题目序号
      */
     questionNumberEndIsBracket(question_number) {
-      return `${question_number}${/[()()]/.test(question_number) ? '' : '.'}`;
+      return `${question_number}${/[()()]/.test(question_number) ? '' : /[一二三四五六七八九十百]/.test(question_number) ? '、' : '.'}`;
     },
     /**
      * 过滤 html,防止 xss 攻击

+ 3 - 3
src/views/home/personal_question/components/ShareDialog.vue

@@ -1,8 +1,8 @@
 <template>
-  <el-dialog :visible="dialogVisible" title="分享" width="730px" @close="dialogClose" @closed="dialogClosed">
+  <el-dialog :visible="dialogVisible" title="推送" width="730px" @close="dialogClose" @closed="dialogClosed">
     <div class="share-condition">
       <div class="exercise-name">
-        <div>分享名称</div>
+        <div>推送名称</div>
         <el-input v-model="share_name" placeholder="请输入" />
       </div>
       <div class="condition-top">
@@ -178,7 +178,7 @@ export default {
     },
     // 生成链接
     generateLink() {
-      if (!this.share_name) return this.$message.warning('请输入分享名称');
+      if (!this.share_name) return this.$message.warning('请输入推送名称');
       let option = {
         name: this.share_name,
         exercise_id: this.exerciseId,

+ 3 - 3
src/views/home/personal_question/index.vue

@@ -27,8 +27,8 @@
           <template slot-scope="{ row }">
             <span class="link" @click="showExercise(row.id)">查看</span>
             <span class="link" @click="$router.push({ path: '/exercise', query: { id: row.id } })">编辑</span>
-            <span class="link" @click="share(row.id)">分享</span>
-            <span class="link" @click="copyExerciseToPublicStore(row.id)">公开</span>
+            <span class="link" @click="share(row.id)">推送</span>
+            <span class="link" @click="copyExerciseToPublicStore(row.id)">共享</span>
             <span
               v-if="row.is_has_share_record === 'true'"
               class="link"
@@ -153,7 +153,7 @@ export default {
     },
     copyExerciseToPublicStore(exercise_id) {
       CopyExerciseToPublicStore({ exercise_id }).then(() => {
-        this.$message.success('公开成功');
+        this.$message.success('共享成功');
         this.getPageList();
       });
     },

Some files were not shown because too many files changed in this diff