Pārlūkot izejas kodu

题库试卷自动生成题卡

zhangjie 1 gadu atpakaļ
vecāks
revīzija
d501e3b7d8

+ 275 - 0
src/modules/card/autoBuild/paperCard.js

@@ -0,0 +1,275 @@
+import { numberToChinese } from "@/plugins/utils";
+import {
+  getModel as createFillQuestion,
+  getFullModel as getFillQuesitonElements,
+} from "../../../../card/elements/fill-question/model";
+import {
+  getModel as createFillLine,
+  getFullModel as getFillLineElements,
+} from "../../../../card/elements/fill-line/model";
+import {
+  getModel as createExplain,
+  getFullModel as getExplainElements,
+} from "../../../../card/elements/explain/model";
+
+const COMMON_QUESTION_TYPES = [
+  "SINGLE_ANSWER_QUESTION",
+  "MULTIPLE_ANSWER_QUESTION",
+  "BOOL_ANSWER_QUESTION",
+  "FILL_BLANK_QUESTION",
+  "TEXT_ANSWER_QUESTION",
+];
+
+const structTypeToCardElementType = {
+  SINGLE_ANSWER_QUESTION: "FILL_QUESTION",
+  MULTIPLE_ANSWER_QUESTION: "FILL_QUESTION",
+  BOOL_ANSWER_QUESTION: "FILL_QUESTION",
+  FILL_BLANK_QUESTION: "FILL_LINE",
+  TEXT_ANSWER_QUESTION: "EXPLAIN",
+};
+
+const elementModel = {
+  FILL_QUESTION: (presetData) => {
+    const model = createFillQuestion(presetData);
+    return getFillQuesitonElements(model, {
+      pageSize: presetData.pageSize,
+      columnNumber: presetData.columnNumber,
+    });
+  },
+  FILL_LINE: (presetData) => {
+    const model = createFillLine(presetData);
+    return getFillLineElements(model);
+  },
+  EXPLAIN: (presetData) => {
+    const model = createExplain(presetData);
+    return getExplainElements(model);
+  },
+};
+
+function parsePaperStruct(paperJson) {
+  let structList = [];
+  paperJson.paperDetails.forEach((detail) => {
+    const commonQuestions = detail.paperDetailUnits.filter((q) =>
+      COMMON_QUESTION_TYPES.includes(q.questionType)
+    );
+    if (commonQuestions.length) {
+      const commonStructList = getCommonQuestionStructList(
+        detail,
+        commonQuestions,
+        true,
+        0
+      );
+      structList.push(...commonStructList);
+    }
+
+    const nestedQuestions = detail.paperDetailUnits.filter(
+      (q) => !COMMON_QUESTION_TYPES.includes(q.questionType)
+    );
+    if (nestedQuestions.length) {
+      const nestedStructList = getNestedQuestionStructList(
+        detail,
+        nestedQuestions
+      );
+      structList.push(...nestedStructList);
+    }
+  });
+
+  structList.sort((a, b) => a.sortNo - b.sortNo);
+  console.log(structList);
+  return structList;
+}
+
+function getCommonQuestionStructList(
+  detail,
+  questions,
+  isCommon,
+  nestedQNo = 0
+) {
+  let structList = [];
+  let structs = {};
+  const qTypeNum = isCommon ? 1 : 2;
+  questions.forEach((question) => {
+    if (!structs[question.questionType]) structs[question.questionType] = [];
+    structs[question.questionType].push(question);
+  });
+  Object.keys(structs).forEach((structType) => {
+    const typeNo = COMMON_QUESTION_TYPES.indexOf(structType) + 1;
+    const questions = parseCommonTypeQuestions(structType, structs[structType]);
+
+    structList.push({
+      detailName: detail.name,
+      detailNo: detail.number,
+      structType,
+      isCommon,
+      nestedQNo,
+      sortNo:
+        typeNo * 10000 + detail.number * 1000 + qTypeNum * 100 + nestedQNo,
+      questions,
+    });
+  });
+  return structList;
+}
+
+function getNestedQuestionStructList(detail, questions) {
+  let structList = [];
+  questions.forEach((question) => {
+    const qStructList = getCommonQuestionStructList(
+      detail,
+      question.question.subQuestions.map((q) => {
+        return {
+          number: q.subNumber,
+          questionType: q.questionType,
+          question: q,
+        };
+      }),
+      false,
+      question.number
+    );
+    structList.push(...qStructList);
+  });
+  return structList;
+}
+
+function parseCommonTypeQuestions(structType, dataList) {
+  const choiceQs = [
+    "SINGLE_ANSWER_QUESTION",
+    "MULTIPLE_ANSWER_QUESTION",
+    "BOOL_ANSWER_QUESTION",
+  ];
+  let questions = [];
+  if (choiceQs.includes(structType)) {
+    questions = dataList.map((item) => {
+      return {
+        questionNo: item.number,
+        optionCount:
+          structType === "BOOL_ANSWER_QUESTION"
+            ? 2
+            : item.question.quesOptions.length,
+      };
+    });
+  } else if (structType === "FILL_BLANK_QUESTION") {
+    questions = dataList.map((item) => {
+      return {
+        questionNo: item.number,
+        fillCount: getFillBackClozeCount(item.question.quesBody),
+      };
+    });
+  } else {
+    questions = dataList.map((item) => item.number);
+  }
+  return questions;
+}
+
+export function getFillBackClozeCount(questionBodyContent) {
+  let questionBody = null;
+  let num = 0;
+  try {
+    questionBody = JSON.parse(questionBodyContent);
+    questionBody.sections.forEach((section) => {
+      section.blocks.forEach((block) => {
+        if (block.type === "cloze") num++;
+      });
+    });
+  } catch (error) {
+    num = 1;
+  }
+
+  return num;
+}
+
+export function buildCardElements(structList, elementTypePreSetInfo = {}) {
+  let cardElements = [];
+  structList.forEach((struct) => {
+    const detailChineseNumber = numberToChinese(struct.detailNo);
+    let topicName =
+      struct.isCommon || (!struct.isCommon && struct.isOnlyOneNested)
+        ? `${detailChineseNumber}、${struct.detailName}`
+        : `${detailChineseNumber}(${struct.nestedQNo})`;
+    const modelType = structTypeToCardElementType[struct.structType];
+    const elementTypePreSet = elementTypePreSetInfo[modelType] || {};
+
+    if (!struct.isCommon && struct.structType === "TEXT_ANSWER_QUESTION") {
+      struct.questions.forEach((qno) => {
+        const presetData = {
+          ...elementTypePreSet,
+          topicName: `${topicName}、${qno}`,
+          paperStruct: { ...struct, questions: [qno] },
+        };
+
+        const elements = elementModel[modelType](presetData);
+        cardElements.push(...elements);
+      });
+      return;
+    }
+
+    let presetData = {
+      ...elementTypePreSet,
+      topicName,
+      paperStruct: struct,
+    };
+    if (struct.structType === "MULTIPLE_ANSWER_QUESTION")
+      presetData.isMultiply = true;
+    if (struct.structType === "BOOL_ANSWER_QUESTION")
+      presetData.isBoolean = true;
+
+    const elements = elementModel[modelType](presetData);
+    cardElements.push(...elements);
+  });
+
+  return cardElements;
+}
+
+export function buildPaperCard(paperJson) {
+  const structList = parsePaperStruct(paperJson);
+  const cardElements = buildCardElements(structList);
+  // console.log(cardElements);
+  return cardElements;
+}
+
+// const paperStruct = {
+//   paperName: "",
+//   structList: [
+//     {
+//       detailName: "",
+//       detailNo: 1,
+//       structType: 1,
+//       questions: [
+//         { questionNo: "1", optionCount: 4 },
+//         { questionNo: "2", optionCount: 4 },
+//       ],
+//     },
+//     {
+//       detailName: "",
+//       detailNo: 1,
+//       structType: 2,
+//       questions: [
+//         { questionNo: "3", optionCount: 4 },
+//         { questionNo: "4", optionCount: 4 },
+//       ],
+//     },
+//     {
+//       detailName: "",
+//       detailNo: 1,
+//       structType: 3,
+//       questions: [
+//         { questionNo: "5", optionCount: 2 },
+//         { questionNo: "6", optionCount: 2 },
+//       ],
+//     },
+//     {
+//       detailName: "",
+//       detailNo: 1,
+//       structType: 4,
+//       questions: [
+//         { questionNo: "7", fillCount: 1 },
+//         { questionNo: "8", fillCount: 2 },
+//       ],
+//     },
+//     {
+//       detailName: "",
+//       detailNo: 1,
+//       structType: 5,
+//       questions: ["11", "12"],
+//     },
+//   ],
+// };

