فهرست منبع

错题分析打印pdf

liurongli 3 هفته پیش
والد
کامیت
d9d6e5b6f1
3فایلهای تغییر یافته به همراه462 افزوده شده و 1 حذف شده
  1. 452 0
      src/components/ErrorsTablePdf.vue
  2. 9 0
      src/views/analysis/errorAnalysis.vue
  3. 1 1
      src/views/analysis/reportDownload.vue

+ 452 - 0
src/components/ErrorsTablePdf.vue

@@ -0,0 +1,452 @@
+<template>
+  <div class="report_module error">
+    <div class="area_page">
+      <div class="area_title">
+        <div class="page_num">第1页(共10页)</div>
+        <div class="title_text">
+          【{{ `${examName}错题名单(${className})` }}】
+        </div>
+        <div class="print_time">打印时间:{{ getNowTime() }}</div>
+      </div>
+    </div>
+    <div class="area_page print_page">
+      <div class="area_title">
+        <div class="page_num">第1页(共10页)</div>
+        <div class="title_text">
+          【{{ `${examName}错题名单(${className})` }}】
+        </div>
+        <div class="print_time">打印时间:{{ getNowTime() }}</div>
+      </div>
+      <div class="module_table table_42">
+        <el-table
+          :data="tablePageData"
+          border
+          stripe
+          row-key="questionId"
+          :span-method="SpanMethod"
+          :row-class-name="ErrorTableRowClassName"
+          :cell-class-name="ErrorTableCellClassName"
+        >
+          <el-table-column
+            prop="questionName"
+            label="小题名称"
+            width="60"
+            align="center"
+          />
+          <el-table-column
+            prop="questionType"
+            label="题型"
+            width="50"
+            align="center"
+          />
+          <el-table-column
+            prop="questionScore"
+            label="满分"
+            width="48"
+            align="center"
+          />
+          <el-table-column
+            prop="averageScore"
+            label="平均分"
+            width="48"
+            align="center"
+          />
+          <el-table-column
+            prop="scoreRate"
+            label="得分率"
+            width="50"
+            align="center"
+          />
+          <el-table-column
+            prop="studentCount"
+            label="答题人数"
+            width="55"
+            align="center"
+          />
+          <el-table-column
+            prop="errorCount"
+            label="错题人数"
+            width="55"
+            align="center"
+          />
+          <el-table-column
+            prop="answerValue"
+            label="正确答案"
+            width="55"
+            align="center"
+          >
+            <template #default="scope">
+              {{ scope.row.answerValue ?? "-" }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="errorDetail" label="错题详情" align="center">
+            <el-table-column
+              :align="key == 'registrationCodeList' ? 'left' : 'center'"
+              v-for="(key, index) in [
+                'name',
+                'rate',
+                'studentNum',
+                'registrationCodeList',
+              ]"
+              :key="key"
+              label=""
+              :prop="key"
+              :min-width="[28, 50, 30, 320][index]"
+            >
+              <template #default="{ row: org }">
+                <span v-if="key == 'registrationCodeList'">{{ org[key] }}</span>
+                <span v-else-if="key == 'rate'">{{
+                  org[key] == "-" ? "-" : `${org[key]}%`
+                }}</span>
+                <span v-else>{{ org[key] }}</span>
+              </template>
+            </el-table-column>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getNowTime } from "@/utils/common";
+import { ref, nextTick, watch } from "vue";
+import { ElMessage } from "element-plus";
+import { jsPDF } from "jspdf";
+import domtoimage from "dom-to-image";
+// 定义数据类型
+interface TableItem {
+  rowKey: number | string;
+  questionName?: string;
+  questionType?: string;
+  answerValue?: string;
+  questionScore?: number | string;
+  averageScore?: number | string;
+  scoreRate?: number | string;
+  hiddenRatioStu?: boolean;
+  name?: string;
+  rate?: number | string;
+  studentNum?: number | string;
+  studentCount?: number | string;
+  registrationCodeList?: string;
+  hiddenRatio?: boolean;
+  hiddenStu?: boolean;
+}
+
+// 定义 Props
+const props = defineProps({
+  examName: {
+    type: String,
+    default: "",
+  },
+  tableData: {
+    type: Array,
+    default: () => [],
+  },
+  subjectName: {
+    type: String,
+    default: "",
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+});
+const tablePageData = ref<TableItem[][]>([]);
+// 定义 Emits
+const emit = defineEmits(["PdfLoadEnd"]);
+
+// Refs & 常量
+const pageTitle = ref<HTMLElement | null>(null);
+const titleHeight = ref(80);
+const questionHeight = 30;
+const lineHeight = 16;
+const printPageHeight = 1205;
+
+// 表格分页逻辑
+const TablePagesData = () => {};
+
+// 下载 PDF (使用 dom-to-image)
+const DownloadPdf = async () => {
+  const elements = document.querySelectorAll(".page_report .print_page");
+  if (elements.length === 0) {
+    ElMessage.warning("未找到可导出的页面元素!");
+    emit("PdfLoadEnd");
+    return;
+  }
+
+  const pdf = new jsPDF("p", "pt", "a4");
+  const pdfWidth = pdf.internal.pageSize.getWidth();
+  const pdfHeight = pdf.internal.pageSize.getHeight();
+
+  for (const [pageIndex, element] of Array.from(elements).entries()) {
+    await nextTick();
+    if (element) {
+      const htmlElement = element as HTMLElement;
+      htmlElement.style.visibility = "visible";
+      htmlElement.offsetHeight;
+
+      await new Promise((resolve) => setTimeout(resolve, 100));
+
+      const dataUrl = await domtoimage.toJpeg(htmlElement, {
+        quality: 0.8,
+        width: htmlElement.scrollWidth * 2,
+        height: htmlElement.scrollHeight * 2,
+        canvasWidth: htmlElement.scrollWidth * 2,
+        canvasHeight: htmlElement.scrollHeight * 2,
+        style: {
+          transform: "scale(2)",
+          "transform-origin": "top left",
+          "background-color": "#ffffff",
+          overflow: "visible",
+          "white-space": "normal",
+          position: "relative",
+          visibility: "visible",
+        },
+        bgcolor: "#ffffff",
+      });
+
+      const imgProps = pdf.getImageProperties(dataUrl);
+      const imgWidth = imgProps.width;
+      const imgHeight = imgProps.height;
+
+      const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
+      const adjustWidth = imgWidth * ratio;
+      const adjustHeight = imgHeight * ratio;
+
+      const xPosition = (pdfWidth - adjustWidth) / 2;
+      const yPosition = (pdfHeight - adjustHeight) / 2;
+
+      if (pageIndex > 0) {
+        pdf.addPage();
+      }
+      pdf.addImage(
+        dataUrl,
+        "JPEG",
+        xPosition,
+        yPosition,
+        adjustWidth,
+        adjustHeight,
+      );
+    }
+  }
+
+  pdf.save(
+    `${props.examName}_${props.subjectName}_${props.className}错题名单.pdf`,
+  );
+  emit("PdfLoadEnd");
+};
+const LoadData = () => {
+  nextTick(() => {
+    const pageTitleHeight = pageTitle.value?.offsetHeight || 60;
+    titleHeight.value = pageTitleHeight + 20;
+    tablePageData.value = props.tableData;
+    TablePagesData();
+  });
+};
+// 错题分析  合并单元格
+const SpanMethod = ({ row, column }: any) => {
+  const props = [
+    "errorDetail",
+    "name",
+    "rate",
+    "studentNum",
+    "registrationCodeList",
+  ];
+  if (props.indexOf(column.property) == -1) {
+    if (row.rowspan) {
+      return {
+        rowspan: row.rowspan,
+        colspan: row.colspan,
+      };
+    } else {
+      return {
+        rowspan: 0,
+        colspan: 0,
+      };
+    }
+  }
+};
+
+// 定义行样式
+const ErrorTableRowClassName = ({ row }: any) => {
+  if (row.rowspanIndex % 2 === 0) {
+    return "row_color_FFFFFF";
+  } else {
+    return "row_color_FAFAFA";
+  }
+};
+
+// 设置单元格样式
+const ErrorTableCellClassName = ({ column }: any) => {
+  if (column.property == "registrationCodeList") {
+    return "white_space_normal";
+  } else {
+    return "";
+  }
+};
+// 监听筛选条件
+watch(
+  () => props.tableData,
+  async () => {
+    if (props.tableData.length) {
+      LoadData();
+    }
+  },
+  { deep: true },
+);
+
+// 暴露方法供父组件调用
+defineExpose({
+  DownloadPdf,
+});
+</script>
+
+<style lang="scss" scoped>
+.error {
+  width: 908px;
+  margin: 0 auto;
+  // position: absolute;
+  top: 0;
+  left: -9999px;
+  z-index: -10;
+
+  .area_page {
+    width: 908px;
+    margin-top: 20px;
+    padding: 10px !important;
+    background-color: #fff;
+    box-sizing: border-box;
+    overflow: hidden;
+
+    &.print_page {
+      height: 1285px;
+    }
+
+    .area_title {
+      width: 100%;
+      line-height: 30px;
+      display: flex;
+      gap: 5px;
+
+      .page_num {
+        font-size: 12px;
+        color: #000000;
+        flex-shrink: 0;
+      }
+
+      .print_time {
+        font-size: 12px;
+        color: #000000;
+        flex-shrink: 0;
+      }
+
+      .title_text {
+        font-weight: bold;
+        font-size: 16px;
+        color: #000000;
+        flex: 1;
+        text-align: center;
+      }
+    }
+
+    .module_table {
+      width: 100%;
+      box-sizing: border-box;
+      border-collapse: collapse;
+
+      :deep(.el-table) {
+        border-radius: 6px;
+        border-left: 0px;
+        border-right: 0px;
+        thead {
+          height: 23px !important;
+          font-size: 12px;
+        }
+        .el-table__header {
+          .cell {
+            font-size: 12px;
+          }
+        }
+
+        td {
+          &.is-left {
+            .cell {
+              text-align: left;
+            }
+          }
+        }
+
+        .cell {
+          font-size: 12px;
+          line-height: 23px;
+          display: block;
+          text-align: center;
+          white-space: nowrap;
+          padding: 0 !important;
+        }
+        .el-table__header-wrapper {
+          .el-table__header {
+            .is-group {
+              tr {
+                &:nth-child(2) {
+                  display: none;
+                }
+              }
+            }
+          }
+        }
+
+        .el-table__cell {
+          height: 23px !important;
+          &.white_space_normal {
+            .cell {
+              white-space: normal;
+              padding: 0 0 0 3px !important;
+            }
+          }
+        }
+
+        &.el-table--striped {
+          .el-table__body {
+            tr {
+              height: 23px;
+              font-size: 12px;
+              &.el-table__row--striped {
+                &.row_color_FFFFFF {
+                  td.el-table__cell {
+                    background: #ffffff;
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        &.el-table--striped
+          .el-table__body
+          tr.el-table__row.row_color_FAFAFA
+          td.el-table__cell {
+          background: #fafafa;
+        }
+
+        &.el-table--striped
+          .el-table__body
+          tr.el-table__row--striped.row_color_FAFAFA
+          td.el-table__cell {
+          background: #fafafa;
+        }
+
+        &.el-table--enable-row-hover .el-table__body tr.row_color_FFFFFF > td {
+          background-color: transparent !important;
+        }
+      }
+
+      :deep(.table_row_blue) {
+        color: #2e64fa;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 9 - 0
src/views/analysis/errorAnalysis.vue

@@ -135,11 +135,20 @@
     :examName="getExamName"
     @PdfLoadEnd="PdfLoadEnd"
   />
+  <!-- <ErrorsTablePdf
+    ref="errPdfReport"
+    :tableData="state.tableData"
+    :className="analysisStore.filterObject.classGroupName"
+    :subjectName="analysisStore.filterObject.subjectName || ''"
+    :examName="getExamName"
+    @PdfLoadEnd="PdfLoadEnd"
+  /> -->
 </template>
 <script lang="ts" setup>
 import ReportModule from "@/components/ReportModule.vue";
 import { useAnalysisStore } from "@/store/analysis";
 import ErrorsPdf from "@/components/ErrorsPdf.vue";
+// import ErrorsTablePdf from "@/components/ErrorsTablePdf.vue";
 import { onMounted, reactive, computed, ref, watch } from "vue";
 import { errorQuestionAnalysis, exportErrorQuestion } from "@/api/analysis";
 import { loadingSvgIcon } from "@/utils/common";

+ 1 - 1
src/views/analysis/reportDownload.vue

@@ -398,7 +398,7 @@ function groupAnalysisModule(): ExcelModule {
   let excelsList: ExcelsItem[] = [
     {
       label: "分组分析表",
-      value: "questionGroupAnalysis",
+      value: "questionGroupAnalysis4",
     },
   ];
   let excelModuleList = ["questionGroupAnalysis4"];