|
@@ -0,0 +1,981 @@
|
|
|
+<template>
|
|
|
+ <div class="paper-template-build">
|
|
|
+ <div class="paper-template-build-body">
|
|
|
+ <div class="margin_top_10">
|
|
|
+ <el-select
|
|
|
+ v-model="seqMode"
|
|
|
+ class="margin-right-10"
|
|
|
+ size="small"
|
|
|
+ @change="seqModeChange"
|
|
|
+ >
|
|
|
+ <el-option value="MODE1" label="单题型连续"></el-option>
|
|
|
+ <el-option value="MODE2" label="客观题整体连续"></el-option>
|
|
|
+ <el-option value="MODE3" label="按大题独立"></el-option>
|
|
|
+ <el-option value="MODE5" label="整卷连续"></el-option>
|
|
|
+ </el-select>
|
|
|
+ <el-select
|
|
|
+ v-model="curPaperTemp"
|
|
|
+ class="margin-right-10"
|
|
|
+ placeholder="请选择"
|
|
|
+ value-key="id"
|
|
|
+ size="small"
|
|
|
+ @change="paperTempChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in paperTempList"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item"
|
|
|
+ >
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" size="small" @click="toDownload"
|
|
|
+ >下载试卷</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <paper-build-config
|
|
|
+ ref="PaperBuildConfig"
|
|
|
+ :config-sources="configSources"
|
|
|
+ @confirm="buildConfigChange"
|
|
|
+ ></paper-build-config>
|
|
|
+ <paper-template-view
|
|
|
+ ref="PaperTemplateView"
|
|
|
+ class="preview-body"
|
|
|
+ :pages="pages"
|
|
|
+ :page-config="paperTempJson.pageConfig"
|
|
|
+ ></paper-template-view>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import PaperTemplateView from "../components/PaperTemplateView.vue";
|
|
|
+import PaperBuildConfig from "../components/PaperBuildConfig.vue";
|
|
|
+import { getModel as getRichTextModel } from "../elements/rich-text/model";
|
|
|
+import { getModel as getPageModel } from "../elements/page/model";
|
|
|
+import { getElementId, randomCode, deepCopy } from "../../card/plugins/utils";
|
|
|
+import { calcSum, maxNum } from "@/plugins/utils";
|
|
|
+import previewTemp from "../previewTemp";
|
|
|
+import { paperDetailInfoApi } from "../../paper/api";
|
|
|
+import { paperTemplateListApi, paperPdfDownloadApi } from "../api";
|
|
|
+import { downloadByApi } from "@/plugins/download";
|
|
|
+// import paperJson from "./data/paper.json";
|
|
|
+// import paperTempJson from "./data/paper-temp.json";
|
|
|
+
|
|
|
+const numberToUpperCase = function (val) {
|
|
|
+ if (val < 1 || val > 26) return;
|
|
|
+
|
|
|
+ return String.fromCharCode(64 + val);
|
|
|
+};
|
|
|
+
|
|
|
+const checkRichTextHasCont = function (data) {
|
|
|
+ if (!data) return false;
|
|
|
+ if (!data.sections || !data.sections.length) return false;
|
|
|
+
|
|
|
+ if (!data.sections[0].blocks || !data.sections[0].blocks.length) return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "PaperTemplateBuild",
|
|
|
+ components: { PaperTemplateView, PaperBuildConfig },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ paperId: this.$route.params.paperId,
|
|
|
+ viewType: this.$route.params.viewType,
|
|
|
+ seqMode: "MODE1",
|
|
|
+ renderStructList: [],
|
|
|
+ pages: [],
|
|
|
+ paperJson: {},
|
|
|
+ paperTempJson: {
|
|
|
+ pages: [],
|
|
|
+ pageConfig: {},
|
|
|
+ },
|
|
|
+ maxColumnWidth: 200,
|
|
|
+ maxColumnHeight: 200,
|
|
|
+ paperTempList: [],
|
|
|
+ curPaperTemp: {},
|
|
|
+ downloading: false,
|
|
|
+ fieldData: {},
|
|
|
+ paperStructs: [],
|
|
|
+ TEXT_INDENT_SIZE: 28,
|
|
|
+ textIndent: 28,
|
|
|
+ configModalForm: {
|
|
|
+ showDetailNo: true,
|
|
|
+ showDetailScoreTable: false,
|
|
|
+ },
|
|
|
+ configSources: [],
|
|
|
+ prepareDownloadPdf: false,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ if (this.viewType === "frame") {
|
|
|
+ this.initFrame();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.initData();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getTextIndexStyle() {
|
|
|
+ return {
|
|
|
+ textIndent: `-${this.textIndent}px`,
|
|
|
+ paddingLeft: `${this.textIndent}px`,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ async initFrame() {
|
|
|
+ try {
|
|
|
+ const paperSet = window.parent.paperSet;
|
|
|
+ if (!paperSet) {
|
|
|
+ this.emitFrameResult(false, "数据缺失");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.seqMode = paperSet.seqMode;
|
|
|
+ this.curPaperTemp = paperSet.paperTemp;
|
|
|
+ this.configModalForm = paperSet.configModalForm;
|
|
|
+
|
|
|
+ await this.getPaperJson();
|
|
|
+
|
|
|
+ let paperTempJson = this.curPaperTemp.content
|
|
|
+ ? JSON.parse(this.curPaperTemp.content)
|
|
|
+ : { pages: [], pageConfig: {} };
|
|
|
+ this.paperTempJson = paperTempJson;
|
|
|
+ this.pages = paperTempJson.pages;
|
|
|
+ this.updaterFieldInfo();
|
|
|
+ } catch (error) {
|
|
|
+ this.emitFrameResult(false, "数据错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$nextTick(async () => {
|
|
|
+ try {
|
|
|
+ this.maxColumnWidth =
|
|
|
+ document.getElementById("column-0-0").offsetWidth;
|
|
|
+ this.maxColumnHeight =
|
|
|
+ document.getElementById("column-0-0").offsetHeight - 10;
|
|
|
+ this.parseRenderStructList();
|
|
|
+ this.buildPrePages();
|
|
|
+ } catch (error) {
|
|
|
+ this.emitFrameResult(false, "构建错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ const loadRes = await this.waitAllImgLoaded().catch(() => {});
|
|
|
+ if (!loadRes) {
|
|
|
+ this.emitFrameResult(false, "数据缓存错误");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.$nextTick(() => {
|
|
|
+ try {
|
|
|
+ this.addDetailScoreTable();
|
|
|
+ this.resetRenderStructSize();
|
|
|
+ this.buildPageAutoPage();
|
|
|
+ } catch (error) {
|
|
|
+ this.emitFrameResult(false, "构建pdf错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.addDetailScoreTable();
|
|
|
+ this.emitFrameResult(true, "", this.getPreviewTemp());
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ emitFrameResult(success = true, errorMsg = "", htmlCont = "") {
|
|
|
+ window.parent &&
|
|
|
+ window.parent.submitPaperTemp &&
|
|
|
+ window.parent.submitPaperTemp({
|
|
|
+ success,
|
|
|
+ errorMsg,
|
|
|
+ htmlCont,
|
|
|
+ templateId: this.curPaperTemp.id,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async initData() {
|
|
|
+ await this.getPaperJson();
|
|
|
+ await this.getPaperTempList();
|
|
|
+
|
|
|
+ if (!this.paperTempList.length) {
|
|
|
+ this.$message.error("导出模板缺失!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.paperTempChange(this.paperTempList[0]);
|
|
|
+
|
|
|
+ // test--->
|
|
|
+ // this.paperJson = paperJson;
|
|
|
+ // this.paperTempJson = paperTempJson;
|
|
|
+ // this.pages = paperTempJson.pages;
|
|
|
+ // this.$nextTick(() => {
|
|
|
+ // this.buildData();
|
|
|
+ // });
|
|
|
+ },
|
|
|
+ async getPaperJson() {
|
|
|
+ const res = await paperDetailInfoApi({
|
|
|
+ paperId: this.paperId,
|
|
|
+ seqMode: this.seqMode,
|
|
|
+ });
|
|
|
+ this.paperJson = res.data;
|
|
|
+ this.resetClozeSerialNo(this.paperJson);
|
|
|
+ this.fieldData = {
|
|
|
+ paperName: res.data.name,
|
|
|
+ courseName: res.data.course.name,
|
|
|
+ courseCode: res.data.course.code,
|
|
|
+ totalScore: res.data.totalScore,
|
|
|
+ rootOrgName: res.data.rootOrgName,
|
|
|
+ };
|
|
|
+ this.paperStructs = this.paperJson.paperDetails.map((detail) => {
|
|
|
+ return {
|
|
|
+ detailName: detail.name,
|
|
|
+ questionCount: detail.unitCount,
|
|
|
+ totalScore: detail.score,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ },
|
|
|
+ resetClozeSerialNo(paperData) {
|
|
|
+ const clozeQuestionTypes = ["CLOZE", "BANKED_CLOZE"];
|
|
|
+ paperData.paperDetails.forEach((detail) => {
|
|
|
+ detail.paperDetailUnits.forEach((question) => {
|
|
|
+ if (!clozeQuestionTypes.includes(question.questionType)) return;
|
|
|
+ question.question.quesBody.sections.forEach((section) => {
|
|
|
+ section.blocks.forEach((block) => {
|
|
|
+ if (block.type !== "cloze") return;
|
|
|
+ block.value =
|
|
|
+ question.question.subQuestions[block.value - 1].questionSeq;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async seqModeChange() {
|
|
|
+ await this.getPaperJson();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.buildData();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async getPaperTempList() {
|
|
|
+ const res = await paperTemplateListApi("PAPER_EXPORT");
|
|
|
+ this.paperTempList = res.data;
|
|
|
+ },
|
|
|
+ getConfigSources() {
|
|
|
+ const { pages } = this.paperTempJson;
|
|
|
+ let sources = [],
|
|
|
+ fieldAble = {};
|
|
|
+ pages.forEach((page) => {
|
|
|
+ page.columns.forEach((column) => {
|
|
|
+ column.elements.forEach((element) => {
|
|
|
+ if (element.type !== "PAPER_PROPS") return;
|
|
|
+ if (!sources.length) {
|
|
|
+ sources = deepCopy(element.props);
|
|
|
+ }
|
|
|
+ element.props.forEach((prop) => {
|
|
|
+ fieldAble[prop.field] = fieldAble[prop.field] || prop.enable;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ sources.forEach((item) => {
|
|
|
+ item.enable = fieldAble[item.field];
|
|
|
+ });
|
|
|
+ this.configSources = sources;
|
|
|
+ },
|
|
|
+ buildConfigChange(val) {
|
|
|
+ this.configModalForm = val;
|
|
|
+ this.updaterFieldInfo();
|
|
|
+
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.buildData();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ paperTempChange(paperTemp) {
|
|
|
+ // console.log(paperTemp);
|
|
|
+ this.curPaperTemp = paperTemp;
|
|
|
+ let paperTempJson = paperTemp.content
|
|
|
+ ? JSON.parse(paperTemp.content)
|
|
|
+ : { pages: [], pageConfig: {} };
|
|
|
+ this.paperTempJson = paperTempJson;
|
|
|
+ this.pages = paperTempJson.pages;
|
|
|
+ this.getConfigSources();
|
|
|
+ this.updaterFieldInfo();
|
|
|
+
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.buildData();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ updaterFieldInfo() {
|
|
|
+ const VALID_ELEMENTS_FOR_EXTERNAL = ["FIELD_TEXT"];
|
|
|
+ this.paperTempJson.pages.forEach((page) => {
|
|
|
+ page.columns.forEach((column) => {
|
|
|
+ column.elements.forEach((elem) => {
|
|
|
+ if (elem.type === "PAPER_STRUCT") {
|
|
|
+ elem.structs = this.paperStructs;
|
|
|
+ } else if (elem.type === "SCORE_TABLE") {
|
|
|
+ elem.detailCount = this.paperStructs.length;
|
|
|
+ } else if (elem.type === "PAPER_PROPS") {
|
|
|
+ elem.props.forEach((prop) => {
|
|
|
+ prop.value = this.configModalForm[prop.field];
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!elem.elements || !elem.elements.length) return;
|
|
|
+
|
|
|
+ elem.elements.forEach((element) => {
|
|
|
+ if (!VALID_ELEMENTS_FOR_EXTERNAL.includes(element.type)) return;
|
|
|
+
|
|
|
+ if (element.type === "FIELD_TEXT" && element.field) {
|
|
|
+ element.content = this.fieldData[element.field];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async buildData() {
|
|
|
+ this.maxColumnWidth = document.getElementById("column-0-0").offsetWidth;
|
|
|
+ this.maxColumnHeight =
|
|
|
+ document.getElementById("column-0-0").offsetHeight - 10;
|
|
|
+ this.parseRenderStructList();
|
|
|
+ this.buildPrePages();
|
|
|
+
|
|
|
+ const loadRes = await this.waitAllImgLoaded().catch(() => {});
|
|
|
+ if (!loadRes) {
|
|
|
+ this.$message.error("图片加载有误!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.buildReleasePages();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ parseRenderStructList() {
|
|
|
+ let renderStructList = [this.parseLineGap(), this.parseLineGap()];
|
|
|
+
|
|
|
+ this.paperJson.paperDetails.forEach((detail) => {
|
|
|
+ renderStructList.push(this.parseDetailTitle(detail));
|
|
|
+ if (checkRichTextHasCont(detail.description)) {
|
|
|
+ const descData = this.parseTitleOption(detail.description, "");
|
|
|
+ renderStructList.push(...descData);
|
|
|
+ }
|
|
|
+ detail.paperDetailUnits.forEach((question) => {
|
|
|
+ let questionInfo = question.question;
|
|
|
+ if (questionInfo.subQuestions && questionInfo.subQuestions.length) {
|
|
|
+ const bodys = this.parseTitleOption(questionInfo.quesBody, "");
|
|
|
+ renderStructList.push(...bodys);
|
|
|
+
|
|
|
+ const isMatches = this.checkIsMatches(questionInfo.questionType);
|
|
|
+ if (
|
|
|
+ isMatches &&
|
|
|
+ questionInfo.quesOptions &&
|
|
|
+ questionInfo.quesOptions.length
|
|
|
+ ) {
|
|
|
+ questionInfo.quesOptions.forEach((op) => {
|
|
|
+ const obodys = this.parseTitleOption(
|
|
|
+ op.optionBody,
|
|
|
+ `${numberToUpperCase(op.number)}、`,
|
|
|
+ true,
|
|
|
+ {
|
|
|
+ contType: "option",
|
|
|
+ textStyles: { paddingLeft: `${this.textIndent}px` },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ renderStructList.push(...obodys);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选词填空不展示小题
|
|
|
+ if (questionInfo.questionType === "BANKED_CLOZE") {
|
|
|
+ renderStructList.push(this.parseLineGap());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ questionInfo.subQuestions.forEach((sq, sqindex) => {
|
|
|
+ sq.subNumber = sqindex + 1;
|
|
|
+ if (isMatches) sq.quesOptions = []; // 选词填空、段落匹配小题中不展示选项
|
|
|
+ const contents = this.parseSimpleQuestion(sq, false);
|
|
|
+ renderStructList.push(...contents);
|
|
|
+ renderStructList.push(this.parseLineGap());
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ questionInfo.number = question.number;
|
|
|
+ const datas = this.parseSimpleQuestion(questionInfo, true);
|
|
|
+ renderStructList.push(...datas);
|
|
|
+ renderStructList.push(this.parseLineGap());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 去掉最后一题的间隔行
|
|
|
+ // console.log(renderStructList);
|
|
|
+ this.renderStructList = renderStructList.slice(0, -1);
|
|
|
+ },
|
|
|
+ getRichStruct(blocks) {
|
|
|
+ return {
|
|
|
+ sections: [
|
|
|
+ {
|
|
|
+ blocks: [...blocks],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ },
|
|
|
+ transformRichJson(richJson) {
|
|
|
+ if (!richJson || !richJson.sections) return [];
|
|
|
+ let contents = [];
|
|
|
+ let curBlock = [];
|
|
|
+ const checkNeedSplitSection = (block) => {
|
|
|
+ if (block.type !== "image") return false;
|
|
|
+
|
|
|
+ if (block.param) {
|
|
|
+ if (block.param.width)
|
|
|
+ return block.param.width > this.maxColumnWidth / 2;
|
|
|
+ if (block.param.height) return block.param.height > 150;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ richJson.sections.forEach((section) => {
|
|
|
+ section.blocks.forEach((block) => {
|
|
|
+ if (checkNeedSplitSection(block) && curBlock.length) {
|
|
|
+ contents.push(this.getRichStruct(curBlock));
|
|
|
+ curBlock = [];
|
|
|
+ }
|
|
|
+ curBlock.push(block);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (curBlock.length) {
|
|
|
+ contents.push(this.getRichStruct(curBlock));
|
|
|
+ curBlock = [];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return contents;
|
|
|
+ },
|
|
|
+ changeRichTextCloze(richText) {
|
|
|
+ return {
|
|
|
+ sections: richText.sections.map((section) => {
|
|
|
+ return {
|
|
|
+ blocks: section.blocks.map((item) => {
|
|
|
+ if (item.type === "cloze") {
|
|
|
+ return {
|
|
|
+ type: "text",
|
|
|
+ value: "_____",
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ return { ...item };
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ };
|
|
|
+ }),
|
|
|
+ };
|
|
|
+ },
|
|
|
+ parseSimpleQuestion(question) {
|
|
|
+ let contents = [];
|
|
|
+ let quesBody = question.quesBody;
|
|
|
+ if (question.questionType === "FILL_BLANK_QUESTION") {
|
|
|
+ quesBody = this.changeRichTextCloze(quesBody);
|
|
|
+ }
|
|
|
+
|
|
|
+ const tbodys = this.parseTitleOption(
|
|
|
+ quesBody,
|
|
|
+ `${question.questionSeq}、`,
|
|
|
+ true,
|
|
|
+ {
|
|
|
+ textStyles: { paddingLeft: `${this.textIndent}px` },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ contents.push(...tbodys);
|
|
|
+ const hasNobody = !tbodys.length;
|
|
|
+
|
|
|
+ if (question.quesOptions && question.quesOptions.length) {
|
|
|
+ question.quesOptions.forEach((op, oIndex) => {
|
|
|
+ let noVal = `${numberToUpperCase(op.number)}、`;
|
|
|
+ if (!hasNobody) {
|
|
|
+ const obodys = this.parseTitleOption(op.optionBody, noVal, false, {
|
|
|
+ contType: "option",
|
|
|
+ textStyles: { paddingLeft: `${this.textIndent}px` },
|
|
|
+ });
|
|
|
+ contents.push(...obodys);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!oIndex) {
|
|
|
+ // 针对如完形填空的小题做的特殊处理
|
|
|
+ noVal = `${question.questionSeq}、${noVal}`;
|
|
|
+ }
|
|
|
+ this.textIndent = 2 * this.TEXT_INDENT_SIZE;
|
|
|
+ const obodys = this.parseTitleOption(op.optionBody, noVal, true, {
|
|
|
+ contType: "option",
|
|
|
+ textStyles: { paddingLeft: `${this.textIndent}px` },
|
|
|
+ });
|
|
|
+ contents.push(...obodys);
|
|
|
+ this.textIndent = this.TEXT_INDENT_SIZE;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return contents;
|
|
|
+ },
|
|
|
+ parseDetailTitle(data) {
|
|
|
+ let blocks = [
|
|
|
+ {
|
|
|
+ type: "text",
|
|
|
+ value: `${data.name}(共${data.unitCount}小题,满分${data.score}分)`,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ if (this.configModalForm.showDetailNo) {
|
|
|
+ blocks.unshift({
|
|
|
+ type: "text",
|
|
|
+ value: `${data.cnNum}、`,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ let content = this.getRichStruct(blocks);
|
|
|
+ return getRichTextModel({
|
|
|
+ styles: { width: "100%", fontWeight: 900 },
|
|
|
+ content,
|
|
|
+ classNames: this.configModalForm.showDetailScoreTable
|
|
|
+ ? "is-detail-title"
|
|
|
+ : "",
|
|
|
+ });
|
|
|
+ },
|
|
|
+ parseTitleOption(
|
|
|
+ richJson,
|
|
|
+ noVal,
|
|
|
+ needIndent = false,
|
|
|
+ modelData = { textStyles: null, styles: null, contType: "" }
|
|
|
+ ) {
|
|
|
+ if (!richJson) return [];
|
|
|
+ const { textStyles, styles, contType } = modelData;
|
|
|
+ const bodys = this.transformRichJson(richJson);
|
|
|
+
|
|
|
+ return bodys.map((body, index) => {
|
|
|
+ let presetData = {
|
|
|
+ content: body,
|
|
|
+ };
|
|
|
+ if (contType) presetData.contType = contType;
|
|
|
+ if (textStyles) presetData.textStyles = textStyles;
|
|
|
+ if (styles) {
|
|
|
+ presetData.styles = styles;
|
|
|
+ } else {
|
|
|
+ presetData.styles = {
|
|
|
+ width: contType === "option" ? "auto" : "100%",
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (index === 0 && noVal) {
|
|
|
+ let cont = {
|
|
|
+ type: "text",
|
|
|
+ value: noVal,
|
|
|
+ };
|
|
|
+ if (needIndent) {
|
|
|
+ cont.param = {
|
|
|
+ width: this.textIndent,
|
|
|
+ };
|
|
|
+ presetData.textStyles = {
|
|
|
+ ...(presetData.textStyles || {}),
|
|
|
+ ...this.getTextIndexStyle(),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ body.sections[0].blocks.unshift(cont);
|
|
|
+ }
|
|
|
+
|
|
|
+ return getRichTextModel(presetData);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ parseLineGap() {
|
|
|
+ return getRichTextModel({
|
|
|
+ contType: "gap",
|
|
|
+ styles: { width: "100%", height: "10px" },
|
|
|
+ content: this.getRichStruct([{ type: "text", value: "" }]),
|
|
|
+ });
|
|
|
+ },
|
|
|
+ checkIsMatches(structType) {
|
|
|
+ const matchesTypes = ["BANKED_CLOZE", "PARAGRAPH_MATCHING"];
|
|
|
+ return matchesTypes.includes(structType);
|
|
|
+ },
|
|
|
+ buildPrePages() {
|
|
|
+ let pages = deepCopy(this.paperTempJson.pages);
|
|
|
+ const firstPageNo = pages.findIndex((p) => p.pageType !== "cover");
|
|
|
+ pages[firstPageNo].columns[0].texts = [];
|
|
|
+ pages[firstPageNo].columns[0].texts.push(...this.renderStructList);
|
|
|
+ this.pages = pages;
|
|
|
+ },
|
|
|
+ buildReleasePages() {
|
|
|
+ this.addDetailScoreTable();
|
|
|
+ this.resetRenderStructSize();
|
|
|
+ // console.log(this.renderStructList);
|
|
|
+ this.buildPageAutoPage();
|
|
|
+
|
|
|
+ this.$nextTick(async () => {
|
|
|
+ this.addDetailScoreTable();
|
|
|
+
|
|
|
+ if (this.prepareDownloadPdf) {
|
|
|
+ await this.downloadPaperPdf().catch(() => {});
|
|
|
+ this.prepareDownloadPdf = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ addDetailScoreTable() {
|
|
|
+ if (!this.configModalForm.showDetailScoreTable) return;
|
|
|
+
|
|
|
+ const scoreTableHtml = `<table class="detail-score-table"><tr><th>得分</th><th>评分人</th></tr><tr><td></td><td></td></tr></table>`;
|
|
|
+ const dom = document.createElement("div");
|
|
|
+ dom.innerHTML = scoreTableHtml;
|
|
|
+ document.querySelectorAll(".is-detail-title").forEach((node) => {
|
|
|
+ const hasScoreTable =
|
|
|
+ node.firstChild.className.includes("detail-score-table");
|
|
|
+ if (hasScoreTable) return;
|
|
|
+ node.insertBefore(dom.firstChild.cloneNode(true), node.firstChild);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ resetRenderStructSize() {
|
|
|
+ let curOptions = [];
|
|
|
+ this.renderStructList.forEach((elem, eindex) => {
|
|
|
+ const elemDom = document.getElementById(`rich-text-${elem.id}`);
|
|
|
+ elem.w = elemDom.offsetWidth;
|
|
|
+ elem.h = elemDom.offsetHeight;
|
|
|
+
|
|
|
+ if (elem.contType !== "option") return;
|
|
|
+
|
|
|
+ curOptions.push(elem);
|
|
|
+
|
|
|
+ // 全选选项逻辑
|
|
|
+ const nextElem = this.renderStructList[eindex + 1];
|
|
|
+ if (nextElem && nextElem.contType === "option") return;
|
|
|
+
|
|
|
+ curOptions.forEach((optionElem) => {
|
|
|
+ optionElem._percent = this.getSizePercent(
|
|
|
+ optionElem.w,
|
|
|
+ this.maxColumnWidth
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ const optionCount = curOptions.length;
|
|
|
+ // 奇数选项,全部一行
|
|
|
+ if (optionCount <= 7 && optionCount % 2 > 0) {
|
|
|
+ curOptions.forEach((optionElem) => {
|
|
|
+ optionElem._percent = 1;
|
|
|
+ optionElem.styles.width = "100%";
|
|
|
+ });
|
|
|
+ curOptions = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const percents = curOptions.map((item) => item._percent);
|
|
|
+ const maxPercent = maxNum(percents);
|
|
|
+ // let aveOptionPercent = 1;
|
|
|
+ // if (optionCount % 4 === 0) {
|
|
|
+ // aveOptionPercent = this.calcAveOptionPercent(maxPercent);
|
|
|
+ // } else {
|
|
|
+ // aveOptionPercent = maxPercent > 0.5 ? 1 : 0.5;
|
|
|
+ // }
|
|
|
+ const aveOptionPercent = this.calcAveOptionPercent(maxPercent);
|
|
|
+
|
|
|
+ curOptions.forEach((optionElem) => {
|
|
|
+ optionElem._percent = aveOptionPercent;
|
|
|
+ optionElem.styles.width = aveOptionPercent * 100 + "%";
|
|
|
+ });
|
|
|
+
|
|
|
+ curOptions = [];
|
|
|
+ });
|
|
|
+ this.renderStructList.forEach((elem) => {
|
|
|
+ if (elem.styles.width === "100%") {
|
|
|
+ this.$set(elem.styles, "display", "block");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ buildPageAutoPage() {
|
|
|
+ let pages = [];
|
|
|
+ let curPage = null,
|
|
|
+ curElem = null;
|
|
|
+ let curColumn = null,
|
|
|
+ curColumnNo = 0,
|
|
|
+ curColumnHeight = 0;
|
|
|
+ let curLinePercent = 0;
|
|
|
+ let groups = [],
|
|
|
+ curGroup = [];
|
|
|
+
|
|
|
+ // 分组自动分页 选项分组
|
|
|
+ const getNextElem = () => {
|
|
|
+ return this.renderStructList.shift();
|
|
|
+ };
|
|
|
+
|
|
|
+ curElem = getNextElem();
|
|
|
+ while (curElem) {
|
|
|
+ if (
|
|
|
+ curElem.contType !== "option" ||
|
|
|
+ (curElem.contType === "option" && curElem._percent === 1)
|
|
|
+ ) {
|
|
|
+ if (curGroup.length) {
|
|
|
+ groups.push(curGroup);
|
|
|
+ curGroup = [];
|
|
|
+ curLinePercent = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ groups.push([curElem]);
|
|
|
+ curElem = getNextElem();
|
|
|
+ } else {
|
|
|
+ if (curLinePercent + curElem._percent > 1) {
|
|
|
+ groups.push(curGroup);
|
|
|
+ curGroup = [];
|
|
|
+ curLinePercent = 0;
|
|
|
+ } else {
|
|
|
+ curGroup.push(curElem);
|
|
|
+ curLinePercent += curElem._percent;
|
|
|
+ curElem = getNextElem();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (curGroup.length) {
|
|
|
+ groups.push(curGroup);
|
|
|
+ curGroup = [];
|
|
|
+ }
|
|
|
+ // console.log(groups);
|
|
|
+
|
|
|
+ const getNextGroup = () => {
|
|
|
+ return groups.shift();
|
|
|
+ };
|
|
|
+ curGroup = getNextGroup();
|
|
|
+ while (curGroup) {
|
|
|
+ if (!curPage) {
|
|
|
+ curPage = this.getNewPageModel(pages.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!curColumn) {
|
|
|
+ curColumn = curPage.columns[curColumnNo++];
|
|
|
+ curColumnHeight = this.calcInitColumnHeight(curColumn);
|
|
|
+ }
|
|
|
+
|
|
|
+ let curGroupHeigth =
|
|
|
+ curGroup.length === 1
|
|
|
+ ? curGroup[0].h
|
|
|
+ : Math.max.apply(
|
|
|
+ null,
|
|
|
+ curGroup.map((item) => item.h)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (curGroupHeigth + curColumnHeight > this.maxColumnHeight) {
|
|
|
+ // 当前栏第一个元素就超过最大高度时,直接放当前栏
|
|
|
+ if (!curColumn.texts.length) {
|
|
|
+ curColumn.texts.push(...curGroup);
|
|
|
+ curGroup = getNextGroup();
|
|
|
+ }
|
|
|
+ // 当前栏满了
|
|
|
+ if (curColumnNo >= curPage.columnNumber) {
|
|
|
+ // 当前页满了
|
|
|
+ pages.push(curPage);
|
|
|
+ curPage = null;
|
|
|
+ curColumnNo = 0;
|
|
|
+ }
|
|
|
+ curColumn = null;
|
|
|
+ curColumnHeight = 0;
|
|
|
+ } else {
|
|
|
+ // 当前栏未满
|
|
|
+ curColumnHeight += curGroupHeigth;
|
|
|
+ curColumn.texts.push(...curGroup);
|
|
|
+ curGroup = getNextGroup();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (curPage) {
|
|
|
+ pages.push(curPage);
|
|
|
+ curPage = null;
|
|
|
+ }
|
|
|
+ // 正文部分保证偶数页
|
|
|
+ if (pages.length % 2) {
|
|
|
+ pages.push(this.getNewPageModel(pages.length));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.paperTempJson.pageConfig.showCover) {
|
|
|
+ // 封面自动插入反面空白页
|
|
|
+ let coverPages = deepCopy(
|
|
|
+ this.paperTempJson.pages.filter((p) => p.pageType === "cover")
|
|
|
+ );
|
|
|
+ let coverBackPage = deepCopy(coverPages[0]);
|
|
|
+ coverBackPage.columns.forEach((column) => {
|
|
|
+ column.elements = [];
|
|
|
+ });
|
|
|
+ let nCoverPages = [];
|
|
|
+ coverPages.forEach((cpage) => {
|
|
|
+ nCoverPages.push(cpage);
|
|
|
+ nCoverPages.push(coverBackPage);
|
|
|
+ });
|
|
|
+ pages = [...nCoverPages, ...pages];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.pages = pages;
|
|
|
+ },
|
|
|
+ getNewPageModel(pageNo) {
|
|
|
+ let contentPages = this.paperTempJson.pages.slice(-2);
|
|
|
+ let pNo = pageNo % 2;
|
|
|
+ const pageTemp = contentPages[pNo];
|
|
|
+ let newPage = getPageModel({
|
|
|
+ ...this.paperTempJson.pageConfig,
|
|
|
+ pageType: pageTemp.pageType,
|
|
|
+ });
|
|
|
+ newPage.sides = pageTemp.sides.map((elem) => {
|
|
|
+ let nelem = deepCopy(elem);
|
|
|
+ nelem.id = getElementId();
|
|
|
+ nelem.key = randomCode();
|
|
|
+
|
|
|
+ // if (pNo === 1 && nelem.type === "GUTTER") {
|
|
|
+ // nelem.direction = "right";
|
|
|
+ // }
|
|
|
+ return nelem;
|
|
|
+ });
|
|
|
+ newPage.columns.forEach((column) => {
|
|
|
+ column.texts = [];
|
|
|
+ });
|
|
|
+
|
|
|
+ if (pageNo > 1) return newPage;
|
|
|
+
|
|
|
+ newPage.columns.forEach((column, cindex) => {
|
|
|
+ column.elements = pageTemp.columns[cindex].elements.map((elem) => {
|
|
|
+ let nelem = deepCopy(elem);
|
|
|
+ nelem.id = getElementId();
|
|
|
+ nelem.key = randomCode();
|
|
|
+ nelem.h = this.getElementHeight(`preview-${elem.id}`);
|
|
|
+ if (nelem.elements && nelem.elements.length) {
|
|
|
+ nelem.elements.forEach((celem) => {
|
|
|
+ celem.id = getElementId();
|
|
|
+ celem.key = randomCode();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return nelem;
|
|
|
+ });
|
|
|
+ column.texts = [];
|
|
|
+ });
|
|
|
+ return newPage;
|
|
|
+ },
|
|
|
+ getElementHeight(elementId) {
|
|
|
+ const dom = document.getElementById(elementId);
|
|
|
+ return dom ? dom.offsetHeight : 0;
|
|
|
+ },
|
|
|
+ calcAveOptionPercent(maxPercent) {
|
|
|
+ if (maxPercent > 0.5) return 1;
|
|
|
+ if (maxPercent > 0.25) return 0.5;
|
|
|
+ return 0.25;
|
|
|
+ },
|
|
|
+ calcInitColumnHeight(column) {
|
|
|
+ return calcSum(column.elements.map((item) => item.h));
|
|
|
+ },
|
|
|
+ getSizePercent(size, fullSize) {
|
|
|
+ const rate = size / fullSize;
|
|
|
+ if (rate <= 0.25) return 0.25;
|
|
|
+ if (rate <= 0.5) return 0.5;
|
|
|
+ return 1;
|
|
|
+ },
|
|
|
+ // img
|
|
|
+ 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 = [];
|
|
|
+ if (!richJson) return urls;
|
|
|
+ richJson.sections.forEach((section) => {
|
|
|
+ section.blocks.forEach((elem) => {
|
|
|
+ if (elem.type === "image" && elem.value.startsWith("http")) {
|
|
|
+ urls.push(elem.value);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return urls;
|
|
|
+ },
|
|
|
+ async waitAllImgLoaded() {
|
|
|
+ let imgUrls = [];
|
|
|
+ this.renderStructList.forEach((item) => {
|
|
|
+ if (item.contType === "gap") return;
|
|
|
+ imgUrls.push(...this.getRichJsonImgUrls(item.content));
|
|
|
+ });
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getPreviewTemp() {
|
|
|
+ const elementDoms =
|
|
|
+ this.$refs.PaperTemplateView.$el.querySelectorAll(".elem-rich-text");
|
|
|
+ elementDoms.forEach((eDom) => {
|
|
|
+ const width = eDom.offsetWidth;
|
|
|
+ if (eDom.firstChild && eDom.firstChild.nodeName === "DIV") {
|
|
|
+ eDom.firstChild.style.width = width + "px";
|
|
|
+ if (
|
|
|
+ eDom.firstChild.firstChild &&
|
|
|
+ eDom.firstChild.firstChild.nodeName === "DIV"
|
|
|
+ )
|
|
|
+ eDom.firstChild.firstChild.style.width = "100%";
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return previewTemp(this.$refs.PaperTemplateView.$el.outerHTML);
|
|
|
+ },
|
|
|
+ async toDownload() {
|
|
|
+ const valid = await this.$refs.PaperBuildConfig.checkData().catch(
|
|
|
+ () => {}
|
|
|
+ );
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ const configData = this.$refs.PaperBuildConfig.getData();
|
|
|
+ if (JSON.stringify(configData) === JSON.stringify(this.configModalForm)) {
|
|
|
+ this.downloadPaperPdf();
|
|
|
+ } else {
|
|
|
+ this.prepareDownloadPdf = true;
|
|
|
+ this.buildConfigChange(configData);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async downloadPaperPdf() {
|
|
|
+ const htmlCont = this.getPreviewTemp();
|
|
|
+
|
|
|
+ if (this.downloading) return;
|
|
|
+ this.downloading = true;
|
|
|
+
|
|
|
+ const res = await downloadByApi(() => {
|
|
|
+ return paperPdfDownloadApi({
|
|
|
+ content: htmlCont,
|
|
|
+ templateId: this.curPaperTemp.id,
|
|
|
+ paperId: this.paperId,
|
|
|
+ });
|
|
|
+ }).catch((e) => {
|
|
|
+ this.$message.error(e || "下载失败,请重新尝试!");
|
|
|
+ });
|
|
|
+ this.downloading = false;
|
|
|
+
|
|
|
+ if (!res) return;
|
|
|
+ this.$message.success("下载成功!");
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+.paper-template-build {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.paper-template-build-body {
|
|
|
+ display: inline-block;
|
|
|
+ text-align: initial;
|
|
|
+}
|
|
|
+
|
|
|
+.paper-template-build .page-box {
|
|
|
+ margin-top: 10px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.paper-template-build .paper-build-config {
|
|
|
+ padding: 10px 15px 2px;
|
|
|
+ margin-top: 5px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+}
|
|
|
+.paper-build-config .el-form-item {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ margin-right: 30px;
|
|
|
+}
|
|
|
+</style>
|