+ 85 - 0
src/modules/card/autoBuild/paperStruct.js

@@ -0,0 +1,85 @@
+import { getFillBackClozeCount } from "./paperCard";
+
+const COMMON_QUESTION_TYPES = [
+  "SINGLE_ANSWER_QUESTION",
+  "MULTIPLE_ANSWER_QUESTION",
+  "BOOL_ANSWER_QUESTION",
+  "FILL_BLANK_QUESTION",
+  "TEXT_ANSWER_QUESTION",
+];
+// paper json
+export function getPaperJsonSimpleStructInfo(paperJson) {
+  let struct = [];
+  paperJson.paperDetails.forEach((detail) => {
+    struct.push(`detail:${detail.number}-${detail.name}`);
+    detail.paperDetailUnits.forEach((question) => {
+      if (COMMON_QUESTION_TYPES.includes(question.questionType)) {
+        question.question.number = question.number;
+        const info = parseCommonTypeQuestion(question.question);
+        struct.push(`${detail.number}-${info}`);
+      } else {
+        let infos = parseNestedTypeQuestion(question);
+        infos.forEach((info) => {
+          struct.push(`${detail.number}-${info}`);
+        });
+      }
+    });
+  });
+  return struct.join("#");
+}
+
+function parseCommonTypeQuestion(question) {
+  const structType = question.questionType;
+  let info = `${question.number}:${structType}`;
+  const choiceQs = ["SINGLE_ANSWER_QUESTION", "MULTIPLE_ANSWER_QUESTION"];
+  if (choiceQs.includes(structType)) {
+    info += `:${question.quesOptions.length}`;
+  } else if (structType === "FILL_BLANK_QUESTION") {
+    info += `:${getFillBackClozeCount(question.quesOptions)}`;
+  } else {
+    info += ":";
+  }
+
+  return info;
+}
+
+function parseNestedTypeQuestion(question) {
+  let struct = [];
+  let qinfo = `${question.number}`;
+  question.question.subQuestions.forEach((subq, sindex) => {
+    subq.number = sindex + 1;
+    const info = parseCommonTypeQuestion(subq);
+    struct.push(`${qinfo}-${info}`);
+  });
+  return struct;
+}
+
+// paper struct
+export function getPaperStructSimpleStructInfo(paperStruct) {
+  let struct = [];
+  paperStruct.details.forEach((detail) => {
+    struct.push(`detail:${detail.number}-${detail.name}`);
+    detail.units.forEach((question) => {
+      if (COMMON_QUESTION_TYPES.includes(question.type)) {
+        const info = parseStructCommonTypeQuestion(question);
+        struct.push(`${detail.number}-${info}`);
+      }
+    });
+  });
+  return struct.join("#");
+}
+
+function parseStructCommonTypeQuestion(question) {
+  const structType = question.type;
+  let info = `${question.number}:${structType}`;
+  const choiceQs = ["SINGLE_ANSWER_QUESTION", "MULTIPLE_ANSWER_QUESTION"];
+  if (choiceQs.includes(structType)) {
+    info += `:4`;
+  } else if (structType === "FILL_BLANK_QUESTION") {
+    info += `:1`;
+  } else {
+    info += ":";
+  }
+
+  return info;
+}

