|
|
@@ -4,27 +4,49 @@
|
|
|
<template v-for="(item, page) in state.tableData">
|
|
|
<div class="area_page print_page">
|
|
|
<div class="area_title" v-if="item.isFirstPage">
|
|
|
- <div class="page_num">第{{ page + 1 }}页(共{{ state.pageTotal }}页)</div>
|
|
|
- <div class="title_text">【{{ `${examName}成绩单(${item?.className || ''})` }}】</div>
|
|
|
+ <div class="page_num">
|
|
|
+ 第{{ page + 1 }}页(共{{ state.pageTotal }}页)
|
|
|
+ </div>
|
|
|
+ <div class="title_text">
|
|
|
+ 【{{ `${examName}成绩单(${item?.className || ""})` }}】
|
|
|
+ </div>
|
|
|
<div class="print_time">打印时间:{{ getNowTime() }}</div>
|
|
|
</div>
|
|
|
<div class="area_count" v-if="item.isFirstPage">
|
|
|
应考:{{ item?.fiveRateResult?.examAllNum || 0 }}人
|
|
|
实考:{{ item?.fiveRateResult?.effectiveNum || 0 }}人
|
|
|
- 缺考:{{ Number(item?.fiveRateResult?.examAllNum || 0) - Number(item?.fiveRateResult?.effectiveNum || 0)
|
|
|
- }}人
|
|
|
- 满分:{{ item?.fullScore || 0 }}分
|
|
|
+ 缺考:{{
|
|
|
+ Number(item?.fiveRateResult?.examAllNum || 0) -
|
|
|
+ Number(item?.fiveRateResult?.effectiveNum || 0)
|
|
|
+ }}人 满分:{{ item?.fullScore || 0 }}分
|
|
|
最高分:{{ item?.fiveRateResult?.maxScore || 0 }}分
|
|
|
最低分:{{ item?.fiveRateResult?.minScore || 0 }}分
|
|
|
- 年级均分:{{ item?.fiveRateResult?.avgSchoolScore || 0 }}分
|
|
|
- 班级均分:{{ item?.fiveRateResult?.avgScore || 0 }}分
|
|
|
+ 年级均分:{{
|
|
|
+ item?.fiveRateResult?.avgSchoolScore || 0
|
|
|
+ }}分 班级均分:{{ item?.fiveRateResult?.avgScore || 0 }}分
|
|
|
</div>
|
|
|
<div class="module_table table_42">
|
|
|
- <el-table :data="item?.listData || []" border stripe :show-header="item.isFirstPage">
|
|
|
- <el-table-column v-for="item in state.staticHeaderData" :key="item.prop" :prop="item.prop"
|
|
|
- :label="item.label" min-width="5">
|
|
|
+ <el-table
|
|
|
+ :data="item?.listData || []"
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ :show-header="item.isFirstPage"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ v-for="item in state.staticHeaderData"
|
|
|
+ :key="item.prop"
|
|
|
+ :prop="item.prop"
|
|
|
+ :label="item.label"
|
|
|
+ min-width="5"
|
|
|
+ >
|
|
|
<template #default="scope">
|
|
|
- <span v-if="scope.row.score != '缺考' && scope.row.score != '违纪' && item.prop === 'studentUserName'">
|
|
|
+ <span
|
|
|
+ v-if="
|
|
|
+ scope.row.score != '缺考' &&
|
|
|
+ scope.row.score != '违纪' &&
|
|
|
+ item.prop === 'studentUserName'
|
|
|
+ "
|
|
|
+ >
|
|
|
{{ scope.row[item.prop] }}
|
|
|
</span>
|
|
|
<template v-else>
|
|
|
@@ -32,9 +54,16 @@
|
|
|
</template>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column v-for="(item, index) in state.groupDataTitleData" :key="item.prop" :prop="`${index}_${item.prop}`"
|
|
|
- :label="item.label" min-width="5">
|
|
|
- <template #default="scope">{{ scope.row?.dynamicsData?.groupData?.[index] || "-" }}</template>
|
|
|
+ <el-table-column
|
|
|
+ v-for="(item, index) in state.groupDataTitleData"
|
|
|
+ :key="item.prop"
|
|
|
+ :prop="`${index}_${item.prop}`"
|
|
|
+ :label="item.label"
|
|
|
+ min-width="5"
|
|
|
+ >
|
|
|
+ <template #default="scope">{{
|
|
|
+ scope.row?.dynamicsData?.groupData?.[index] || "-"
|
|
|
+ }}</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
@@ -44,10 +73,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
-import {
|
|
|
- studentTranscriptTitle,
|
|
|
- queryStudentTranscript,
|
|
|
-} from "@/api/analysis";
|
|
|
+import { studentTranscriptTitle, queryStudentTranscript } from "@/api/analysis";
|
|
|
import { useAnalysisStore } from "@/store/analysis";
|
|
|
import { getNowTime } from "@/utils/common";
|
|
|
import { reactive, computed, nextTick } from "vue";
|
|
|
@@ -68,7 +94,6 @@ interface TableCount {
|
|
|
missExamCount: number | string;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
interface State {
|
|
|
keyWord: string;
|
|
|
selectClassList: string[];
|
|
|
@@ -87,7 +112,7 @@ const examName = computed(() => {
|
|
|
return analysisStore.analysisExamInfo.examName || "";
|
|
|
});
|
|
|
const state = reactive<State>({
|
|
|
- keyWord: '',
|
|
|
+ keyWord: "",
|
|
|
selectClassList: [],
|
|
|
sortType: 1,
|
|
|
filterType: 1,
|
|
|
@@ -100,7 +125,7 @@ const state = reactive<State>({
|
|
|
normalCount: 0,
|
|
|
missExamCount: 0,
|
|
|
},
|
|
|
- tableLoading: true
|
|
|
+ tableLoading: true,
|
|
|
});
|
|
|
/** 获取表头 */
|
|
|
const GetStudentTranscriptTitle = async () => {
|
|
|
@@ -113,7 +138,10 @@ const GetStudentTranscriptTitle = async () => {
|
|
|
if (res.code === 200) {
|
|
|
const staticHeaderData = res.data?.title?.staticHeaderData || [];
|
|
|
state.staticHeaderData = staticHeaderData.filter(
|
|
|
- (item: { display: any, prop: string }) => item.display && item.prop !== 'studentUserCode' && item.prop !== 'studentRegistrationTypeName',//移除学号和类型
|
|
|
+ (item: { display: any; prop: string }) =>
|
|
|
+ item.display &&
|
|
|
+ item.prop !== "studentUserCode" &&
|
|
|
+ item.prop !== "studentRegistrationTypeName", //移除学号和类型
|
|
|
);
|
|
|
state.groupDataTitleData =
|
|
|
res.data?.title?.dynamicsHeaderData?.groupDataTitle || [];
|
|
|
@@ -134,11 +162,11 @@ const GetTableData = async () => {
|
|
|
queryStr: state.keyWord,
|
|
|
classIdCodes: state.selectClassList,
|
|
|
sortType: state.sortType,
|
|
|
- filterType: state.filterType
|
|
|
+ filterType: state.filterType,
|
|
|
});
|
|
|
if (res.code === 200) {
|
|
|
const tableData = res?.data || [];
|
|
|
- tablePageData(tableData);//分页处理(内部会设置 state.tableData 和 state.pageTotal)
|
|
|
+ tablePageData(tableData); //分页处理(内部会设置 state.tableData 和 state.pageTotal)
|
|
|
}
|
|
|
} catch (err) {
|
|
|
console.error("获取表格数据失败:", err);
|
|
|
@@ -150,12 +178,12 @@ const GetTableData = async () => {
|
|
|
};
|
|
|
//分页:超过一页自动换页,后续页不包含标题行和应考/实考/缺考统计行
|
|
|
const tablePageData = (list: any[]) => {
|
|
|
- const trLineHeight = 21;//表格表头、tr行高
|
|
|
- const printPageHeight = 1255;//打印页面高度 减掉padding 30px
|
|
|
+ const trLineHeight = 21; //表格表头、tr行高
|
|
|
+ const printPageHeight = 1255; //打印页面高度 减掉padding 30px
|
|
|
|
|
|
- const rowNum = Math.floor(printPageHeight / trLineHeight);//每页可容纳行数 向下取整
|
|
|
- const firstPageRows = rowNum - 3;//第一页数据行数:标题行+统计行+表头行占3行
|
|
|
- const otherPageRows = rowNum;//后续页数据行数:无标题、无统计、无表头,数据行放满 原来是 rowNum - 1,后续页没有表头占位了,数据行放满
|
|
|
+ const rowNum = Math.floor(printPageHeight / trLineHeight); //每页可容纳行数 向下取整
|
|
|
+ const firstPageRows = rowNum - 3; //第一页数据行数:标题行+统计行+表头行占3行
|
|
|
+ const otherPageRows = rowNum; //后续页数据行数:无标题、无统计、无表头,数据行放满 原来是 rowNum - 1,后续页没有表头占位了,数据行放满
|
|
|
|
|
|
const pageList: any[] = [];
|
|
|
list.forEach((item) => {
|
|
|
@@ -169,8 +197,7 @@ const tablePageData = (list: any[]) => {
|
|
|
const pageNum =
|
|
|
Math.ceil((dataList.length - firstPageRows) / otherPageRows) + 1;
|
|
|
for (let page = 1; page <= pageNum; page++) {
|
|
|
- const start =
|
|
|
- page === 1 ? 0 : firstPageRows + (page - 2) * otherPageRows;
|
|
|
+ const start = page === 1 ? 0 : firstPageRows + (page - 2) * otherPageRows;
|
|
|
const rows =
|
|
|
page === 1
|
|
|
? Math.min(firstPageRows, dataList.length)
|
|
|
@@ -185,9 +212,14 @@ const tablePageData = (list: any[]) => {
|
|
|
});
|
|
|
state.tableData = pageList;
|
|
|
state.pageTotal = pageList.length;
|
|
|
-}
|
|
|
+};
|
|
|
// 初始化
|
|
|
-const PageInit = async (keyWord: string, selectClassList: string[], sortType: number, filterType: number) => {
|
|
|
+const PageInit = async (
|
|
|
+ keyWord: string,
|
|
|
+ selectClassList: string[],
|
|
|
+ sortType: number,
|
|
|
+ filterType: number,
|
|
|
+) => {
|
|
|
state.keyWord = keyWord;
|
|
|
state.selectClassList = selectClassList;
|
|
|
state.sortType = sortType;
|
|
|
@@ -196,8 +228,131 @@ const PageInit = async (keyWord: string, selectClassList: string[], sortType: nu
|
|
|
};
|
|
|
|
|
|
// 导出PDF并在浏览器新窗口打开
|
|
|
+// const ExportPdf = async () => {
|
|
|
+// const elements = document.querySelectorAll(".score_pdf .print_page");
|
|
|
+// if (!elements.length) {
|
|
|
+// ElMessage.warning("未找到可导出的页面内容,请先加载数据!");
|
|
|
+// return;
|
|
|
+// }
|
|
|
+// // 在用户手势内同步打开空白窗口,避免被浏览器拦截
|
|
|
+// const win = window.open("", "_blank");
|
|
|
+// if (!win) {
|
|
|
+// ElMessage.warning("浏览器拦截了弹窗,请允许后重试");
|
|
|
+// return;
|
|
|
+// }
|
|
|
+// win.document.write(
|
|
|
+// "<p style='font-family:sans-serif;text-align:center;padding-top:40px'>成绩单 PDF 生成中…</p>",
|
|
|
+// );
|
|
|
+
|
|
|
+// 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();
|
|
|
+// const htmlElement = element as HTMLElement;
|
|
|
+// htmlElement.style.visibility = "visible";
|
|
|
+// htmlElement.offsetHeight;
|
|
|
+
|
|
|
+// // 等待该页渲染完成
|
|
|
+// await new Promise((resolve) => setTimeout(resolve, 150));
|
|
|
+
|
|
|
+// 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;
|
|
|
+// // 顶对齐:页面顶部与PDF页面上边缘对齐,避免标题被垂直居中往下推
|
|
|
+// const yPosition = 0;
|
|
|
+
|
|
|
+// if (pageIndex > 0) {
|
|
|
+// pdf.addPage();
|
|
|
+// }
|
|
|
+// pdf.addImage(
|
|
|
+// dataUrl,
|
|
|
+// "JPEG",
|
|
|
+// xPosition,
|
|
|
+// yPosition,
|
|
|
+// adjustWidth,
|
|
|
+// adjustHeight,
|
|
|
+// );
|
|
|
+// }
|
|
|
+// // // 直接打开生成的PDF,保留浏览器自带的下载/打印工具栏
|
|
|
+// // const filename = `${examName.value}成绩单.pdf`;
|
|
|
+// // // 通过 PDF 文档属性设置标题,浏览器查看器的标签页标题即显示该文件名
|
|
|
+// // pdf.setProperties({ title: filename });
|
|
|
+// // const blob = pdf.output("blob");
|
|
|
+// // const url = URL.createObjectURL(
|
|
|
+// // new File([blob], filename, { type: "application/pdf" }),
|
|
|
+// // );
|
|
|
+// // win.location.replace(url);
|
|
|
+// // 在浏览器新窗口打开生成的PDF(中转页方案:窗口标题、下载文件名均使用指定名称)
|
|
|
+// const filename = `${examName.value}_${analysisStore?.filterObject?.subjectName || ''}_${analysisStore?.filterObject?.classGroupName || ''}成绩单.pdf`;
|
|
|
+// const blob = pdf.output("blob");
|
|
|
+// const url = URL.createObjectURL(
|
|
|
+// new File([blob], filename, { type: "application/pdf" }),
|
|
|
+// );
|
|
|
+// // 转义文件名中的特殊字符,防止破坏 HTML
|
|
|
+// const safeName = filename
|
|
|
+// .replace(/&/g, "&")
|
|
|
+// .replace(/</g, "<")
|
|
|
+// .replace(/>/g, ">")
|
|
|
+// .replace(/"/g, """);
|
|
|
+// // 清空"生成中"占位页,写入中转页
|
|
|
+// win.document.open();
|
|
|
+// win.document.write(
|
|
|
+// `<!DOCTYPE html>
|
|
|
+// <html lang="zh-CN">
|
|
|
+// <head>
|
|
|
+// <meta charset="utf-8">
|
|
|
+// <title>${safeName}</title>
|
|
|
+// <style>
|
|
|
+// body{margin:0;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}
|
|
|
+// .bar{height:48px;display:flex;align-items:center;gap:12px;padding:0 16px;background:#f5f6f7;border-bottom:1px solid #dcdfe6;box-sizing:border-box;}
|
|
|
+// .bar .fname{flex:1;font-size:14px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
|
+// .bar .btn{color:#fff;background:#2e64fa;border:0;padding:6px 16px;border-radius:4px;font-size:13px;cursor:pointer;flex-shrink:0;text-decoration:none;display:inline-block;}
|
|
|
+// iframe{width:100%;height:calc(100vh - 48px);border:0;display:block;}
|
|
|
+// @media print{
|
|
|
+// .bar{display:none;}
|
|
|
+// iframe{height:100vh;}
|
|
|
+// }
|
|
|
+// </style>
|
|
|
+// </head>
|
|
|
+// <body>
|
|
|
+// <div class="bar">
|
|
|
+// <span class="fname">${safeName}</span>
|
|
|
+// <a class="btn" href="${url}" download="${safeName}">下载PDF</a>
|
|
|
+// </div>
|
|
|
+// <iframe src="${url}"></iframe>
|
|
|
+// </body>
|
|
|
+// </html>`,
|
|
|
+// );
|
|
|
+// win.document.close();
|
|
|
+// };
|
|
|
const ExportPdf = async () => {
|
|
|
- const elements = document.querySelectorAll(".score_pdf .print_page");
|
|
|
+ const elements = Array.from(
|
|
|
+ document.querySelectorAll<HTMLElement>(".score_pdf .print_page"),
|
|
|
+ );
|
|
|
if (!elements.length) {
|
|
|
ElMessage.warning("未找到可导出的页面内容,请先加载数据!");
|
|
|
return;
|
|
|
@@ -212,37 +367,86 @@ const ExportPdf = async () => {
|
|
|
"<p style='font-family:sans-serif;text-align:center;padding-top:40px'>成绩单 PDF 生成中…</p>",
|
|
|
);
|
|
|
|
|
|
+ // 1. 确保页面 DOM 渲染完成(只在最开始等一次,不再逐页等待)
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ // 2. 一次性把所有页面设为可见,只触发一次重排
|
|
|
+ elements.forEach((el) => {
|
|
|
+ el.style.visibility = "visible";
|
|
|
+ });
|
|
|
+ void document.body.offsetHeight;
|
|
|
+
|
|
|
+ // 3. 等字体加载完成(替代原来每页 150ms 的固定等待)
|
|
|
+ if (document.fonts?.ready) {
|
|
|
+ await document.fonts.ready;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 预加载页面内尚未加载的图片(含 lazy 图片),避免截图时空白
|
|
|
+ const pendingImgs = elements.flatMap((el) =>
|
|
|
+ Array.from(el.querySelectorAll<HTMLImageElement>("img")).filter(
|
|
|
+ (img) => !img.complete,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ pendingImgs.forEach((img) => {
|
|
|
+ img.loading = "eager";
|
|
|
+ });
|
|
|
+ await Promise.all(
|
|
|
+ pendingImgs.map(
|
|
|
+ (img) =>
|
|
|
+ new Promise<void>((resolve) => {
|
|
|
+ const done = () => resolve();
|
|
|
+ img.addEventListener("load", done);
|
|
|
+ img.addEventListener("error", done);
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+
|
|
|
+ // 5. 等两帧,确保样式/布局已应用
|
|
|
+ await new Promise<void>((resolve) =>
|
|
|
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
|
|
|
+ );
|
|
|
+
|
|
|
+ // 6. 并发截图:多页并行,限制并发数防止内存峰值过高
|
|
|
+ // 机器内存大、页面多时可以调大到 6-8;页面很大时可降到 2
|
|
|
+ const CONCURRENCY = 4;
|
|
|
+ const dataUrls: string[] = new Array(elements.length);
|
|
|
+ let cursor = 0;
|
|
|
+ const worker = async () => {
|
|
|
+ while (cursor < elements.length) {
|
|
|
+ const pageIndex = cursor++;
|
|
|
+ const htmlElement = elements[pageIndex];
|
|
|
+ // 渲染参数与原来完全一致,保留 scale(2)
|
|
|
+ dataUrls[pageIndex] = await domtoimage.toJpeg(htmlElement, {
|
|
|
+ quality: 0.7,
|
|
|
+ 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",
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ await Promise.all(
|
|
|
+ Array.from(
|
|
|
+ { length: Math.min(CONCURRENCY, elements.length) },
|
|
|
+ () => worker(),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+
|
|
|
+ // 7. 组装 PDF(布局逻辑与原来一致:居中 + 顶对齐)
|
|
|
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();
|
|
|
- const htmlElement = element as HTMLElement;
|
|
|
- htmlElement.style.visibility = "visible";
|
|
|
- htmlElement.offsetHeight;
|
|
|
-
|
|
|
- // 等待该页渲染完成
|
|
|
- await new Promise((resolve) => setTimeout(resolve, 150));
|
|
|
-
|
|
|
- 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",
|
|
|
- });
|
|
|
-
|
|
|
+ dataUrls.forEach((dataUrl, pageIndex) => {
|
|
|
const imgProps = pdf.getImageProperties(dataUrl);
|
|
|
const imgWidth = imgProps.width;
|
|
|
const imgHeight = imgProps.height;
|
|
|
@@ -250,9 +454,7 @@ const ExportPdf = async () => {
|
|
|
const adjustWidth = imgWidth * ratio;
|
|
|
const adjustHeight = imgHeight * ratio;
|
|
|
const xPosition = (pdfWidth - adjustWidth) / 2;
|
|
|
- // 顶对齐:页面顶部与PDF页面上边缘对齐,避免标题被垂直居中往下推
|
|
|
const yPosition = 0;
|
|
|
-
|
|
|
if (pageIndex > 0) {
|
|
|
pdf.addPage();
|
|
|
}
|
|
|
@@ -264,29 +466,19 @@ const ExportPdf = async () => {
|
|
|
adjustWidth,
|
|
|
adjustHeight,
|
|
|
);
|
|
|
- }
|
|
|
- // // 直接打开生成的PDF,保留浏览器自带的下载/打印工具栏
|
|
|
- // const filename = `${examName.value}成绩单.pdf`;
|
|
|
- // // 通过 PDF 文档属性设置标题,浏览器查看器的标签页标题即显示该文件名
|
|
|
- // pdf.setProperties({ title: filename });
|
|
|
- // const blob = pdf.output("blob");
|
|
|
- // const url = URL.createObjectURL(
|
|
|
- // new File([blob], filename, { type: "application/pdf" }),
|
|
|
- // );
|
|
|
- // win.location.replace(url);
|
|
|
- // 在浏览器新窗口打开生成的PDF(中转页方案:窗口标题、下载文件名均使用指定名称)
|
|
|
+ });
|
|
|
+
|
|
|
+ // 中转页方案保持不变
|
|
|
const filename = `${examName.value}_${analysisStore?.filterObject?.subjectName || ''}_${analysisStore?.filterObject?.classGroupName || ''}成绩单.pdf`;
|
|
|
const blob = pdf.output("blob");
|
|
|
const url = URL.createObjectURL(
|
|
|
new File([blob], filename, { type: "application/pdf" }),
|
|
|
);
|
|
|
- // 转义文件名中的特殊字符,防止破坏 HTML
|
|
|
const safeName = filename
|
|
|
.replace(/&/g, "&")
|
|
|
.replace(/</g, "<")
|
|
|
.replace(/>/g, ">")
|
|
|
.replace(/"/g, """);
|
|
|
- // 清空"生成中"占位页,写入中转页
|
|
|
win.document.open();
|
|
|
win.document.write(
|
|
|
`<!DOCTYPE html>
|
|
|
@@ -317,10 +509,14 @@ const ExportPdf = async () => {
|
|
|
);
|
|
|
win.document.close();
|
|
|
};
|
|
|
-
|
|
|
// 加载数据并导出PDF(供父组件一步调用)
|
|
|
-const LoadAndExportPdf = async (keyWord: string, selectClassList: string[], sortType: number, filterType: number) => {
|
|
|
- await PageInit(keyWord, selectClassList, sortType, filterType);
|
|
|
+const LoadAndExportPdf = async (
|
|
|
+ keyWord: string,
|
|
|
+ selectClassList: string[],
|
|
|
+ sortType: number,
|
|
|
+ filterType: number,
|
|
|
+) => {
|
|
|
+ await PageInit(keyWord, selectClassList, sortType, filterType);
|
|
|
await nextTick();
|
|
|
await ExportPdf();
|
|
|
};
|
|
|
@@ -329,7 +525,7 @@ const LoadAndExportPdf = async (keyWord: string, selectClassList: string[], sort
|
|
|
defineExpose({
|
|
|
PageInit,
|
|
|
ExportPdf,
|
|
|
- LoadAndExportPdf
|
|
|
+ LoadAndExportPdf,
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -344,7 +540,7 @@ defineExpose({
|
|
|
|
|
|
.area_page {
|
|
|
width: 908px;
|
|
|
- margin-top:20px;
|
|
|
+ margin-top: 20px;
|
|
|
padding: 8px 15px 0 !important;
|
|
|
background-color: #fff;
|
|
|
box-sizing: border-box;
|
|
|
@@ -352,6 +548,8 @@ defineExpose({
|
|
|
|
|
|
&.print_page {
|
|
|
height: 1285px;
|
|
|
+ -webkit-print-color-adjust: exact !important;
|
|
|
+ print-color-adjust: exact !important;
|
|
|
}
|
|
|
|
|
|
.area_title {
|