Przeglądaj źródła

试题导入富文本编辑改造

zhangjie 2 lat temu
rodzic
commit
1591d3344d

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

@@ -1289,6 +1289,23 @@
       }
     }
   }
+
+  div.remark-error {
+    position: relative;
+    color: #fe5d4e;
+
+    &::after {
+      content: "";
+      display: block;
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background-color: transparent;
+      z-index: 9;
+    }
+  }
 }
 
 // question-info-edit

+ 37 - 2
src/components/vEditor/VEditor.vue

@@ -75,6 +75,18 @@ export default {
       type: String,
       default: "json",
     },
+    customEmitInput: {
+      type: Boolean,
+      default: false,
+    },
+    customRenderAction: {
+      type: Function,
+      default: null,
+    },
+    customTojsonAction: {
+      type: Function,
+      default: null,
+    },
   },
   data() {
     return {
@@ -104,10 +116,31 @@ export default {
         this.$refs.editor.innerHTML = this.value;
       } else {
         const content = this.value || { sections: [] };
+        this.renderRichTextAction(content);
+      }
+    },
+    renderRichTextAction(content) {
+      if (
+        this.customRenderAction &&
+        typeof this.customRenderAction === "function"
+      ) {
+        this.customRenderAction(content, this.$refs.editor);
+      } else {
         renderRichText(content, this.$refs.editor, false);
       }
     },
+    emitJsonAction() {
+      if (
+        this.customTojsonAction &&
+        typeof this.customTojsonAction === "function"
+      ) {
+        return this.customTojsonAction(this.$refs.editor);
+      } else {
+        return toJSON(this.$refs.editor);
+      }
+    },
     emitJSON($event) {
+      if (this.customEmitInput) return;
       if (!this.$refs.editor.contentEditable) {
         // 不是出于contentEditable则不更新
         return;
@@ -127,7 +160,7 @@ export default {
       this.addSetTime(() => {
         // console.log("input:" + Math.random());
         this.inputDelaying = false;
-        const json = toJSON(this.$refs.editor);
+        const json = this.emitJsonAction();
         this.$emit("input", json);
         this.$emit("change", json);
         // this.$emit("on-result", json);
@@ -141,7 +174,7 @@ export default {
         return;
       }
 
-      const json = toJSON(this.$refs.editor);
+      const json = this.emitJsonAction();
       this.$emit("input", json);
       this.$emit("change", json);
     },
@@ -169,6 +202,8 @@ export default {
         this.activeResize(el);
         return;
       }
+      this.clearActiveImg();
+      this.clearResizeDom();
     },
     clearActiveImg() {
       this.$refs.editor.querySelectorAll("img").forEach((imgDom) => {

+ 1 - 1
src/components/vEditor/renderJSON.js

@@ -46,7 +46,7 @@ function renderSection(section) {
  * @param {Boolean} inline 图片是否以 inline 的样式展示
  * @returns {HTMLElement} 返回根据 block 渲染好的 HTMLElement
  */
-function renderBlock(block, inline) {
+export function renderBlock(block, inline) {
   // let node = document.createElement('span')
   // let classList = node.classList
   let node;

+ 1 - 1
src/components/vEditor/toJSON.js

@@ -86,7 +86,7 @@ function checkAncestorElementTag(e) {
  * @param {Node} e
  * @returns {RichTextBlockJSON}
  */
-function toJSONBlock(e) {
+export function toJSONBlock(e) {
   /** @type {RichTextBlockJSON} */
   let block = {};
   if (e.nodeType === Node.TEXT_NODE) {

+ 44 - 7
src/modules/question/components/QuestionImportEdit.vue

@@ -49,9 +49,13 @@
             </div>
             <div class="qe-part-body">
               <v-editor
+                ref="RichTextEditor"
                 v-model="paperRichJson"
                 :enable-formula="false"
                 :enable-audio="false"
+                custom-emit-input
+                :custom-render-action="renderRichText"
+                :custom-tojson-action="richTextToJSON"
               ></v-editor>
             </div>
           </div>
@@ -117,8 +121,8 @@
 </template>
 
 <script>
-// import paperRichTextJson from "../datas/paperRichText.json";
-// import paperParseData from "../datas/paperParseData.json";
+import paperRichTextJson from "../datas/paperRichText.json";
+import paperParseData from "../datas/paperParseData.json";
 
 import { calcSum, deepCopy, objTypeOf, randomCode } from "@/plugins/utils";
 import QuestionImportPaperEdit from "./QuestionImportPaperEdit.vue";
@@ -133,6 +137,7 @@ import ImportFileDialog from "@/components/ImportFileDialog.vue";
 import { QUESTION_API } from "@/constants/constants";
 import { propertyNameQueryApi } from "@/modules/question/api";
 import { downloadByApi } from "@/plugins/download";
+import { richTextToJSON, renderRichText } from "./import-edit/richText";
 
 const questionInfoField = [
   "courseId",
@@ -174,6 +179,8 @@ export default {
       questionKey: "",
       paperData: [],
       paperRichJson: { sections: [] },
+      richTextToJSON,
+      renderRichText,
       // upload answer
       uploadAnswerUrl: `${QUESTION_API}/word/parse/import`,
       uploadAnswerData: {},
@@ -186,14 +193,38 @@ export default {
   methods: {
     async visibleChange() {
       await this.getCourseProperty();
-      // this.paperData = deepCopy(paperParseData);
-      // this.paperRichJson = deepCopy(paperRichTextJson);
+      this.paperData = deepCopy(paperParseData);
+      this.paperRichJson = this.transformRichText(paperRichTextJson);
       this.uploadData = { courseId: this.data.importData.courseId };
-      this.paperRichJson = deepCopy(this.data.richText);
-      this.paperData = deepCopy(this.data.detailInfo);
+      // this.paperRichJson = deepCopy(this.data.richText);
+      // this.paperData = deepCopy(this.data.detailInfo);
       this.transformDataInfo();
       this.questionKey = randomCode();
     },
+    transformRichText(richText) {
+      let nsections = [];
+      richText.sections.forEach((section) => {
+        nsections.push({
+          blocks: section.blocks,
+          attributes: { id: `remark-${section.remark.index}` },
+        });
+        if (section.remark && !section.remark.status) {
+          nsections.push({
+            blocks: [
+              {
+                type: "text",
+                value: section.remark.cause,
+              },
+            ],
+            attributes: {
+              id: `remark-error-${section.remark.index}`,
+              class: "remark-error",
+            },
+          });
+        }
+      });
+      return { sections: nsections };
+    },
     async getCourseProperty() {
       const res = await propertyNameQueryApi(this.data.importData.courseId, "");
       const optionList = res.data || [];
@@ -272,8 +303,14 @@ export default {
       if (this.loading) return;
       this.loading = true;
 
+      let richText = this.$refs.RichTextEditor.emitJsonAction();
+      richText.sections = richText.sections.filter(
+        (item) =>
+          !item.attributes || item.attributes["class"] !== "remark-error"
+      );
+
       const res = await questionImportParseRichText({
-        richText: this.paperRichJson,
+        richText,
         courseId: this.data.importData.courseId,
       }).catch(() => {});
       this.loading = false;

+ 117 - 0
src/modules/question/components/import-edit/richText.js

@@ -0,0 +1,117 @@
+import { renderBlock } from "@/components/vEditor/renderJSON";
+import { toJSONBlock } from "@/components/vEditor/toJSON";
+// render ----------------------------------->
+/**
+ * 将富文本 JSON 渲染到指定的元素中
+ *
+ * @param {RichTextJSON} body
+ * @param {HTMLDivElement} container
+ */
+export function renderRichText(body, container) {
+  let sections = body?.sections || [];
+  let nodes = [];
+  sections.forEach((section) => {
+    nodes.push(renderSection(section));
+  });
+  if (container != undefined) {
+    container.innerHTML = "";
+    nodes.forEach((node) => {
+      container.appendChild(node);
+    });
+  }
+}
+
+/**
+ * @param {RichTextSectionJSON} section
+ * @returns {HTMLDivElement} 返回根据 section 渲染好的 HTMLDivElement
+ */
+function renderSection(section) {
+  let blocks = section.blocks || [];
+  let inline = blocks.length > 1;
+  let node = document.createElement("div");
+  if (section.attributes) {
+    Object.keys(section.attributes).forEach((key) => {
+      node.setAttribute(key, section.attributes[key]);
+    });
+  }
+  // node.style = "display: flex;";
+  blocks.forEach((block) => {
+    node.appendChild(renderBlock(block, inline));
+  });
+  return node;
+}
+
+// toJson ----------------------------------->
+/**
+ * 将编辑器的 HTMLDivElement 转为我们需要的 JSON.stringify(RichTextJSON)
+ *
+ * @param {HTMLDivElement} editor
+ *
+ * @returns {String} JSON.stringify(RichTextJSON)
+ */
+export function richTextToJSON(editor) {
+  let newSections = [];
+  const nodesSectionsCollector = toNodeSections(editor);
+  nodesSectionsCollector.forEach((section) => {
+    let newSection = { blocks: [], attributes: section.attributes };
+    section.blockElements.forEach((elem) => {
+      const newBlock = toJSONBlock(elem);
+      if (newBlock) newSection.blocks.push(newBlock);
+    });
+
+    if (!newSection.blocks.length) {
+      // 空行特殊处理
+      newSection.blocks = [{ type: "text", value: "", param: null }];
+    }
+    newSections.push(newSection);
+  });
+  // console.log(newSections);
+
+  /** @type {RichTextJSON} */
+  const result = { sections: newSections };
+  return result;
+  // return JSON.stringify(result, null);
+}
+
+function toNodeSections(node) {
+  let sections = [];
+  let curSection = { attributes: {}, blockElements: [] };
+
+  const checkIsDiv = (elem) => {
+    return elem.nodeType == Node.ELEMENT_NODE && elem.nodeName === "DIV";
+  };
+
+  const parseNode = (node) => {
+    node.childNodes.forEach((elem) => {
+      if (checkIsDiv(elem)) {
+        if (curSection.blockElements.length) {
+          sections.push(curSection);
+        }
+        curSection = { attributes: {}, blockElements: [] };
+        if (elem.attributes) {
+          let attributes = {};
+          for (let i = 0; i < elem.attributes.length; i++) {
+            const attr = elem.attributes[i];
+            attributes[attr.nodeName] = attr.nodeValue;
+          }
+          curSection.attributes = attributes;
+        }
+      }
+
+      if (elem.childNodes && elem.childNodes.length) {
+        parseNode(elem);
+      } else {
+        curSection.blockElements.push(elem);
+      }
+    });
+  };
+
+  parseNode(node);
+
+  if (curSection.blockElements.length) {
+    sections.push(curSection);
+  }
+
+  // console.log(sections);
+  return sections;
+}

Plik diff jest za duży
+ 24 - 4
src/modules/question/datas/paperParseData.json


Plik diff jest za duży
+ 17 - 15
src/modules/question/datas/paperRichText.json


+ 8 - 1
src/modules/question/views/QuestionManage.vue

@@ -341,7 +341,14 @@ export default {
       this.$refs.QuestionEditDialog.open();
     },
     toImportQuestion() {
-      this.$refs.QuestionImportDialog.open();
+      // this.$refs.QuestionImportDialog.open();
+      this.questionImportData = {
+        importData: {
+          courseId: 9,
+          courseName: "化学",
+        },
+      };
+      this.$refs.QuestionImportEdit.open();
     },
     toViewQuestion(row) {
       this.curQuestion = row;

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików