123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 |
- <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"
- @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"
- @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" @click="toDownload">下载试卷</el-button>
- </div>
- <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 { 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 },
- 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: [],
- };
- },
- mounted() {
- if (this.viewType === "frame") {
- this.initFrame();
- return;
- }
- this.initData();
- },
- methods: {
- async initFrame() {
- try {
- const paperSet = window.parent.paperSet;
- if (!paperSet) {
- this.emitFrameResult(false, "数据缺失");
- return;
- }
- this.seqMode = paperSet.seqMode;
- this.curPaperTemp = paperSet.paperTemp;
- 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;
- 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.buildReleasePages();
- } catch (error) {
- this.emitFrameResult(false, "构建pdf错误");
- }
- this.$nextTick(() => {
- 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}(${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;
- },
- 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.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;
- }
- 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;
- this.parseRenderStructList();
- this.buildPrePages();
- const loadRes = await this.waitAllImgLoaded().catch(() => {});
- if (!loadRes) {
- this.$message.error("图片加载有误!");
- return;
- }
- this.$nextTick(() => {
- this.buildReleasePages();
- });
- },
- parseRenderStructList() {
- let renderStructList = [];
- 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)}、`,
- "option"
- );
- 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}、`
- );
- 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 && !oIndex) {
- // 针对如完形填空的小题做的特殊处理
- noVal = `${question.questionSeq}、${noVal}`;
- }
- const obodys = this.parseTitleOption(op.optionBody, noVal, "option");
- contents.push(...obodys);
- });
- }
- return contents;
- },
- parseDetailTitle(data) {
- let content = this.getRichStruct([
- {
- type: "text",
- value: `${data.cnNum}、${data.name}(共${data.unitCount}小题,满分${data.score}分)`,
- },
- ]);
- return getRichTextModel({
- styles: { width: "100%", fontWeight: 600 },
- content,
- });
- },
- parseTitleOption(richJson, noVal, contType = "content") {
- if (!richJson) return [];
- const bodys = this.transformRichJson(richJson);
- return bodys.map((body, index) => {
- if (index === 0 && noVal) {
- body.sections[0].blocks.unshift({
- type: "text",
- value: noVal,
- });
- }
- return getRichTextModel({
- contType,
- styles: { width: contType !== "option" ? "100%" : "auto" },
- content: body,
- });
- });
- },
- 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.resetRenderStructSize();
- // console.log(this.renderStructList);
- this.buildPageAutoPage();
- },
- 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 % 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;
- }
- 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 = [];
- }
- 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 =
- groups.length === 1
- ? curGroup[0].h
- : Math.max.apply(
- null,
- curGroup.map((item) => item.h)
- );
- if (curGroupHeigth + curColumnHeight > this.maxColumnHeight) {
- // 当前栏满了
- if (curColumnNo >= curPage.columnNumber) {
- // 当前页满了
- pages.push(curPage);
- curPage = null;
- curColumnNo = null;
- }
- 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 = width + "px";
- }
- });
- return previewTemp(this.$refs.PaperTemplateView.$el.outerHTML);
- },
- async toDownload() {
- 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;
- }
- </style>
|