Browse Source

研报下载pdf修改

yujinwen 2 months ago
parent
commit
86b38c721f
4 changed files with 395 additions and 71 deletions
  1. 2 1
      package.json
  2. 287 59
      src/hooks/useDownLoadFile.js
  3. 82 10
      src/views/report/List.vue
  4. 24 1
      src/views/report/PreviewDetail.vue

+ 2 - 1
package.json

@@ -32,6 +32,7 @@
     "minio-es": "0.0.2",
     "moment": "^2.29.4",
     "normalize.css": "^8.0.1",
+    "pdf-lib": "^1.17.1",
     "pinia": "^2.0.36",
     "pptxgenjs": "^3.12.0",
     "rollup-plugin-node-polyfills": "^0.2.1",
@@ -39,8 +40,8 @@
     "vant": "^4.6.4",
     "vconsole": "^3.15.0",
     "vue": "^3.5.13",
-    "vue-qr": "^4.0.9",
     "vue-i18n": "^10.0.1",
+    "vue-qr": "^4.0.9",
     "vue-router": "^4.1.6",
     "vue3-clipboard": "^1.0.0",
     "vue3-tree-org": "^4.2.2",

+ 287 - 59
src/hooks/useDownLoadFile.js

@@ -1,11 +1,11 @@
 // 下载文件
 import axios from "axios";
 import { showToast } from "vant";
-import {ref} from 'vue'
-
+import { ref } from "vue";
+import { PDFDocument } from "pdf-lib";
 
 /**
- * 
+ * 下载文件
  * @returns {
  *  progress 下载进度
  *  fileSize 文件大小
@@ -13,63 +13,291 @@ import {ref} from 'vue'
  *  cancelDownload  取消下载
  * }
  */
