bookFlip.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. <template>
  2. <div :class="['report_page', { 'full_screen': isShowFullScreen }]">
  3. <!-- 点击封面进入全屏 -->
  4. <div class="joint_print_area">
  5. <!-- 点击封面进入全屏预览模式 -->
  6. <!-- <BookCover :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle" :showButton="true" :requestLoading="requestLoading" :openLoading="openLoading" @openFullScreen="openFullScreen" :style="{width:`${bookWidth}px`,height:`${bookHeight}px`,margin:'0 auto'}" v-if="!isShowFullScreen"></BookCover> -->
  7. <!-- 全屏预览 -->
  8. <div id="canvas" v-if="isShowFullScreen">
  9. <!-- 右上角放大缩小按钮 -->
  10. <div class="close-zoom-icon" @click="CloseFullScreen"><img class="close" src="@/assets/icon/close.webp" /><span>关闭全屏</span></div>
  11. <div class="magazine-viewport">
  12. <div class="container">
  13. <!-- 翻书容器 -->
  14. <div class="magazine" id="flipbook" ref="bookContainer">
  15. <!-- 封面 -->
  16. <BookCover :showButton="false" :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle"></BookCover>
  17. <div class="area_page" v-for="(page, index) in bookPages" :key="index">
  18. <!-- 插槽:自定义每页内容 -->
  19. <slot :page="index + 1" :content="page"></slot>
  20. <!-- <div class="area_page_number">第 {{ currentPage }} 页</div> -->
  21. </div>
  22. <!-- 封面 -->
  23. <BookCover :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
  24. <template v-if="bookPages.length % 2 !== 0">
  25. <BookCover :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
  26. </template>
  27. </div>
  28. </div>
  29. <!-- Next button -->
  30. <div ignore="1" class="next-button"><i class="el-icon-arrow-right"></i></div>
  31. <!-- Previous button -->
  32. <div ignore="1" class="previous-button"><i class="el-icon-arrow-left"></i></div>
  33. </div>
  34. <!-- 翻页控制按钮 -->
  35. <div class="thumbnails">
  36. <div class="book_controls">
  37. <div class="zoom">
  38. <img class="zoom_icon zoom_in_icon" src="@/assets/icon/enlarge_icon.png" />
  39. <img class="zoom_icon zoom_out_icon" src="@/assets/icon/shrink_icon.png" />
  40. </div>
  41. <div class="page_size">
  42. <span class="butn_prev" @click="prevPage"><img src="@/assets/report/turn_prev.webp" /></span>
  43. <span class="page_info">{{ currentPage }}{{ bookPages.length }}</span>
  44. <span class="butn_next" @click="nextPage"><img src="@/assets/report/turn_next.webp" /></span>
  45. </div>
  46. <el-button class="download" type="primary" :loading="loading" @click="DownloadPdf">下载报告册</el-button>
  47. </div>
  48. </div>
  49. <!-- <div class="page_dialog">
  50. <el-dialog title="下载PDF" class="page_dialog" :visible.sync="showReportLoading" width="500px" top="15%" append-to-body>
  51. <div class="report_loading">
  52. <div class="report_loading_icon">
  53. <img src="@/assets/report/report_loading.png">
  54. </div>
  55. <div class="report_loading_title">正在努力生成PDF中,请稍等...</div>
  56. <div class="report_loading_progress">
  57. <el-progress :text-inside="true" color="#2E64FA" text-color="#ffffff" :stroke-width="16" :percentage="targetProgress"></el-progress>
  58. </div>
  59. </div>
  60. </el-dialog>
  61. </div> -->
  62. </div>
  63. <!-- 下载模版 -->
  64. <!-- style="position: absolute;top: -9999999px;z-index: -10;" -->
  65. <div class="magazine-viewport web_mode">
  66. <el-button class="download_btn" size="small" type="primary" :loading="pdfLoading" @click="DownloadPdfNew">下载PDF</el-button>
  67. <!-- 封面 -->
  68. <BookCover class="web_cover web_area_page" :showButton="false" :bookBg="`${bookBg}_bg`" :title="coverTitle" :subtitle="subtitle"></BookCover>
  69. <slot type="web_mode"></slot>
  70. <!-- 封面 -->
  71. <BookCover class="web_cover web_area_page" :showButton="false" :isCover="true" :bookBg="`${bookBg}_cover_bg`"></BookCover>
  72. </div>
  73. </div>
  74. <div class="page_dialog">
  75. <el-dialog title="下载PDF" class="page_dialog" :visible.sync="showReportLoading" width="500px" top="15%" append-to-body>
  76. <div class="report_loading">
  77. <div class="report_loading_icon">
  78. <img src="@/assets/report/report_loading.png">
  79. </div>
  80. <div class="report_loading_title">正在努力生成PDF中,请稍等...</div>
  81. <div class="report_loading_progress">
  82. <el-progress :text-inside="true" color="#2E64FA" text-color="#ffffff" :stroke-width="16" :percentage="targetProgress"></el-progress>
  83. </div>
  84. </div>
  85. </el-dialog>
  86. </div>
  87. </div>
  88. </template>
  89. <script>
  90. // import $ from 'jquery'; // 先导入 jQuery
  91. // import 'turn.js';
  92. import turn from '@/utils/turn.js';
  93. import zoom from './js/zoom.min.js';
  94. import BookCover from './bookCover.vue';
  95. import { disableControls, addPage,largeMagazineWidth,resizeViewport,setArrows,loadLargePage,loadSmallPage } from './js/magazine.js';
  96. import { jsPDF } from "jspdf";
  97. import html2canvas from "html2canvas";
  98. import domtoimage from 'dom-to-image';
  99. import { mapGetters } from "vuex";
  100. export default {
  101. props: {
  102. // 书页数据(数组长度建议为偶数)
  103. bookPages: {
  104. type: Array,
  105. required: true,
  106. default: () => []
  107. },
  108. bookBg:{
  109. type:String,
  110. default: 'grade_leader'
  111. },
  112. coverTitle:{//封面标题
  113. type:String,
  114. default: '联校总分分析报告册'
  115. },
  116. subtitle:{//封面副标题
  117. type:String,
  118. default: '联校总分分析报告册'
  119. },
  120. requestLoading:{
  121. type:Boolean,
  122. default: false
  123. },//接口请求的loading
  124. openLoading:{
  125. type:Boolean,
  126. default: false
  127. },//开发全屏预览
  128. },
  129. components:{BookCover},
  130. data() {
  131. return {
  132. isShowFullScreen: false,
  133. bookHeight:780,//封面高度
  134. bookWidth:600,//封面宽度
  135. currentPage: '', // 当前页码
  136. totalPages: 0, // 总页数(由 turn.js 计算)
  137. showReportLoading:false,
  138. targetProgress:0,
  139. loading:false,
  140. pdfLoading:false,
  141. };
  142. },
  143. computed: {
  144. ...mapGetters(["userInfo"]),
  145. reportTitle() {
  146. return this?.$store?.state?.report?.examSelectItem?.examName ?? '';
  147. }
  148. },
  149. mounted() {
  150. //设置书本宽度和高度
  151. this.setBookSize();
  152. // 监听窗口大小变化,实时更新
  153. window.addEventListener('resize', this.setBookSize);
  154. },
  155. beforeDestroy() {
  156. // 销毁翻书实例,避免内存泄漏
  157. if (this.$refs.bookContainer) {
  158. $(this.$refs.bookContainer).turn('destroy');
  159. }
  160. window.removeEventListener('resize', this.setBookSize);
  161. },
  162. methods: {
  163. //设置书本宽度和高度
  164. setBookSize(){
  165. const bookHeight = $(window).height() - 120;//书本高度
  166. this.bookHeight = bookHeight;
  167. this.bookWidth = Math.round(bookHeight * 0.706);// 书本宽度 0.706 宽高的比例
  168. },
  169. //点击查看全屏
  170. openFullScreen(){
  171. this.$emit('OpenBookImages');
  172. },
  173. //打开全屏预览
  174. showFullScreen() {
  175. $('#canvas').hide();
  176. this.isShowFullScreen = true;//是否显示全屏
  177. this.initBook(); // 初始化翻书效果
  178. },
  179. // 初始化翻书效果
  180. initBook() {
  181. const b_height = $(window).height() - 120;//书本高度
  182. const b_width = Math.round(b_height * 0.706) * 2;// 书本宽度 0.706 宽高的比例
  183. this.$nextTick(() => {
  184. $('#canvas').fadeIn(1000);
  185. const $book = $(this.$refs.bookContainer);
  186. if ($book.width() == 0 || $book.height() == 0) {
  187. setTimeout(loadApp, 10);
  188. return;
  189. }
  190. // 初始化 turn.js
  191. const _that = this;
  192. const totalPages = this.bookPages.length;//总页数
  193. $book.turn({
  194. // 页面尺寸(A4 高度示例)
  195. // height: 1285, // 单页高度(对应 A4 72dpi 高度)
  196. // width: 1816, // 单页宽度(对应 A4 72dpi 宽度)908
  197. width: b_width, // 书本宽度
  198. height: b_height, // 书本高度
  199. duration: 1000, //翻页速度,值越小越快
  200. autoCenter: true, // 自动居中
  201. acceleration: false,//设置硬件加速模式,用于触摸设备此值必须是真的
  202. gradients: true,//置翻页时是否显示翻页跟阴影
  203. display: 'double', // 双页显示(适合PC端)
  204. elevation: 50, // 翻页阴影高度(增强立体感)
  205. pages: totalPages,
  206. when: {
  207. // 翻页前触发
  208. turning: function (event, page, view) {
  209. const book = $(this);
  210. const pages = book.turn('pages') - 2;
  211. if(page > 1 && page < pages){
  212. console.log(page , pages)
  213. const prev = view[0] - 1;
  214. const next = view[1] < pages ? view[1] - 1 : '';
  215. _that.currentPage = next?`${prev}-${next}/`:`${prev}/`;
  216. }else{
  217. _that.currentPage = '';
  218. }
  219. // 显示和隐藏按钮
  220. disableControls(page);
  221. },
  222. // 翻页后触发
  223. turned: function (event, page, view) {
  224. // 显示和隐藏按钮
  225. disableControls(page);
  226. $(this).turn('center');
  227. if (page == 1) {
  228. // 表示调用翻页 API 并指定翻页效果为 “Peel( peeling,即 “剥开” 效果)”,且翻页的起始位置为右下角(br 是 bottom right 的缩写)
  229. $(this).turn('peel', 'br');
  230. }
  231. },
  232. // missing: function (event, pages) {
  233. // // Add pages that aren't in the magazine
  234. // for (var i = 0; i < pages.length; i++){
  235. // addPage(pages[i], $(this));
  236. // }
  237. // }
  238. }
  239. });
  240. // 初始化页码信息
  241. // this.totalPages = $book.turn('pages');
  242. // Zoom.js
  243. $('#canvas .magazine-viewport').zoom({
  244. flipbook: $('.magazine'),
  245. max: function () {
  246. return largeMagazineWidth() / $('.magazine').width();
  247. },
  248. when: {
  249. swipeLeft: function () {
  250. $(this).zoom('flipbook').turn('next');
  251. },
  252. swipeRight: function () {
  253. $(this).zoom('flipbook').turn('previous');
  254. },
  255. resize: function (event, scale, page, pageElement) {
  256. // if (scale == 1)
  257. // loadSmallPage(page, pageElement);
  258. // else
  259. // loadLargePage(page, pageElement);
  260. },
  261. zoomIn: function () {
  262. $('.made').hide();
  263. $('.magazine').removeClass('animated').addClass('zoom-in');
  264. $('.zoom-icon').removeClass('zoom-icon-in').addClass('zoom-icon-out');
  265. // if (!window.escTip && !$.isTouch) {
  266. // escTip = true;
  267. // $('<div />', { 'class': 'exit-message' }).
  268. // html('<div>Press ESC to exit</div>').
  269. // appendTo($('body')).
  270. // delay(2000).
  271. // animate({ opacity: 0 }, 500, function () {
  272. // $(this).remove();
  273. // });
  274. // }
  275. },
  276. zoomOut: function () {
  277. $('.exit-message').hide();
  278. $('.thumbnails').fadeIn();
  279. $('.made').fadeIn();
  280. $('.zoom-icon').removeClass('zoom-icon-out').addClass('zoom-icon-in');
  281. setTimeout(function () {
  282. $('.magazine').addClass('animated').removeClass('zoom-in');
  283. resizeViewport();
  284. }, 0);
  285. }
  286. }
  287. });
  288. $(document).keydown(function (e) {
  289. const previous = 37, next = 39, esc = 27;
  290. switch (e.keyCode) {
  291. case previous:
  292. // left arrow
  293. $('.magazine').turn('previous');
  294. e.preventDefault();
  295. break;
  296. case next:
  297. //right arrow
  298. $('.magazine').turn('next');
  299. e.preventDefault();
  300. break;
  301. case esc:
  302. $('#canvas .magazine-viewport').zoom('zoomOut');
  303. e.preventDefault();
  304. break;
  305. }
  306. });
  307. $(window).resize(function () {
  308. resizeViewport();
  309. }).bind('orientationchange', function () {
  310. resizeViewport();
  311. });
  312. // Events for the next button
  313. $('.next-button').click(function () {
  314. $('.magazine').turn('next');
  315. setTimeout(function () {
  316. setArrows();
  317. }, 300);
  318. });
  319. // Events for the next button
  320. $('.previous-button').bind($.mouseEvents.over, function () {
  321. $(this).addClass('previous-button-hover');
  322. }).bind($.mouseEvents.out, function () {
  323. $(this).removeClass('previous-button-hover');
  324. }).bind($.mouseEvents.down, function () {
  325. $(this).addClass('previous-button-down');
  326. }).bind($.mouseEvents.up, function () {
  327. $(this).removeClass('previous-button-down');
  328. }).click(function () {
  329. $('.magazine').turn('previous');
  330. setTimeout(function () {
  331. setArrows();
  332. }, 300);
  333. });
  334. // Zoom icon
  335. $('.zoom_in_icon').on('click', function () {
  336. $('#canvas .magazine-viewport').zoom('zoomIn');
  337. });
  338. $('.zoom_out_icon').on('click', function () {
  339. $('#canvas .magazine-viewport').zoom('zoomOut');
  340. });
  341. resizeViewport();
  342. $('.magazine').addClass('animated');
  343. })
  344. },
  345. //退出全屏
  346. CloseFullScreen() {
  347. this.isShowFullScreen = false;
  348. },
  349. // 上一页
  350. prevPage() {
  351. $('.magazine').turn('previous');
  352. setTimeout(function () {
  353. setArrows();
  354. }, 300);
  355. },
  356. // 下一页
  357. nextPage() {
  358. $('.magazine').turn('next');
  359. setTimeout(function () {
  360. setArrows();
  361. }, 300);
  362. },
  363. async DownloadPdf() {
  364. this.loading = true;
  365. this.targetProgress = 1;
  366. // this.showReportLoading = true;
  367. //获取所有的area_page 元素
  368. const elements = document.querySelectorAll(".web_mode .web_cover");
  369. const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
  370. const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
  371. const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
  372. let yPos = 0; //当前图像在pdf页面上的垂直位置的变量
  373. let i = 1;
  374. let imageList = [];
  375. // 遍历每个 area_page 元素并按顺序处理
  376. for (const element of elements) {
  377. await html2canvas(element, {
  378. scale: 2, // 增加缩放比例
  379. useCORS: true, // 允许跨域
  380. logging: true, // 是否显示进度条
  381. letterRendering: true, // 文字抗锯齿 启用字母渲染
  382. }).then(async(canvas) => {
  383. // 将 Canvas 对象转换为 PNG 格式的 Data URL 字符串
  384. const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
  385. const imgUrl = URL.createObjectURL(blob);
  386. imageList.push(imgUrl)
  387. });
  388. }
  389. imageList.splice(1, 0, ...this.bookPages);
  390. // console.log(imageList)
  391. const stepLens = imageList?.length || 1;
  392. for (const imgData of imageList) {
  393. const imgProps = pdf.getImageProperties(imgData); // 获取图像的属性,包括宽度和高度
  394. const imgWidth = imgProps.width;
  395. const imgHeight = imgProps.height;
  396. const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); //计算图片的缩放比例
  397. const adjustWidth = imgWidth * ratio;
  398. const adjustHeight = imgHeight * ratio;
  399. // 在添加每个图像之前,检查当前页面的高度是否足够。如果不够,则添加新页面,并将 yPos 重置为 0
  400. if (yPos + adjustHeight > pdfHeight) {
  401. pdf.addPage();
  402. yPos = 0;
  403. }
  404. // 将图像添加到pdf中
  405. pdf.addImage(imgData, "PNG", 0, yPos, adjustWidth, adjustHeight);
  406. yPos += adjustHeight;
  407. // 如果添加图像后剩余空间不足一页,则添加新页面
  408. if (yPos > pdfHeight) {
  409. pdf.addPage();
  410. yPos = 0;
  411. }
  412. const currentProgress = Math.min(Math.round((i / stepLens) * 100),100);
  413. this.targetProgress = currentProgress;
  414. i++;
  415. }
  416. // 保存pdf文件
  417. pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
  418. // this.showReportLoading = false; //关闭加载loading
  419. this.loading = false;
  420. },
  421. // async DownloadPdfNew() {
  422. // //获取所有的area_page 元素
  423. // const elements = document.querySelectorAll(".web_mode .web_area_page");
  424. // this.loading = true;
  425. // // this.targetProgress = 1;
  426. // // this.showReportLoading = true;
  427. // const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
  428. // const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
  429. // const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
  430. // let yPos = 0; //当前图像在pdf页面上的垂直位置的变量
  431. // // let i = 1;
  432. // let imageList = [];
  433. // // 遍历每个 area_page 元素并按顺序处理
  434. // for (const element of elements) {
  435. // await html2canvas(element, {
  436. // scale: 2, // 增加缩放比例
  437. // useCORS: true, // 允许跨域
  438. // logging: true, // 是否显示进度条
  439. // letterRendering: true, // 文字抗锯齿 启用字母渲染
  440. // }).then(async(canvas) => {
  441. // // 将 Canvas 对象转换为 PNG 格式的 Data URL 字符串
  442. // const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
  443. // const imgUrl = URL.createObjectURL(blob);
  444. // imageList.push(imgUrl)
  445. // });
  446. // }
  447. // // const stepLens = imageList?.length || 1;
  448. // for (const imgData of imageList) {
  449. // const imgProps = pdf.getImageProperties(imgData); // 获取图像的属性,包括宽度和高度
  450. // const imgWidth = imgProps.width;
  451. // const imgHeight = imgProps.height;
  452. // const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); //计算图片的缩放比例
  453. // const adjustWidth = imgWidth * ratio;
  454. // const adjustHeight = imgHeight * ratio;
  455. // // 在添加每个图像之前,检查当前页面的高度是否足够。如果不够,则添加新页面,并将 yPos 重置为 0
  456. // if (yPos + adjustHeight > pdfHeight) {
  457. // pdf.addPage();
  458. // yPos = 0;
  459. // }
  460. // // 将图像添加到pdf中
  461. // pdf.addImage(imgData, "PNG", 0, yPos, adjustWidth, adjustHeight);
  462. // yPos += adjustHeight;
  463. // // 如果添加图像后剩余空间不足一页,则添加新页面
  464. // if (yPos > pdfHeight) {
  465. // pdf.addPage();
  466. // yPos = 0;
  467. // }
  468. // // const currentProgress = Math.min(Math.round((i / stepLens) * 100),100);
  469. // // this.targetProgress = currentProgress;
  470. // // i++;
  471. // }
  472. // // 保存pdf文件
  473. // pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
  474. // // this.showReportLoading = false; //关闭加载loading
  475. // this.loading = false;
  476. // this.$emit('PdfLoadEnd');
  477. // },
  478. async DownloadPdfNew() {
  479. //获取所有的area_page 元素
  480. const elements = document.querySelectorAll(".web_mode .web_area_page");
  481. const elLens = elements.length;
  482. const elLensIndex = elLens - 1;
  483. this.pdfLoading = true;
  484. this.targetProgress = 0;
  485. this.showReportLoading = true;
  486. const itemProgress = elLens > 0 ? Math.ceil(100 / elLens) : 0;
  487. const pdf = new jsPDF("p", "pt", "a4"); // 'p'表示纵向,'a4'表示A4纸张尺寸
  488. const pdfWidth = pdf.internal.pageSize.getWidth(); //获取pdf的宽度
  489. const pdfHeight = pdf.internal.pageSize.getHeight(); //获取pdf的高度
  490. // 遍历每个 area_page 元素并按顺序处理
  491. for (const [pageIndex, element] of Array.from(elements).entries()) {
  492. // 确保页面元素存在并可见
  493. await this.$nextTick();
  494. if (element) {
  495. // 确保元素可见并已渲染
  496. // 确保元素可见并已渲染,并设置为横向排列样式
  497. element.style.visibility = 'visible';
  498. element.offsetHeight; // 触发重排
  499. // 等待元素完全渲染
  500. await new Promise(resolve => setTimeout(resolve, 100));
  501. // 使用 dom-to-image 生成图片
  502. const dataUrl = await domtoimage.toJpeg(element, {
  503. quality: 0.8,// 降低质量以减小文件大小
  504. // 获取完整尺寸
  505. width: element.scrollWidth*2,
  506. height: element.scrollHeight*2,
  507. // 提高画布分辨率以获得更清晰的图像
  508. canvasWidth: element.scrollWidth * 2,
  509. canvasHeight: element.scrollHeight * 2,
  510. style: {
  511. 'transform': 'scale(2)',
  512. 'transform-origin': 'top left',
  513. 'background-color': '#ffffff',
  514. // 关键:背景图尺寸适配原元素
  515. 'background-size': `${element.scrollWidth}px ${element.scrollHeight}px`, // 优先铺满且完整显示
  516. 'background-position': 'top left',
  517. 'background-repeat': 'no-repeat', // 防止背景图重复导致视觉不完整
  518. 'overflow': 'visible',
  519. 'white-space': 'normal',
  520. 'position': 'relative',
  521. 'visibility': 'visible'
  522. },
  523. bgcolor: '#ffffff'
  524. });
  525. // 获取图像属性并计算缩放比例
  526. const imgProps = pdf.getImageProperties(dataUrl);
  527. const imgWidth = imgProps.width;
  528. const imgHeight = imgProps.height;
  529. // 计算缩放比例(保持宽高比)
  530. const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
  531. const adjustWidth = imgWidth * ratio;
  532. const adjustHeight = imgHeight * ratio;
  533. // 居中放置图像
  534. const xPosition = (pdfWidth - adjustWidth) / 2;
  535. const yPosition = (pdfHeight - adjustHeight) / 2;
  536. // console.log("打印pdf图片尺寸", imgWidth, imgHeight);
  537. // console.log("打印pdf页面尺寸", pdfWidth, pdfHeight);
  538. // console.log("打印pdf缩放比例", ratio);
  539. // console.log("打印pdf缩放后尺寸",adjustWidth, adjustHeight);
  540. // console.log("打印pdf图片位置", xPosition, yPosition);
  541. // 如果不是第一页,添加新页面
  542. if (pageIndex > 0)
  543. {
  544. pdf.addPage();
  545. }
  546. //处理封面图铺满屏
  547. let pdfAdjustHeight = adjustHeight;
  548. if(pageIndex == 0 || pageIndex == elLensIndex){
  549. pdfAdjustHeight = adjustHeight + 6
  550. }
  551. pdf.addImage(dataUrl, "JPEG", xPosition, yPosition - 6, adjustWidth, pdfAdjustHeight);
  552. this.targetProgress += itemProgress;
  553. // 边界处理:进度值不超过100%
  554. this.targetProgress = Math.min(this.targetProgress, 100);
  555. }
  556. }
  557. // 保存pdf文件
  558. pdf.save(`${this.reportTitle}_${this.coverTitle}.pdf`);
  559. this.showReportLoading = false; //关闭加载loading
  560. this.targetProgress = 0;
  561. this.pdfLoading = false;
  562. this.$emit('PdfLoadEnd');
  563. },
  564. }
  565. };
  566. </script>
  567. <style scoped lang="scss">
  568. @import '../components/css/magazine.css'; // 引入 SCSS 文件
  569. .report_page {
  570. display: flex;
  571. flex-direction: column;
  572. align-items: center;
  573. padding: 0;
  574. box-sizing: border-box;
  575. background-color: #F0F4FB;
  576. }
  577. /* 书本容器样式 */
  578. // #flipbook {
  579. // box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
  580. // }
  581. /* 单页样式(注意:宽度为书本的一半,因为默认双页显示) */
  582. // .joint_print_area {
  583. // width: 50%;
  584. // height: 100%;
  585. // background-color: #fff;
  586. // overflow: hidden;
  587. // }
  588. // .area_page {
  589. // width: 100%;
  590. // height: 100%;
  591. // padding: 32px;
  592. // box-sizing: border-box;
  593. // // background: #FFFFFF;
  594. // box-shadow: inset 1px 0px 0px 0px #EEEEEE;
  595. // }
  596. /* 控制按钮样式 */
  597. .book_controls {
  598. display: flex;
  599. align-items: center;
  600. justify-content: space-between;
  601. width: 100%;
  602. height: 60px;
  603. background: #FFFFFF;
  604. // margin-top: 40px;
  605. padding: 12px 24px;
  606. box-sizing: border-box;
  607. .zoom {
  608. display: inline-flex;
  609. align-items: center;
  610. .zoom_icon {
  611. width: 32px;
  612. height: 32px;
  613. margin-right: 20px;
  614. cursor: pointer;
  615. }
  616. }
  617. .page_size {
  618. display: inline-flex;
  619. align-items: center;
  620. .butn_prev{
  621. width: 24px;
  622. height: 24px;
  623. margin-right: 15px;
  624. cursor: pointer;
  625. }
  626. .butn_next{
  627. width: 24px;
  628. height: 24px;
  629. margin-left: 15px;
  630. cursor: pointer;
  631. }
  632. img{
  633. width: 24px;
  634. height: 24px;
  635. }
  636. .page_info {
  637. font-weight: 500;
  638. font-size: 16px;
  639. color: #333333;
  640. }
  641. }
  642. .download {
  643. padding: 8px 16px;
  644. height: 36px;
  645. background-color: #2E64FA;
  646. border-radius: 4px;
  647. font-size: 14px;
  648. color: #FFFFFF;
  649. border: none;
  650. cursor: pointer;
  651. &:disabled {
  652. background-color: #ccc;
  653. cursor: not-allowed;
  654. }
  655. }
  656. }
  657. </style>