|
|
@@ -0,0 +1,417 @@
|
|
|
+<template>
|
|
|
+ <div :key="dialogKey">
|
|
|
+ <el-dialog title="导出个性化错题手册" class="page_full_dialog" :visible.sync="showDialog" fullscreen height="100%">
|
|
|
+ <div class="dialog_print_content">
|
|
|
+ <div class="page_jg_20"></div>
|
|
|
+ <div class="dialog_print_page">
|
|
|
+ <div class="item_page">内容</div>
|
|
|
+ </div>
|
|
|
+ <div class="fixed_set_box">
|
|
|
+ <div class="item_set">
|
|
|
+ <el-radio-group v-model="radioVal">
|
|
|
+ <el-radio :label="0">答案解析在卷底</el-radio>
|
|
|
+ <el-radio :label="1">答案解析在每题后</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="item_set">
|
|
|
+ <el-checkbox-group v-model="checkVal">
|
|
|
+ <el-checkbox label="0">我的作答</el-checkbox>
|
|
|
+ <el-checkbox label="1">答案解析</el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="dialog_close" @click="showDialog = false">关闭</div>
|
|
|
+ <div class="dialog_print" @click="DownloadPdfs()">下载PDF文件</div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ <div class="page_dialog">
|
|
|
+ <el-dialog title="下载PDF" class="page_dialog" :visible.sync="showReportLoading" width="500px" top="15%">
|
|
|
+ <div class="report_loading">
|
|
|
+ <div class="report_loading_icon">
|
|
|
+ <img src="@/assets/report/report_loading.png" />
|
|
|
+ </div>
|
|
|
+ <div class="report_loading_title">正在努力生成PDF中,请稍等...</div>
|
|
|
+ <div class="report_loading_progress">
|
|
|
+ <el-progress :text-inside="true" color="#2E64FA" text-color="#ffffff" :stroke-width="16"
|
|
|
+ :percentage="targetProgress"></el-progress>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { jsPDF } from "jspdf";
|
|
|
+import domtoimage from "dom-to-image";
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ showDialog: false, //是否显示弹窗
|
|
|
+ tableData: [],
|
|
|
+ progressInterval: null,
|
|
|
+ total: 0,
|
|
|
+ loading: null,
|
|
|
+ radioVal: 0,
|
|
|
+ checkVal: ['0', '1'], //默认值
|
|
|
+ pageInfo: {
|
|
|
+ //794*1123
|
|
|
+ width: 794, //595
|
|
|
+ height: 1123, //842
|
|
|
+ }, //成绩小条比例配置
|
|
|
+ showReportLoading: false, //成绩小条页加载图标
|
|
|
+ reportLoadingProgress: 0,
|
|
|
+ targetProgress: 0, //目标进度值
|
|
|
+ dialogKey: 1,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.GetSlipData(); //获取成绩小条
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 组件销毁前清除定时器
|
|
|
+ if (this.progressInterval) {
|
|
|
+ clearInterval(this.progressInterval);
|
|
|
+ this.progressInterval = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ //获取总分 成绩小条数据
|
|
|
+ GetSlipData() {
|
|
|
+ this.dialogKey += 1;
|
|
|
+ },
|
|
|
+ //导出所有pdf domtoimage的方式
|
|
|
+ async DownloadPdfs() {
|
|
|
+ this.showReportLoading = true;
|
|
|
+
|
|
|
+ // 创建PDF对象,使用自定义纸张尺寸
|
|
|
+ const pdf = new jsPDF("p", "pt", "a4"); //p 为纵向布局
|
|
|
+ const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
|
+ const pdfHeight = pdf.internal.pageSize.getHeight();
|
|
|
+
|
|
|
+ //获取所有的area_page 元素
|
|
|
+ const elements = document.querySelectorAll(".item_page");
|
|
|
+
|
|
|
+ let fillCount = 0; //完成数量
|
|
|
+ let pageCount = elements.length; //总数量
|
|
|
+ this.targetProgress = 0;
|
|
|
+ let oneProgress = Math.round((1 / pageCount) * 100);
|
|
|
+ this.StartProgress(0, oneProgress);
|
|
|
+
|
|
|
+ console.log("开始导出", this.targetProgress);
|
|
|
+
|
|
|
+ // 遍历每个页面数据
|
|
|
+ for (const [pageIndex, element] of Array.from(elements).entries()) {
|
|
|
+ // 确保页面元素存在并可见
|
|
|
+ await this.$nextTick();
|
|
|
+ if (element) {
|
|
|
+ // 确保元素可见并已渲染
|
|
|
+ // 确保元素可见并已渲染,并设置为横向排列样式
|
|
|
+ element.style.visibility = "visible";
|
|
|
+ element.offsetHeight; // 触发重排
|
|
|
+
|
|
|
+ // 等待元素完全渲染
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
+
|
|
|
+ // 使用 dom-to-image 生成图片
|
|
|
+ const dataUrl = await domtoimage.toJpeg(element, {
|
|
|
+ quality: 0.8, // 降低质量以减小文件大小
|
|
|
+ // 获取完整尺寸
|
|
|
+ width: element.scrollWidth * 2,
|
|
|
+ height: element.scrollHeight * 2,
|
|
|
+ // 提高画布分辨率以获得更清晰的图像
|
|
|
+ canvasWidth: element.scrollWidth * 2,
|
|
|
+ canvasHeight: element.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,
|
|
|
+ );
|
|
|
+ fillCount++;
|
|
|
+ // console.log("打印当前完成数量", fillCount);
|
|
|
+
|
|
|
+ // 更新进度
|
|
|
+ const progress = Math.floor((fillCount / pageCount) * 100);
|
|
|
+ const lastProgressprogress = progress - oneProgress;
|
|
|
+ // if(this.targetProgress<progress)
|
|
|
+ // {
|
|
|
+ // this.targetProgress=progress;
|
|
|
+ // }
|
|
|
+
|
|
|
+ this.StartProgress(lastProgressprogress, progress);
|
|
|
+
|
|
|
+ // 页面间短暂延迟
|
|
|
+ // await new Promise(resolve => setTimeout(resolve, 50));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存PDF文件
|
|
|
+ pdf.save(this.examName + "-" + this.subjectName + "-成绩小条.pdf");
|
|
|
+
|
|
|
+ this.showReportLoading = false; //关闭加载loading
|
|
|
+ },
|
|
|
+ // 开始进度条数值变化 最大增加进度值 maxIncrement
|
|
|
+ StartProgress(currentValue, targetValue) {
|
|
|
+ console.log("开始进度动画,目标值:", targetValue);
|
|
|
+
|
|
|
+ // 清除已存在的定时器
|
|
|
+ if (this.progressInterval) {
|
|
|
+ clearInterval(this.progressInterval);
|
|
|
+ this.progressInterval = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前进度值
|
|
|
+ if (this.targetProgress < currentValue) {
|
|
|
+ this.targetProgress = currentValue;
|
|
|
+ }
|
|
|
+ const targetProgress = targetValue;
|
|
|
+ // 如果当前值大于等于目标值,则不需要动画(进度不能递减)
|
|
|
+ if (this.targetProgress >= targetProgress) {
|
|
|
+ console.log("当前进度已达到或超过目标进度,无需动画");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算差值
|
|
|
+ const difference = targetProgress - currentValue;
|
|
|
+ console.log("差值:", difference);
|
|
|
+
|
|
|
+ // 根据差值计算时间间隔(差值越大,时间间隔越小)
|
|
|
+ // 这里设置时间间隔在50ms到100ms之间变化
|
|
|
+ const minInterval = 50;
|
|
|
+ const maxInterval = 500;
|
|
|
+ // 限制差值在1-10范围内
|
|
|
+ // 限制差值在1-10范围内
|
|
|
+ const clampedDifference = Math.max(1, Math.min(difference, 10));
|
|
|
+
|
|
|
+ // 计算时间间隔:差值1->500ms,差值10->50ms
|
|
|
+ let interval =
|
|
|
+ maxInterval -
|
|
|
+ (clampedDifference - 1) * ((maxInterval - minInterval) / 9);
|
|
|
+
|
|
|
+ // 将时间间隔调整为10的倍数
|
|
|
+ interval = Math.round(interval / 10) * 10;
|
|
|
+
|
|
|
+ // 确保时间间隔在有效范围内
|
|
|
+ interval = Math.max(minInterval, Math.min(interval, maxInterval));
|
|
|
+ // 计算每次增加的步长(差值越大,步长越大)
|
|
|
+ const step = Math.max(1, Math.floor(difference / 5)) || 1;
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ "动画参数:当前进度=",
|
|
|
+ currentValue,
|
|
|
+ "目标进度=",
|
|
|
+ targetProgress,
|
|
|
+ "差值=",
|
|
|
+ difference,
|
|
|
+ "时间间隔=",
|
|
|
+ interval,
|
|
|
+ "步长=",
|
|
|
+ step,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 创建新的定时器
|
|
|
+ this.progressInterval = setInterval(() => {
|
|
|
+ // 只能增加,不能减少
|
|
|
+ this.targetProgress = Math.min(
|
|
|
+ this.targetProgress + step,
|
|
|
+ targetProgress,
|
|
|
+ );
|
|
|
+ console.log("进度更新:", this.targetProgress);
|
|
|
+
|
|
|
+ // 如果达到目标值,清除定时器
|
|
|
+ if (this.targetProgress >= targetProgress) {
|
|
|
+ this.targetProgress = targetProgress;
|
|
|
+ console.log("进度动画完成,最终值:", this.targetProgress);
|
|
|
+ clearInterval(this.progressInterval);
|
|
|
+ this.progressInterval = null;
|
|
|
+ }
|
|
|
+ }, interval);
|
|
|
+ }
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+<style scoped lang="scss">
|
|
|
+.dialog_print_content {
|
|
|
+ width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ background: #f0f4fb;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .dialog_print_page {
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100% - 40px);
|
|
|
+ margin: auto;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .item_page {
|
|
|
+ width: 595px;
|
|
|
+ height: 842px;
|
|
|
+ margin: auto;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 0px 0px 0px 0px;
|
|
|
+ padding: 20px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .fixed_set_box {
|
|
|
+ position: absolute;
|
|
|
+ top: 20px;
|
|
|
+ left: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item_set {
|
|
|
+ width: 160px;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 12px 14px 12px 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ :deep(.el-radio-group) {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .el-radio {
|
|
|
+ margin-right: 0;
|
|
|
+ line-height: 22px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333333;
|
|
|
+
|
|
|
+ .el-radio__input {
|
|
|
+ &.is-checked+.el-radio__label {
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-checked .el-radio__inner {
|
|
|
+ border-color: #2e64fa;
|
|
|
+ background-color: #ffff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-radio__inner {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ border-color: rgba(0, 0, 0, 0.2);
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ background-color: #2e64fa;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-radio__label {
|
|
|
+ padding-left: 3px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-checkbox-group) {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .el-checkbox {
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333333;
|
|
|
+ margin-right: 0;
|
|
|
+
|
|
|
+ .el-checkbox__input {
|
|
|
+ .el-checkbox__inner {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ left: 5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-checked+.el-checkbox__label {
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-checkbox__label {
|
|
|
+ padding-left: 3px;
|
|
|
+ line-height: 22px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .dialog_close {
|
|
|
+ position: absolute;
|
|
|
+ right: 20px;
|
|
|
+ top: 20px;
|
|
|
+ width: 160px;
|
|
|
+ height: 42px;
|
|
|
+ background: rgba(245, 108, 108, 0.1);
|
|
|
+ border-radius: 4px 4px 4px 4px;
|
|
|
+ border: 1px solid #f56c6c;
|
|
|
+ line-height: 42px;
|
|
|
+ text-align: center;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #f56c6c;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dialog_print {
|
|
|
+ position: absolute;
|
|
|
+ right: 20px;
|
|
|
+ top: 82px;
|
|
|
+ width: 160px;
|
|
|
+ height: 44px;
|
|
|
+ background: #2e64fa;
|
|
|
+ border-radius: 4px 4px 4px 4px;
|
|
|
+ line-height: 42px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #ffffff;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|