-export function useDownLoadFile(){
-    let progress=ref(0)//进度 0-1
-    let fileSize=ref(0)//文件大小
-
-    const controller = new AbortController();
-
-    // 开始下载
-    const startDownload=(url,filename)=>{
-        axios({
-            url:url,
-            method:'get',
-            responseType:'blob',
-            signal: controller.signal,
-            onDownloadProgress:function(progressEvent){
-                // console.log(progressEvent);
-                fileSize.value=Math.floor(progressEvent.total/1024/1024)
-                progress.value = Math.floor(progressEvent.progress*100)
-            }
-        }).then(res=>{
-            // console.log(res);
-            const {status,data}=res
-            if(status!=200){
-                showToast('下载失败')
-                return
-            }
-            const content = data
-            const blob = new Blob([content])
-            if ('download' in document.createElement('a')) {
-                const elink = document.createElement('a')
-                elink.download = filename || url.split('/')[url.split('/').length - 1]
-                elink.style.display = 'none'
-                elink.href = window.URL.createObjectURL(blob)
-                document.body.appendChild(elink)
-                elink.click()
-                window.URL.revokeObjectURL(elink.href)
-                document.body.removeChild(elink)
-            } else {
-                navigator.msSaveBlob(blob, filename)
+export function useDownLoadFile() {
+  let progress = ref(0); //进度 0-1
+  let fileSize = ref(0); //文件大小
+
+  const controller = new AbortController();
+
+  // 开始下载
+  const startDownload = (url, filename) => {
+    axios({
+      url: url,
+      method: "get",
+      responseType: "blob",
+      signal: controller.signal,
+      onDownloadProgress: function (progressEvent) {
+        // console.log(progressEvent);
+        fileSize.value = Math.floor(progressEvent.total / 1024 / 1024);
+        progress.value = Math.floor(progressEvent.progress * 100);
+      },
+    })
+      .then((res) => {
+        // console.log(res);
+        const { status, data } = res;
+        if (status != 200) {
+          showToast("下载失败");
+          return;
+        }
+        const content = data;
+        const blob = new Blob([content]);
+        if ("download" in document.createElement("a")) {
+          const elink = document.createElement("a");
+          elink.download = filename || url.split("/")[url.split("/").length - 1];
+          elink.style.display = "none";
+          elink.href = window.URL.createObjectURL(blob);
+          document.body.appendChild(elink);
+          elink.click();
+          window.URL.revokeObjectURL(elink.href);
+          document.body.removeChild(elink);
+        } else {
+          navigator.msSaveBlob(blob, filename);
+        }
+      })
+      .catch((e) => {
+        console.log(e);
+        showToast("下载失败");
+      });
+  };
+
+  //取消下载
+  const cancelDownload = () => {
+    // 取消请求
+    controller.abort();
+    progress.value = 0;
+    fileSize.value = 0;
+  };
+
+  return {
+    fileSize,
+    progress,
+    startDownload,
+    cancelDownload,
+  };
+}
+
+/**
+ * 下载文件加水印
+ */
+export function useDownLoadFileAddWaterMark() {
+  let progress = ref(0); //进度 0-1
+  let fileSize = ref(0); //文件大小
+
+  const controller = new AbortController();
+
+  // 获取水印图片数据
+  function getWaterMark(str) {
+    return new Promise((resolve, reject) => {
+      const text = str || ""; // 默认水印文字
+      const canvas = document.createElement("canvas");
+
+      // 设置canvas尺寸,增加分辨率
+      const canvasWidth = 800;
+      const canvasHeight = 400;
+      canvas.width = canvasWidth;
+      canvas.height = canvasHeight;
+
+      const ctx = canvas.getContext("2d");
+
+      // 增大字体尺寸并设置样式
+      ctx.font = "50px Arial"; // 字体大小
+      ctx.textAlign = "center"; // 水平居中
+      ctx.textBaseline = "middle"; // 垂直居中
+      ctx.fillStyle = "#333"; // 水印文字颜色
+      ctx.globalAlpha = 0.1; // 水印透明度,避免过于显眼
+
+      ctx.translate(canvasWidth / 2, canvasHeight / 2); // 移动到中心
+      ctx.rotate((-45 * Math.PI) / 200); // 旋转 -45 度
+      ctx.fillText(text, 0, 0); // 在旋转后的中心绘制文字
+
+      // 将canvas内容转换为base64编码的PNG图片
+      const data = canvas.toDataURL("image/png");
+      resolve(data);
+    });
+  }
+
+  // 辅助方法:将 Blob 转换为 ArrayBuffer
+  function blobToArrayBuffer(blob) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = () => resolve(reader.result);
+      reader.onerror = (error) => reject(error);
+      reader.readAsArrayBuffer(blob);
+    });
+  }
+
+  /**
+   * 开始下载
+   * @param type pdf img
+   * @returns
+   */
+  const startDownload = async ({ url, filename, waterMark, type }) => {
+    try {
+      const res = await axios({
+        url: url,
+        method: "get",
+        responseType: "blob",
+        signal: controller.signal,
+        onDownloadProgress: function (progressEvent) {
+          // console.log(progressEvent);
+          fileSize.value = Math.floor(progressEvent.total / 1024 / 1024);
+          progress.value = Math.floor(progressEvent.progress * 100);
+        },
+      });
+      // console.log(res);
+      const { status, data } = res;
+      if (status != 200) {
+        showToast("下载失败");
+        return;
+      }
+      const blob = new Blob([data]);
+      // 不需要水印
+      if (!waterMark) {
+        if ("download" in document.createElement("a")) {
+          const elink = document.createElement("a");
+          elink.download = filename || url.split("/")[url.split("/").length - 1];
+          elink.style.display = "none";
+          elink.href = window.URL.createObjectURL(blob);
+          document.body.appendChild(elink);
+          elink.click();
+          window.URL.revokeObjectURL(elink.href);
+          document.body.removeChild(elink);
+        } else {
+          navigator.msSaveBlob(blob, filename);
+        }
+        return;
+      }
+
+      if (type === "pdf") {
+				// 加载 PDF 文档 将 blob 转化为 `string` or `Uint8Array` or `ArrayBuffer`
+				const pdfBytes = await blobToArrayBuffer(blob); // 将 Blob 转为 ArrayBuffer
+				const pdfDoc = await PDFDocument.load(pdfBytes);
+				const pages = pdfDoc.getPages();
+
+				// 获取水印图像
+				const watermarkImageData = await getWaterMark(waterMark);
+				const watermarkImage = await pdfDoc.embedPng(watermarkImageData);
+
+				pages.forEach(page => {
+					const { width, height } = page.getSize();
+
+					// 水印图像的大小 (适应页面大小并保持比例)
+					const imageWidth = width / 5;  // 水印宽度为页面宽度的1/5
+					const imageHeight = watermarkImage.height * (imageWidth / watermarkImage.width);
+
+					// 设置水印透明度
+					const opacity = 1;  // 控制透明度
+
+					// 计算水印重复的个数
+					const cols = Math.ceil(width / imageWidth);  // 水印在水平方向的个数
+					const rows = Math.ceil(height / imageHeight); // 水印在垂直方向的个数
+
+					// 在页面上绘制水印(按格子重复)
+					for (let row = 0; row < rows; row++) {
+						for (let col = 0; col < cols; col++) {
+							const x = col * imageWidth;
+							const y = row * imageHeight;
+
+							page.drawImage(watermarkImage, {
+								x: x,
+								y: y,
+								width: imageWidth,
+								height: imageHeight,
+								opacity: opacity,  // 设置透明度
+							});
+						}
+					}
+				});
+
+				// 导出加水印后的 PDF
+				const modifiedPdfBytes = await pdfDoc.save();
+				const blobRes = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
+				if ('download' in document.createElement('a')) {
+					const elink = document.createElement('a');
+					elink.download = filename || url.split("/")[url.split("/").length - 1];
+					elink.style.display = 'none';
+					elink.href = window.URL.createObjectURL(blobRes);
+					document.body.appendChild(elink);
+					elink.click();
+					window.URL.revokeObjectURL(elink.href);
+					document.body.removeChild(elink);
+				} else {
+					navigator.msSaveBlob(blobRes, filename);
+				}
+
+      } else if (type === "img") {
+        // 处理图片加水印
+        const img = new Image();
+        img.src = URL.createObjectURL(blob);
+
+        img.onload = async () => {
+          // 创建canvas并绘制图片
+          const canvas = document.createElement("canvas");
+          const ctx = canvas.getContext("2d");
+          canvas.width = img.width;
+          canvas.height = img.height;
+
+          // 绘制原始图片
+          ctx.drawImage(img, 0, 0, img.width, img.height);
+
+          // 获取水印图像
+          const watermarkImageData = await getWaterMark(waterMark);
+          const watermarkImg = new Image();
+          watermarkImg.src = watermarkImageData;
+
+          watermarkImg.onload = () => {
+            const watermarkWidth = img.width / 5; // 设置水印宽度
+            const watermarkHeight = (watermarkImg.height / watermarkImg.width) * watermarkWidth; // 计算水印高度,保持比例
+
+            // 计算水印的重复个数
+            const cols = Math.ceil(img.width / watermarkWidth); // 水印在水平方向的个数
+            const rows = Math.ceil(img.height / watermarkHeight); // 水印在垂直方向的个数
+
+            // 绘制水印
+            for (let row = 0; row < rows; row++) {
+              for (let col = 0; col < cols; col++) {
+                const x = col * watermarkWidth;
+                const y = row * watermarkHeight;
+                ctx.drawImage(watermarkImg, x, y, watermarkWidth, watermarkHeight);
+              }
             }
-        }).catch((e)=>{
-            console.log(e);
-            showToast('下载失败')
-        })
-    }
 
-    //取消下载
-    const cancelDownload=()=>{
-        // 取消请求
-        controller.abort()
-        progress.value=0
-        fileSize.value=0
+            // 导出加水印后的图片
+            canvas.toBlob((modifiedBlob) => {
+              const blobRes = modifiedBlob;
+              if ("download" in document.createElement("a")) {
+                const elink = document.createElement("a");
+								elink.download = filename || url.split("/")[url.split("/").length - 1];
+								elink.style.display = "none";
+                elink.href = window.URL.createObjectURL(blobRes);
+                document.body.appendChild(elink);
+                elink.click();
+                window.URL.revokeObjectURL(elink.href);
+                document.body.removeChild(elink);
+              } else {
+                navigator.msSaveBlob(blobRes, filename);
+              }
+            }, "image/png");
+          };
+        };
+      }
+    } catch (error) {
+      showToast("下载失败,请稍后重试");
     }
+  };
 
-    return {
-        fileSize,
-        progress,
-        startDownload,
-        cancelDownload
-    }
-}
+  //取消下载
+  const cancelDownload = () => {
+    // 取消请求
+    controller.abort();
+    progress.value = 0;
+    fileSize.value = 0;
+  };
+
+  return {
+    fileSize,
+    progress,
+    startDownload,
+    cancelDownload,
+  };
+}

