Browse Source

原卷预览

zhangjie 3 years ago
parent
commit
00842d6be1

+ 276 - 7
src/features/examwork/StudentExamDetail/PreviewPaperDialog.vue

@@ -3,22 +3,53 @@
     class="preview-paper-dialog"
     :visible.sync="visible"
     title="查看原卷"
-    width="1200px"
     top="0"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     append-to-body
+    fullscreen
+    destroy-on-close
     @open="visibleChange"
   >
+    <div ref="PaperBody" class="paper-body">
+      <div
+        v-if="renderStructList.length"
+        class="paper-content paper-content-long"
+      >
+        <cont-item
+          v-for="(item, index) in renderStructList"
+          :key="index"
+          :data="item"
+        ></cont-item>
+      </div>
+
+      <div
+        v-for="(group, gindex) in renderGroupStructList"
+        :key="gindex"
+        class="paper-content"
+      >
+        <cont-item
+          v-for="(item, index) in group"
+          :key="index"
+          :data="item"
+        ></cont-item>
+      </div>
+    </div>
+
     <div slot="footer"></div>
   </el-dialog>
 </template>
 
 <script>
 import { studentExamDetailInfo } from "@/api/examwork-student";
+import { numberToChinese, numberToUpperCase } from "./spins/renderJSON";
+import ContItem from "./spins/ContItem.vue";
+import previewTem from "./spins/previewTem";
+import { randomCode } from "@/utils/utils";
 
 export default {
   name: "preview-paper-dialog",
+  components: { ContItem },
   props: {
     examRecordId: {
       type: String,
@@ -27,10 +58,16 @@ export default {
   data() {
     return {
       visible: false,
+      paperStruct: {},
+      renderStructList: [],
+      renderGroupStructList: [],
     };
   },
   methods: {
     visibleChange() {
+      this.paperStruct = {};
+      this.renderStructList = [];
+      this.renderGroupStructList = [];
       this.getRecordDetail();
     },
     openDialog() {
@@ -41,13 +78,245 @@ export default {
     },
     async getRecordDetail() {
       const res = await studentExamDetailInfo(this.examRecordId);
-      const paperStructJson = JSON.parse(res.data.data.paperStructJson);
-      const answerJson = JSON.parse(res.data.data.answerJson);
-      const examStudentAnswerJson = JSON.parse(
-        res.data.data.examStudentAnswerJson
-      );
-      console.log(paperStructJson, answerJson, examStudentAnswerJson);
+      this.parsePaperStruct(res.data.data);
+      this.parseRenderStructList();
+
+      const loadres = await this.waitAllImgLoaded().catch(() => {});
+      if (!loadres) {
+        this.$message.error("图片加载有误!");
+        return;
+      }
+      this.$nextTick(() => {
+        this.buildRenderGroupStructList();
+      });
+    },
+    parsePaperStruct(data) {
+      const paperStructJson = JSON.parse(data.paperStructJson);
+      const answerJson = JSON.parse(data.answerJson);
+      const examStudentAnswerJson = data.examStudentAnswerJson
+        ? JSON.parse(data.examStudentAnswerJson)
+        : [];
+      console.log(paperStructJson);
+      console.log(answerJson);
+      console.log(examStudentAnswerJson);
+
+      const answerMap = {};
+      answerJson.details.forEach((detail) => {
+        detail.questions.forEach((question) => {
+          const k = `${detail.number}-${question.number}`;
+          if (question.subQuestions) {
+            question.subQuestions.forEach((sq) => {
+              answerMap[`${k}-${sq.number}`] = sq.answer;
+            });
+          } else {
+            answerMap[k] = question.answer;
+          }
+        });
+      });
+
+      const studentAnsMap = {};
+      examStudentAnswerJson.forEach((question) => {
+        const k = `${question.mainNumber}-${question.subNumber}`;
+        if (question.subQuestions) {
+          question.subQuestions.forEach((sq) => {
+            studentAnsMap[`${k}-${sq.number}`] = JSON.parse(sq.answer);
+          });
+        } else {
+          studentAnsMap[k] = JSON.parse(question.answer);
+        }
+      });
+
+      // detail
+      paperStructJson.details.forEach((detail) => {
+        detail.questions.forEach((question) => {
+          let k = `${detail.number}-${question.number}`;
+          if (question.subQuestions) {
+            question.subQuestions.forEach((sq) => {
+              k += `-${sq.number}`;
+              sq.answer = answerMap[k];
+              sq.studentAnswer = studentAnsMap[k];
+            });
+          } else {
+            question.answer = answerMap[k];
+            question.studentAnswer = studentAnsMap[k];
+          }
+        });
+      });
+      console.log(JSON.stringify(paperStructJson));
+      this.paperStruct = paperStructJson;
+    },
+    parseRenderStructList() {
+      let renderStructList = [];
+      renderStructList.push({
+        cls: "paper-name",
+        type: "text",
+        content: this.paperStruct.name,
+      });
+      this.paperStruct.details.forEach((detail) => {
+        renderStructList.push({
+          cls: "detail-name",
+          type: "text",
+          content: `${numberToChinese(detail.number)}、${detail.name}`,
+        });
+
+        detail.questions.forEach((question) => {
+          if (question.subQuestions) {
+            if (question.body) {
+              renderStructList.push({
+                cls: "topic-title",
+                type: "json",
+                content: {
+                  isCommon: true,
+                  number: question.number,
+                  body: question.body,
+                },
+              });
+            }
+            question.subQuestions.forEach((sq) => {
+              const contents = this.parseSimpleQuestion(sq, false);
+              renderStructList.push(...contents);
+            });
+          } else {
+            const contents = this.parseSimpleQuestion(question, true);
+            renderStructList.push(...contents);
+          }
+        });
+      });
+
+      this.renderStructList = renderStructList.map((item) => {
+        item.id = randomCode();
+        return item;
+      });
+      console.log(renderStructList);
+    },
+    parseSimpleQuestion(question, isCommon) {
+      let contents = [];
+      contents.push({
+        cls: "topic-title",
+        type: "json",
+        content: {
+          isSub: !isCommon,
+          number: question.number,
+          body: question.body,
+        },
+      });
+
+      if (question.options && question.options.length) {
+        question.options.forEach((op) => {
+          contents.push({
+            cls: "topic-option",
+            type: "json",
+            content: {
+              isSub: false,
+              number: numberToUpperCase(op.number),
+              body: op.body,
+            },
+          });
+        });
+      }
+
+      if (question.answer) {
+        contents.push({
+          cls: "topic-answer",
+          type: "json",
+          content: {
+            answerType: "standard",
+            structType: question.structType,
+            body: question.answer,
+          },
+        });
+      }
+      if (question.studentAnswer) {
+        contents.push({
+          cls: "topic-answer std-answer",
+          type: "json",
+          content: {
+            answerType: "student",
+            structType: question.structType,
+            body: question.studentAnswer,
+          },
+        });
+      }
+
+      return contents;
+    },
+    loadImg(url) {
+      return new Promise((resolve, reject) => {
+        const img = new Image();
+        img.onload = function () {
+          resolve(true);
+        };
+        img.onerror = function () {
+          reject();
+        };
+        img.src = url;
+      });
+    },
+    getRichJsonImgUrls(richJson) {
+      let urls = [];
+      richJson.sections.forEach((section) => {
+        section.blocks.forEach((elem) => {
+          if (elem.type === "image") {
+            urls.push(elem.value);
+          }
+        });
+      });
+      return urls;
+    },
+    async waitAllImgLoaded() {
+      let imgUrls = [];
+      this.renderStructList.forEach((item) => {
+        if (item.type === "text") return;
+        if (item.cls === "topic-title" || item.cls === "topic-option") {
+          imgUrls.push(...this.getRichJsonImgUrls(item.content.body));
+          return;
+        }
+        // 简答题才可能会有图片
+        if (item.cls.indexOf("answer") && item.content.structType === 5) {
+          item.content.body.forEach((body) => {
+            imgUrls.push(...this.getRichJsonImgUrls(body));
+          });
+        }
+      });
+
+      console.log(imgUrls);
+
+      if (!imgUrls.length) return Promise.resolve(true);
+      const imgLoads = imgUrls.map((item) => this.loadImg(item));
+      const imgLoadResult = await Promise.all(imgLoads).catch(() => {});
+      if (imgLoadResult && imgLoadResult.length) {
+        return Promise.resolve(true);
+      } else {
+        return Promise.reject();
+      }
+    },
+    buildRenderGroupStructList() {
+      const pageMaxHeight = 1042;
+      let curGroup = [],
+        curGroupHeight = 0;
+      let renderGroupStructList = [];
+      this.renderStructList.forEach((item) => {
+        const itemHeight = document.getElementById(item.id).clientHeight;
+        if (curGroupHeight + itemHeight > pageMaxHeight) {
+          if (curGroup.length) renderGroupStructList.push(curGroup);
+          curGroup = [];
+          curGroupHeight = 0;
+        }
+        curGroup.push(item);
+        curGroupHeight += itemHeight;
+      });
+
+      if (curGroup.length) renderGroupStructList.push(curGroup);
+
+      this.renderGroupStructList = renderGroupStructList;
+      this.renderStructList = [];
+    },
+    getPreviewHtml() {
+      const html = previewTem(this.$refs.PaperBody.innerHTML);
+      console.log(html);
     },
   },
 };
 </script>
+
+<style src="@/styles/paper-preview.css"></style>

+ 42 - 0
src/features/examwork/StudentExamDetail/spins/ContItem.vue

@@ -0,0 +1,42 @@
+<template>
+  <div :class="clses" :id="data.id">
+    <template v-if="data.type === 'text'">
+      <p>{{ data.content }}</p>
+    </template>
+    <template v-else>
+      <component :is="getTypeComp" :data="data.content"></component>
+    </template>
+  </div>
+</template>
+
+<script>
+import TopicAnswer from "./TopicAnswer.vue";
+import TopicTitle from "./TopicTitle.vue";
+
+export default {
+  name: "cont-item",
+  components: { TopicAnswer, TopicTitle },
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  computed: {
+    clses() {
+      return ["cont-item", `cont-${this.data.cls}`];
+    },
+    getTypeComp() {
+      return this.data.cls.indexOf("topic-answer") !== -1
+        ? "topic-answer"
+        : "topic-title";
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>

+ 31 - 0
src/features/examwork/StudentExamDetail/spins/RichText.vue

@@ -0,0 +1,31 @@
+<template>
+  <div class="rich-text"></div>
+</template>
+
+<script>
+import { renderRichText } from "./renderJSON";
+
+export default {
+  name: "RichText",
+  props: {
+    textJson: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {};
+  },
+  watch: {
+    textJson(val) {
+      renderRichText(val, this.$el);
+    },
+  },
+  mounted() {
+    renderRichText(this.textJson, this.$el);
+  },
+  methods: {},
+};
+</script>

+ 76 - 0
src/features/examwork/StudentExamDetail/spins/TopicAnswer.vue

@@ -0,0 +1,76 @@
+<template>
+  <div class="cont-item-detail">
+    <h4 class="cont-part-title">
+      {{ data.answerType === "student" ? "学生答案" : "答案" }}
+    </h4>
+    <div
+      v-if="data.structType === STRUCT_TYPES.BOOLEAN_CHOICE"
+      class="cont-part-body"
+    >
+      {{ data.answer ? "对" : "错" }}
+    </div>
+    <div
+      v-else-if="data.structType === STRUCT_TYPES.FILL_BLANK"
+      class="cont-part-body"
+    >
+      <p v-for="(cont, cindex) in data.body" :key="cindex">
+        <rich-text :text-json="cont"></rich-text>
+      </p>
+    </div>
+    <div
+      v-else-if="data.structType === STRUCT_TYPES.TEXT"
+      class="cont-part-body"
+    >
+      <p v-for="(cont, cindex) in data.body" :key="cindex">
+        <rich-text :text-json="cont"></rich-text>
+      </p>
+    </div>
+    <div v-else class="cont-part-body">
+      {{ answer }}
+    </div>
+  </div>
+</template>
+
+<script>
+import RichText from "./RichText.vue";
+
+export default {
+  name: "topic-answer",
+  components: { RichText },
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      STRUCT_TYPES: {
+        SINGLE_CHOICE: 1,
+        MULTIPLE_CHOICE: 2,
+        BOOLEAN_CHOICE: 3,
+        FILL_BLANK: 4,
+        TEXT: 5,
+        NESTED: 6,
+        LISTENING: 7,
+        MATCHES: 8,
+      },
+      optionNames: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+    };
+  },
+  computed: {
+    answer() {
+      const SELECT_TYPES = [
+        this.STRUCT_TYPES.SINGLE_CHOICE,
+        this.STRUCT_TYPES.MULTIPLE_CHOICE,
+      ];
+      return SELECT_TYPES.includes(this.data.structType)
+        ? this.data.body.map((item) => this.optionNames[item - 1]).join("")
+        : "";
+    },
+  },
+  methods: {},
+};
+</script>

+ 29 - 0
src/features/examwork/StudentExamDetail/spins/TopicTitle.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="cont-item-detail">
+    <h4 v-if="data.isSub" class="cont-part-title">{{ data.number }})</h4>
+    <h4 v-else class="cont-part-title">{{ data.number }}、</h4>
+    <div class="cont-part-body">
+      <rich-text :text-json="data.body"></rich-text>
+    </div>
+  </div>
+</template>
+
+<script>
+import RichText from "./RichText.vue";
+
+export default {
+  name: "topic-title",
+  components: { RichText },
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {};
+  },
+};
+</script>

+ 22 - 0
src/features/examwork/StudentExamDetail/spins/previewTem.js

@@ -0,0 +1,22 @@
+const resetCss = `a,body,div,footer,h1,h2,h3,h4,h5,h6,header,input,li,ol,p,span,td,th,tr,ul{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:rgba(255,255,255,0)}li{list-style:none}em,i,u{font-style:normal}button{font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",Arial,sans-serif}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:700}fieldset,img{border:0}abbr{border:0;font-variant:normal}a{text-decoration:none;color:inherit}img{vertical-align:middle}body{font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:14px;color:#545454;background:#f5f5f5;min-width:1366px}`;
+
+const previewCss = `.paper-content {width: 210mm;height: 297mm;padding: 40px;margin:0 auto;}.cont-item {page-break-after: always;}.cont-item p {margin: 0;}.cont-paper-name {font-size: 20px;padding-bottom: 30px;text-align: center;}.cont-detail-name {font-size: 16px;font-weight: 600;padding: 15px 0;}.cont-topic-title {margin-bottom: 5px;}.cont-item-detail {min-height: 20px;}.cont-part-title {font-size: 14px;line-height: 20px;float: left;margin: 0;}.cont-part-body {min-height: 20px;}.cont-part-body img {max-width: 100%;max-height: 1042px;}.cont-topic-answer .cont-part-title {width: 36px;}.cont-topic-answer .cont-part-body {margin-left: 36px;}.std-answer.cont-topic-answer .cont-part-title {width: 72px;}.std-answer.cont-topic-answer .cont-part-body {margin-left: 72px;}.cont-topic-answer + .cont-topic-title {margin-top: 15px;}`;
+
+export default (domeStr) => {
+  return `
+  <!DOCTYPE html>
+    <html>
+      <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="viewport" content="width=device-width,initial-scale=1.0,
+        maximum-scale=1.0,minimum-scale=1.0, user-scalable=no" "="">
+        <meta name=" renderer" content="webkit|ie-comp|ie-stand" />
+        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+        <title>原卷</title>
+
+      </head>
+      <style>${resetCss}${previewCss}</style>
+      <body>${domeStr}</body>
+    </html>
+  `;
+};

+ 256 - 0
src/features/examwork/StudentExamDetail/spins/renderJSON.js

@@ -0,0 +1,256 @@
+// const _text_styles_ = ["bold", "underline", "italic", "sup", "sub"];
+
+let _isPreview = true;
+let _container;
+
+function transformToLocalResourcePath(value) {
+  return value;
+}
+
+/**
+ * 将富文本 JSON 渲染到指定的元素中
+ *
+ * @param {RichTextJSON} body
+ * @param {HTMLDivElement} container
+ */
+export function renderRichText(body, container, isPreview = true) {
+  _isPreview = isPreview;
+  _container = container;
+  let sections = body?.sections || [];
+  let nodes = [];
+  sections.forEach((section) => {
+    nodes.push(renderSection(section));
+  });
+  if (container != undefined) {
+    // container.classList.add("rich-text");
+    while (container.hasChildNodes()) {
+      container.removeChild(container.lastChild);
+    }
+    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");
+  // node.style = "display: flex;";
+  blocks.forEach((block) => {
+    node.appendChild(renderBlock(block, inline));
+  });
+  return node;
+}
+
+/**
+ * @param {RichTextBlockJSON} block
+ * @param {Boolean} inline 图片是否以 inline 的样式展示
+ * @returns {HTMLElement} 返回根据 block 渲染好的 HTMLElement
+ */
+function renderBlock(block, inline) {
+  // let node = document.createElement('span')
+  // let classList = node.classList
+  let node;
+  if (block.type === "text") {
+    // classList.add('text')
+    // if (block.param != undefined) {
+    //     _text_styles_.forEach(style => {
+    //         if (block.param[style] === true) {
+    //             classList.add(style)
+    //         }
+    //     })
+    // }
+    if (block.param) {
+      let uNode, bNode, iNode;
+      if (block.param.underline) {
+        uNode = document.createElement("u");
+      }
+      if (block.param.bold) {
+        bNode = document.createElement("b");
+      }
+      if (block.param.italic) {
+        iNode = document.createElement("i");
+      }
+      // 将不为空的元素依次append
+      node = [uNode, bNode, iNode]
+        .filter((v) => v)
+        .reduceRight((p, c) => {
+          c.appendChild(p);
+          return c;
+        });
+
+      let childNode = node;
+
+      for (let i = 0; i < 3; i++) {
+        if (childNode && childNode.hasChildNodes()) {
+          childNode = childNode.childNodes[0];
+        }
+      }
+
+      childNode.textContent = block.value;
+    } else {
+      node = document.createTextNode(block.value);
+    }
+  } else if (block.type === "image") {
+    // classList.add("image");
+    // classList.add("loading");
+    node = document.createElement("img");
+    // node.setAttribute("title", "could be a length of audio");
+    // node.addEventListener("click", (ev) => {
+    //   // console.log(ev);
+    //   const shift = ev.shiftKey;
+    //   node.setAttribute(
+    //     "width",
+    //     +getComputedStyle(node).width.replace("px", "") +
+    //       (shift ? 1 : -1) +
+    //       "px"
+    //   );
+    // });
+    if (!_isPreview) {
+      const __container = _container; // because of ES modules static binding
+      node.addEventListener("wheel", (ev) => {
+        // ev.stopPropagation();
+        // console.log(ev);
+        const shift = ev.deltaY > 0;
+        const newWidth =
+          +getComputedStyle(node).width.replace("px", "") + (shift ? 1 : -1);
+        // console.log(newWidth, ev.target.naturalWidth);
+        if (newWidth >= 16 && newWidth <= ev.target.naturalWidth) {
+          node.setAttribute("width", newWidth);
+          // console.log(newWidth);
+          __container.dispatchEvent(new Event("input"));
+        }
+      });
+    }
+    if (inline === true) {
+      node.classList.add("inline");
+    }
+
+    if (block.value.startsWith("./")) {
+      node.src = transformToLocalResourcePath(block.value);
+    } else {
+      node.src = block.value;
+    }
+
+    node.dataset.isImage = true;
+    // 公式latex表达式
+    if (block.latex) node.dataset.latex = block.latex;
+    // param
+    if (block.param) {
+      node.style.width = block.param.width;
+      node.style.height = block.param.height;
+    }
+  } else if (block.type === "cloze") {
+    node = document.createElement("img");
+
+    node.src = `/img/editor/answer_point_${block.value}.png`;
+    node.dataset.isAnswerPoint = true;
+    node.dataset.order = block.value;
+  } else if (block.type === "audio") {
+    // classList.add("audio");
+    // let audio = node.appendChild(new Audio());
+    // audio.src = block.value;
+    // audio.controls = true;
+    // span 可以被追加文本内容,不好
+    // node = document.createElement("span");
+    // node.className = "audio";
+    // node.dataset["src"] = block.value;
+    // node.innerHTML = "&#9654;";
+    // // md5 src?
+    // node.id = "what";
+    // var sheet = window.document.styleSheets[0];
+    // sheet.insertRule(
+    //   `#what::after{content: "1:03"; font-size: 11px;}`,
+    //   sheet.cssRules.length
+    // );
+
+    if (_isPreview) {
+      node = document.createElement("audio");
+      node.className = "audio";
+      node.src = transformToLocalResourcePath(block.value);
+      node.controls = true;
+      node.controlsList = "nodownload";
+    } else {
+      node = document.createElement("img");
+      node.className = "audio";
+      node.src = "/img/editor/text_audio.png";
+      node.dataset.audioSrc = transformToLocalResourcePath(block.value);
+      node.dataset.isAudio = true;
+      node.dataset.duration = block.param.duration;
+      const mDuration = require("moment").unix(0);
+      mDuration.second(block.param.duration);
+      node.title = mDuration.format("mm:ss");
+    }
+  }
+
+  return node;
+}
+
+/**
+ * @param {Array} content
+ * @returns rich json
+ */
+export function createRichJsonFromText(content) {
+  if (content && content.length) {
+    const blocks = content.map((cont) => {
+      return {
+        type: "text",
+        value: cont,
+      };
+    });
+    return {
+      sections: [
+        {
+          blocks,
+        },
+      ],
+    };
+  } else {
+    return {
+      sections: [
+        {
+          blocks: [],
+        },
+      ],
+    };
+  }
+}
+
+/**
+ * 百以下数字转中文汉字
+ * @param {Number} num 大于0的100以下数字,其他数值直接转字符串
+ *
+ * @returns {String}
+ */
+export function numberToChinese(num) {
+  if (num >= 100 || num <= 0) return num + "";
+  const cnNums = "一二三四五六七八九十".split("");
+  if (num <= 10) return cnNums[num - 1];
+
+  return (num + "")
+    .split("")
+    .map((item) => item * 1)
+    .map((item, index) => {
+      if (index) {
+        return !item ? "" : cnNums[item - 1];
+      } else {
+        return item === 1 ? "" : cnNums[item - 1];
+      }
+    })
+    .join("十");
+}
+
+/**
+ *
+ * @param {Number} val 数值,1到26
+ */
+export function numberToUpperCase(val) {
+  if (val < 1 || val > 26) return;
+
+  return String.fromCharCode(64 + val);
+}

+ 19 - 0
src/styles/base.scss

@@ -951,3 +951,22 @@ body {
     }
   }
 }
+
+.preview-paper-dialog {
+  .el-dialog {
+    width: 1000px;
+  }
+  .el-dialog__body {
+    background-color: #f0f4f9;
+  }
+  .el-dialog__footer {
+    display: none;
+  }
+  .paper-content {
+    background-color: #fff;
+    margin: 10px auto;
+  }
+  .paper-content-long {
+    height: auto;
+  }
+}

+ 60 - 0
src/styles/paper-preview.css

@@ -0,0 +1,60 @@
+.paper-content {
+  width: 210mm;
+  height: 297mm;
+  padding: 40px;
+  margin: 0 auto;
+  overflow: hidden;
+}
+
+.cont-item {
+  page-break-after: always;
+}
+.cont-item p {
+  margin: 0;
+}
+.cont-paper-name {
+  font-size: 20px;
+  padding-bottom: 30px;
+  text-align: center;
+}
+.cont-detail-name {
+  font-size: 16px;
+  font-weight: 600;
+  padding: 15px 0;
+}
+.cont-topic-title {
+  padding-bottom: 5px;
+}
+.cont-item-detail {
+  min-height: 20px;
+}
+.cont-part-title {
+  font-size: 14px;
+  line-height: 20px;
+  float: left;
+  margin: 0;
+}
+.cont-part-body {
+  min-height: 20px;
+}
+.cont-part-body img {
+  max-width: 100%;
+  max-height: 1042px;
+}
+
+.cont-topic-answer .cont-part-title {
+  width: 36px;
+}
+.cont-topic-answer .cont-part-body {
+  padding-left: 36px;
+}
+.std-answer.cont-topic-answer .cont-part-title {
+  width: 72px;
+}
+.std-answer.cont-topic-answer .cont-part-body {
+  margin-left: 72px;
+}
+
+.cont-topic-answer + .cont-topic-title {
+  padding-top: 15px;
+}

+ 17 - 0
src/utils/utils.js

@@ -202,3 +202,20 @@ export async function getMd5FromBlob(blob) {
   const ab = await blobToArray(blob);
   return MD5(ab);
 }
+
+/**
+ * 获取随机code,默认获取16位
+ * @param {Number} len 推荐8的倍数
+ *
+ */
+export function randomCode(len = 16) {
+  if (len <= 0) return;
+  let steps = Math.ceil(len / 8);
+  let stepNums = [];
+  for (let i = 0; i < steps; i++) {
+    let ranNum = Math.random().toString(32).slice(-8);
+    stepNums.push(ranNum);
+  }
+
+  return stepNums.join("");
+}