+ 184 - 0
src/modules/card/autoBuild/simplePaperCard.js

@@ -0,0 +1,184 @@
+import { buildCardElements } from "./paperCard";
+
+const COMMON_QUESTION_TYPES = [
+  "SINGLE_ANSWER_QUESTION",
+  "MULTIPLE_ANSWER_QUESTION",
+  "BOOL_ANSWER_QUESTION",
+  "FILL_BLANK_QUESTION",
+  "TEXT_ANSWER_QUESTION",
+];
+
+function parseSimpleQuestion(simpleQuestion) {
+  const [numbers, questionType, qinfo] = simpleQuestion.split(":");
+  const [detailNo, questionNo, subQno] = numbers.split("-");
+  return {
+    detailNo: detailNo * 1,
+    questionNo: questionNo * 1,
+    subQno: subQno ? subQno * 1 : null,
+    questionType,
+    qinfo: qinfo * 1,
+  };
+}
+
+function checkDetailIsOnlyOneNested(detail) {
+  const firstQuestionNo = detail.questions[0].questionNo;
+  return !detail.questions.some((q) => q.questionNo !== firstQuestionNo);
+}
+
+function parsePaperStruct(paperSimpleStruct) {
+  // console.log(paperSimpleStruct);
+  const dataList = paperSimpleStruct.split("#");
+  const details = dataList.filter((item) => item.startsWith("detail"));
+  let detailNames = {};
+  details.forEach((detail) => {
+    const pos = detail.indexOf("-");
+    const detailNo = detail.substring(0, pos).replace("detail:", "") * 1;
+    detailNames[detailNo] = detail.substring(pos + 1);
+  });
+  const questions = dataList
+    .filter((item) => !item.startsWith("detail"))
+    .map((q) => parseSimpleQuestion(q));
+
+  // parse paper struct
+  let detailList = [];
+  questions.forEach((q) => {
+    const dind = q.detailNo - 1;
+    if (!detailList[dind]) {
+      detailList[dind] = {
+        detailNo: q.detailNo,
+        detailName: detailNames[q.detailNo],
+        questions: [],
+      };
+    }
+    detailList[dind].questions.push(q);
+  });
+
+  let structList = [];
+  detailList.forEach((detail) => {
+    detail.isOnlyOneNested = checkDetailIsOnlyOneNested(detail);
+    const commonQuestions = detail.questions.filter(
+      (q) => COMMON_QUESTION_TYPES.includes(q.questionType) && q.subQno === null
+    );
+    if (commonQuestions.length) {
+      const commonStructList = getCommonQuestionStructList(
+        detail,
+        commonQuestions,
+        true,
+        0
+      );
+      structList.push(...commonStructList);
+    }
+
+    const nestedQuestions = detail.questions.filter((q) => q.subQno !== null);
+    if (nestedQuestions.length) {
+      const nestedStructList = getNestedQuestionStructList(
+        detail,
+        nestedQuestions
+      );
+      structList.push(...nestedStructList);
+    }
+  });
+
+  structList.sort((a, b) => a.sortNo - b.sortNo);
+  // console.log(structList);
+  return structList;
+}
+
+function getCommonQuestionStructList(
+  detail,
+  questions,
+  isCommon,
+  nestedQNo = 0
+) {
+  let structList = [];
+  let structs = {};
+  const qTypeNum = isCommon ? 1 : 2;
+  questions.forEach((question) => {
+    if (!structs[question.questionType]) structs[question.questionType] = [];
+    structs[question.questionType].push(question);
+  });
+  Object.keys(structs).forEach((structType) => {
+    const typeNo = COMMON_QUESTION_TYPES.indexOf(structType) + 1;
+    const questions = parseCommonTypeQuestions(structType, structs[structType]);
+
+    structList.push({
+      detailName: detail.detailName,
+      detailNo: detail.detailNo,
+      isOnlyOneNested: detail.isOnlyOneNested,
+      structType,
+      isCommon,
+      nestedQNo,
+      sortNo:
+        typeNo * 10000 + detail.detailNo * 1000 + qTypeNum * 100 + nestedQNo,
+      questions,
+    });
+  });
+  return structList;
+}
+
+function getNestedQuestionStructList(detail, questions) {
+  let nestedQuestions = [];
+  questions.forEach((question) => {
+    const nestedNo = question.questionNo - 1;
+    if (!nestedQuestions[nestedNo]) {
+      nestedQuestions[nestedNo] = {
+        questionNo: question.questionNo,
+        subQuestions: [],
+      };
+    }
+    nestedQuestions[nestedNo].subQuestions.push({
+      ...question,
+      questionNo: question.subQno,
+    });
+  });
+
+  let structList = [];
+  nestedQuestions.forEach((question) => {
+    const qStructList = getCommonQuestionStructList(
+      detail,
+      question.subQuestions,
+      false,
+      question.questionNo
+    );
+    // console.log(qStructList);
+    structList.push(...qStructList);
+  });
+  return structList;
+}
+
+function parseCommonTypeQuestions(structType, dataList) {
+  const choiceQs = [
+    "SINGLE_ANSWER_QUESTION",
+    "MULTIPLE_ANSWER_QUESTION",
+    "BOOL_ANSWER_QUESTION",
+  ];
+  let questions = [];
+  if (choiceQs.includes(structType)) {
+    questions = dataList.map((item) => {
+      return {
+        questionNo: item.questionNo,
+        optionCount: structType === "BOOL_ANSWER_QUESTION" ? 2 : item.qinfo,
+      };
+    });
+  } else if (structType === "FILL_BLANK_QUESTION") {
+    questions = dataList.map((item) => {
+      return {
+        questionNo: item.questionNo,
+        fillCount: item.qinfo,
+      };
+    });
+  } else {
+    questions = dataList.map((item) => item.questionNo);
+  }
+  return questions;
+}
+
+export function buildCardFromPaperSimpleStruct(
+  paperSimpleStruct,
+  elementTypePreSetInfo
+) {
+  const structList = parsePaperStruct(paperSimpleStruct);
+  const cardElements = buildCardElements(structList, elementTypePreSetInfo);
+  // console.log(cardElements);
+  return cardElements;
+}