+ 82 - 10
src/views/report/List.vue

@@ -9,7 +9,7 @@ import { showToast,showDialog,Dialog } from 'vant';
 import { useRouter } from 'vue-router';
 import { useWindowSize } from '@vueuse/core'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
-import {useDownLoadFile} from '@/hooks/useDownLoadFile'
+import {useDownLoadFileAddWaterMark} from '@/hooks/useDownLoadFile'
 import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 import {useReportApprove} from '@/hooks/useReportApprove'
 import {usePublicSettingStore} from '@/store/modules/publicSetting'
@@ -18,12 +18,13 @@ import {isWeiXin} from '@/hooks/common'
 import AddReportBaseInfoV2 from './components/AddReportBaseInfoV2.vue'
 import ReportFilter from './components/ReportFilter.vue'
 import AudioBox from './components/AudioBox.vue'
-
+import { useConfigSettingStore } from '@/store/modules/etaConfig'
+import { storeToRefs } from 'pinia'
 
 const cachedViewsStore=useCachedViewsStore()
 const publicSettingStore = usePublicSettingStore()
 
-const {startDownload}=useDownLoadFile()
+const {startDownload}=useDownLoadFileAddWaterMark()
 const {isApprove,isOtherApprove,getEtaConfig} = useReportApprove()
 
 const {checkAuthBtn} = useAuthBtn()
@@ -241,17 +242,53 @@ function handleReportCancel(item){
     }).catch(()=>{})
 }
 
-// type 1-pdf 2-长图
-function downloadPdfImg(item,type){
+// type 1-电脑版pdf 2-电脑版长图 3-移动版pdf 4-移动版长图
+async function downloadPdfImg(item,type){
+    let waterMark=''
+    // 判断是否需要水印
+    await useConfigSettingStore().getBaseConfigSetting()
+    const configSettingStore = useConfigSettingStore()
+    const { etaConfigInfo } = storeToRefs(configSettingStore)
+    if(etaConfigInfo.value.WatermarkDownloadPdf==='true'){
+        // 设置水印
+        waterMark=decodeURIComponent(Base64.decode(waterMarkStr.value))
+    }
     showReportItemOpt.value=false
     let name = `${item.Title}${moment().format('YYYYMMDD')}`
     if(type == 1){
         // window.open(item.DetailPdfUrl,"_blank")
-        startDownload(item.DetailPdfUrl,`${name}.pdf`)
-    }else{
-        startDownload(item.DetailImgUrl,`${name}.jpeg`)
+        startDownload({
+            url:item.DetailPdfUrl,
+            filename:`${name}.pdf`,
+            waterMark:waterMark,
+            type:'pdf'
+        })
+    }else if(type===2){
+        startDownload({
+            url:item.DetailImgUrl,
+            filename:`${name}.jpeg`,
+            waterMark:waterMark,
+            type:'img'
+        })
+    }else if(type===3){
+        startDownload({
+            url:item.DetailPdfUrlMobile,
+            filename:`${name}.jpeg`,
+            waterMark:waterMark,
+            type:'pdf'
+        })
+    }else if(type===4){
+        startDownload({
+            url:item.DetailImgUrlMobile,
+            filename:`${name}.jpeg`,
+            waterMark:waterMark,
+            type:'img'
+        })
     }
+    showDownloadPdfImg.value=false
 }
+const showDownloadPdfImg=ref(false)
+const downloadPdfImgType=ref('pdf')
 
 // 发布弹窗关闭
 function handlePublishPopClose(refresh){
@@ -670,6 +707,41 @@ onMounted(async ()=>{
         </svg>
     </div>
 
+    <!-- 下载pdf和图片选择下载类型 -->
+    <van-action-sheet 
+        v-model:show="showDownloadPdfImg"
+        cancel-text="取消"
+        close-on-click-action
+    >
+        <div class="report-item-action-box" v-if="activeItem">
+            <div class="title">{{downloadPdfImgType==='pdf'?'下载pdf':'下载长图'}}</div>
+            <template v-if="downloadPdfImgType==='pdf'">
+                <div 
+                    class="item" 
+                    v-if="checkAuthBtn(reportManageBtn.reportManage_exportPdf) && activeItem.DetailPdfUrl"
+                    @click="downloadPdfImg(activeItem,1)"
+                >电脑版</div>
+                <div 
+                    class="item" 
+                    v-if="checkAuthBtn(reportManageBtn.reportManage_exportPdf) && activeItem.DetailPdfUrlMobile"
+                    @click="downloadPdfImg(activeItem,3)"
+                >手机版</div>
+            </template>
+            <template v-if="downloadPdfImgType==='img'">
+                <div 
+                    class="item" 
+                    v-if="checkAuthBtn(reportManageBtn.reportManage_exportImg) && activeItem.DetailImgUrl"
+                    @click="downloadPdfImg(activeItem,2)"
+                >电脑版</div>
+                <div 
+                    class="item" 
+                    v-if="checkAuthBtn(reportManageBtn.reportManage_exportImg) && activeItem.DetailImgUrlMobile"
+                    @click="downloadPdfImg(activeItem,4)"
+                >手机版</div>
+            </template>
+        </div>
+    </van-action-sheet>
+
     <!-- 报告item操作 -->
     <van-action-sheet 
         teleport="body"
@@ -702,9 +774,9 @@ onMounted(async ()=>{
                 <div class="item" v-if="checkAuthBtn(reportManageBtn.reportManage_cancelPublish)&&activeItem.State===6&&activeItem.HasAuth"
                     @click="handleReportCancel(activeItem)">撤销</div>
                 <div class="item" v-if="checkAuthBtn(reportManageBtn.reportManage_exportPdf) && activeItem.DetailPdfUrl"
-                    @click="downloadPdfImg(activeItem,1)">下载pdf</div>
+                    @click="showDownloadPdfImg=true;downloadPdfImgType='pdf'">下载pdf</div>
                 <div class="item" v-if="checkAuthBtn(reportManageBtn.reportManage_exportImg) && activeItem.DetailImgUrl"
-                    @click="downloadPdfImg(activeItem,2)">下载长图</div>   
+                    @click="showDownloadPdfImg=true;downloadPdfImgType='img'">下载长图</div>
                 <div class="item" @click="handldReportMsgSend(activeItem)" v-if="activeItem.MsgIsSend==0&&checkAuthBtn(reportManageBtn.reportManage_sendMsg)&&activeItem.HasAuth">推送消息</div>
             </template>
             <!-- 待审批,已驳回 -->

+ 24 - 1
src/views/report/PreviewDetail.vue

@@ -8,6 +8,7 @@ import {reportManageBtn,useAuthBtn} from '@/hooks/useAuthBtn'
 import { copyText } from 'vue3-clipboard'
 import {usePublicSettingStore} from '@/store/modules/publicSetting'
 import vueQr from 'vue-qr/src/packages/vue-qr.vue'
+import {Base64} from 'js-base64'
 
 
 const {checkAuthBtn} = useAuthBtn()
@@ -16,6 +17,24 @@ const publicSettingStore = usePublicSettingStore()
 const route=useRoute()
 const router=useRouter()
 
+// 智能布局移动端样式时内容排版全部变成1个1行的顺排
+function formatSmartStyle() {
+    nextTick(() =>{
+        const width=window.innerWidth;
+        if(width>600) return
+        $('.report-drag-item-wrap_child-wrap').css({
+            'flex-wrap': 'wrap',
+        });
+        $('.report-drag-item-wrap_child-wrap').children().css({
+            'flex': 'none',
+            'width': '100%'
+        });
+        
+        document.querySelectorAll('div[comp-type="text"]').forEach(function(div) {
+            div.style.height = 'auto';
+        });
+    })
+}
 
 // 获取报告详情
 let reportInfo=ref(null)
@@ -47,6 +66,10 @@ async function getReportDetail(){
         smartState.layoutBaseInfo['研报标题']=reportInfo.value.Title
         smartState.layoutBaseInfo['研报作者']=reportInfo.value.Author
         smartState.layoutBaseInfo['创建时间']=[2,6].includes(reportInfo.value.State)?reportInfo.value.PublishTime:''
+
+        if(res.Data.ReportLayout===2){
+            formatSmartStyle()
+        }
     }
 }
 if(route.query.id==-1){
@@ -72,7 +95,7 @@ const linkUrl = computed(() =>{
 
         str= reportInfo.value.ReportLayout===1 
             ? `${baseUrl}/reportshare_crm_report?code=${reportInfo.value.ReportCode}&flag=${waterMarkStr}& ${reportInfo.value.Title}`
-            : `${baseUrl}/reportshare_smart_report?code=${reportInfo.value.ReportCode}& ${reportInfo.value.Title}`
+            : `${baseUrl}/reportshare_smart_report?code=${reportInfo.value.ReportCode}&flag=${waterMarkStr}& ${reportInfo.value.Title}`
     }
     
     return str