+ 236 - 0
src/modules/card/components/CardBuildDialog.vue

@@ -0,0 +1,236 @@
+<template>
+  <el-dialog
+    class="card-build-dialog"
+    :visible.sync="modalIsShow"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :show-close="false"
+    append-to-body
+    fullscreen
+    @opened="initData"
+  >
+    <div slot="title"></div>
+    <div slot="footer"></div>
+    <div v-loading="loading" element-loading-text="拼命加载中"></div>
+
+    <div
+      v-if="modalIsShow"
+      id="card-build"
+      class="card-build card-preview card-print"
+    >
+      <card-view
+        v-if="pages.length"
+        ref="CardView"
+        class="preview-body"
+        :pages="pages"
+        :card-config="cardConfig"
+      ></card-view>
+      <!-- all topics -->
+      <div v-else class="topic-list">
+        <div :class="['page-box', `page-box-${cardConfig.pageSize}`]">
+          <div class="page-main-inner">
+            <div
+              :class="['page-main', `page-main-${cardConfig.columnNumber}`]"
+              :style="{ margin: `0 -${cardConfig.columnGap / 2}px` }"
+            >
+              <div
+                class="page-column"
+                :style="{ padding: `0 ${cardConfig.columnGap / 2}px` }"
+              >
+                <div class="page-column-main" id="topic-column">
+                  <div class="page-column-body">
+                    <!-- card-head-sample -->
+                    <card-head-sample
+                      :data="cardHeadSampleData"
+                      id="simple-card-head"
+                      v-if="topics.length && cardHeadSampleData"
+                    ></card-head-sample>
+                    <!-- topic element -->
+                    <topic-element-preview
+                      class="page-column-element"
+                      v-for="element in topics"
+                      :key="element.id"
+                      :data="element"
+                    ></topic-element-preview>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { mapState, mapMutations, mapActions } from "vuex";
+import { saveCard, cardConfigInfos } from "../api";
+import { tikuPaperDetail } from "../../exam/api";
+import { getPaperJsonSimpleStructInfo } from "../autoBuild/paperStruct";
+import { buildCardFromPaperSimpleStruct } from "../autoBuild/simplePaperCard";
+// card components
+import { getCardHeadModel } from "../../../../card/elementModel";
+import TopicElementPreview from "../../../../card/components/TopicElementPreview";
+import CardView from "../../../../card/components/CardView";
+import CardHeadSample from "../../../../card/elements/card-head/CardHead";
+
+export default {
+  name: "CardBuild",
+  components: { CardView, TopicElementPreview, CardHeadSample },
+  props: {
+    presetData: {
+      type: Object,
+      default() {
+        return {
+          paperId: "",
+        };
+      },
+    },
+  },
+  data() {
+    return {
+      cardId: "",
+      loading: false,
+      modalIsShow: false,
+    };
+  },
+  computed: {
+    ...mapState("card", ["cardConfig", "topics", "pages"]),
+    cardHeadSampleData() {
+      if (!this.cardConfig["pageSize"]) return;
+      const data = getCardHeadModel(this.cardConfig);
+      data.isSimple = true;
+      return data;
+    },
+  },
+  methods: {
+    ...mapMutations("card", ["setCardConfig", "setTopics", "initState"]),
+    ...mapActions("card", ["resetTopicSeries", "rebuildPages"]),
+    async initData() {
+      this.loading = true;
+      this.initState();
+
+      // 题卡规则
+      const cardConfig = await this.getCardConfig(
+        this.presetData.cardRuleId
+      ).catch(() => {});
+      if (!cardConfig) {
+        this.emitResult({ success: false, message: "题卡规则获取失败" });
+        return;
+      }
+      this.setCardConfig(cardConfig);
+
+      // 试卷信息
+      const res = await tikuPaperDetail(this.presetData.paperId).catch(
+        () => {}
+      );
+      if (!res) {
+        this.emitResult({ success: false, message: "试卷内容获取失败" });
+        return;
+      }
+      // 构建题卡
+      try {
+        const paperSimpleStruct = getPaperJsonSimpleStructInfo(res);
+        console.log(paperSimpleStruct);
+        const elementTypePreSetInfo = {
+          FILL_QUESTION: {
+            pageSize: "A3",
+            columnNumber: 2,
+          },
+        };
+        const elements = buildCardFromPaperSimpleStruct(
+          paperSimpleStruct,
+          elementTypePreSetInfo
+        );
+        this.setTopics([getCardHeadModel(this.cardConfig), ...elements]);
+        this.resetTopicSeries();
+      } catch (error) {
+        this.emitResult({
+          success: false,
+          message: error.message || "数据构建错误",
+        });
+        return;
+      }
+
+      this.$nextTick(() => {
+        this.rebuildPages();
+
+        this.$nextTick(() => {
+          this.buildData();
+        });
+      });
+    },
+    async getCardConfig(cardRuleId) {
+      const data = await cardConfigInfos(cardRuleId);
+      if (!data) {
+        this.$message.error("找不到题卡规则!");
+        return Promise.reject();
+      }
+      let config = {
+        ...data,
+        ...{
+          pageSize: "A3",
+          columnNumber: 2,
+          columnGap: 20,
+          showForbidArea: false,
+          makeMethod: "SELF",
+        },
+      };
+      config.fillNumber = data.examNumberDigit;
+      config.aOrB = false;
+      config.requiredFields = JSON.parse(config.requiredFields);
+      config.extendFields = JSON.parse(config.extendFields);
+      config.cardTitle = this.getCardTitle(config.titleRule);
+      return config;
+    },
+    getCardTitle(titleRule) {
+      const fieldMap = {
+        courseCode: this.presetData.courseCode,
+        courseName: this.presetData.courseName,
+        schoolName: this.presetData.schoolName,
+      };
+      Object.entries(fieldMap).forEach(([key, val]) => {
+        titleRule = titleRule.replace("${" + key + "}", val);
+      });
+      return titleRule;
+    },
+    async buildData() {
+      const model = this.$refs.CardView.getPageModel({
+        cardConfig: this.cardConfig,
+        pages: this.pages,
+      });
+      const htmlContent = this.$refs.CardView.getPreviewTemp(
+        document.getElementById("card-build").outerHTML
+      );
+
+      const datas = {
+        content: model,
+        htmlContent,
+        title: "",
+        status: "SUBMIT",
+        ...this.presetData,
+      };
+      const result = await saveCard(datas).catch((error) => {
+        this.emitResult({
+          success: false,
+          data: error.message || "保存题卡错误",
+        });
+      });
+      if (!result) return;
+      this.emitResult({ success: true, data: result });
+    },
+    emitResult(data) {
+      this.loading = false;
+      this.$emit("confirm", data);
+      this.cancel();
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+  },
+};
+</script>

+ 4 - 1
src/modules/exam/api.js

@@ -172,7 +172,10 @@ export const uploadOrFindExamTaskStudent = (datas) => {
 };
 // 从题库选择试卷 创建命题任务
 export const tikuPaperListQuery = (datas) => {
-  return $postParam("/api/admin/tiku/task/switch_card", datas);
+  return $postParam("/api/admin/exam/task/page_tiku_paper", datas);
+};
+export const tikuPaperDetail = (paperId) => {
+  return $postParam("/api/admin/exam/task/get_tiku_paper_struct", { paperId });
 };
 
 // task-review-manage

+ 34 - 4
src/modules/exam/components/createExamAndPrintTask/InfoExamTask.vue

@@ -352,10 +352,17 @@
       :row="curAttachment"
       @confirm="tikuPaperSelected"
     ></select-tiku-paper-dialog>
+    <!-- CardBuildDialog -->
+    <card-build-dialog
+      ref="CardBuildDialog"
+      :presetData="cardBuildPresetData"
+      @confirm="cardBuildConfirm"
+    ></card-build-dialog>
   </div>
 </template>
 
 <script>
+import { mapState, mapMutations } from "vuex";
 import UploadPaperDialog from "../UploadPaperDialog";
 import SimpleImagePreview from "@/components/SimpleImagePreview";
 import ModifyCard from "../../../card/components/ModifyCard";
@@ -363,9 +370,10 @@ import SelectTikuPaperDialog from "./SelectTikuPaperDialog.vue";
 import { COMMON_CARD_RULE_ID } from "../../../../constants/enumerate";
 import { cardForSelectList } from "../../api";
 import { courseQuery, examConfigByExamIdOrgId } from "../../../base/api";
-import { mapState, mapMutations } from "vuex";
 import { copyCard } from "../../../card/api";
 
+import CardBuildDialog from "../../../card/components/CardBuildDialog.vue";
+
 // type=GENERIC时, 为通卡,不可复制,不可编辑,可预览。
 // type=CUSTOM时,可复制,不可编辑,如果是当前自己任务的题卡,才可编辑。
 export default {
@@ -375,6 +383,7 @@ export default {
     SimpleImagePreview,
     ModifyCard,
     SelectTikuPaperDialog,
+    CardBuildDialog,
   },
   data() {
     return {
@@ -441,6 +450,8 @@ export default {
       semesters: [],
       teachingRoomName: "",
       cardRuleName: "全部通卡",
+      // card-build
+      cardBuildPresetData: {},
       // exam-task-detail
       examTaskDetail: { makeMethod: "" },
       paperConfirmAttachmentId: { attachmentId: "", filename: "", url: "" },
@@ -767,10 +778,29 @@ export default {
       this.curAttachment = attachment;
       this.$refs.SelectTikuPaperDialog.open();
     },
-    tikuPaperSelected(data) {
-      // TODO:
-      Object.assign(this.curAttachment, {
+    async tikuPaperSelected(data) {
+      this.cardBuildPresetData = {
+        courseCode: this.examTask.courseCode,
+        courseName: this.examTask.courseName,
+        schoolName: this.$ls.get("schoolName"),
+        makeMethod: "SELF",
+        cardName: "",
+        cardRuleId: this.examTask.cardRuleId,
+        type: "CUSTOM",
+        createMethod: "STANDARD",
         paperId: data.id,
+      };
+      this.$refs.CardBuildDialog.open();
+    },
+    cardBuildConfirm(data) {
+      if (!data.success) {
+        this.$message.error(data.message);
+        return;
+      }
+      Object.assign(this.curAttachment, {
+        paperId: this.cardBuildPresetData.paperId,
+        cardId: data.cardId,
+        cardTitle: data.cardTitle,
       });
     },
     // exam-task-detail edit

+ 24 - 0
src/plugins/utils.js

@@ -413,3 +413,27 @@ export function objFilterNull(obj) {
   });
   return nobj;
 }
+
+/**
+ * 百以下数字转中文汉字
+ * @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("十");
+}