整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          HTML問題:如何實現分享URL預覽?

          HTML問題:如何實現分享URL預覽?

          端功能問題系列文章,點擊上方合集↑

          序言

          大家好,我是大澈!

          本文2100+,整篇閱讀大約需要3分鐘。

          本文主要內容分三部分,如果您只需要解決問題,請閱讀第一、二部分即可。如果您有更多時間,進一步學習問題相關知識點,請閱讀至第三部分。

          感謝關注微信公眾號:“程序員大澈”,然后加入問答群,從此讓解決問題的你不再孤單!

          何保持頁面樣式基本不變的前提下將HTML頁面導出為PDF,下面提供一些示例代碼,純屬個人原創,如對你有幫助請記得加關注、加收藏、點贊、轉發、分享~謝謝~~

          • 基本思路:保持頁面樣式基本不變,使用 `html2canvas` 將頁面轉換為圖片,然后再通過 `jspdf` 將圖片分頁導出為PDF文件(中間會遇到圖片或文字等內容在分頁處被切割開的問題,如何解決了?詳見末尾干貨)
          • 上基礎代碼:下面為項目中實際代碼截取
          <div>
              <!-- 要打印的內容區 -->
              <div ref="contentRef">
                  <div class="print-item print-out-flow">這是脫離文檔流的內容區域</div>
                  <div class="print-item">這是一行內容,也是最小葉子元素內容</div>
              </div>
              <!-- 打印內容容器 -->
              <div ref="printContainerRef" class="print-container"></div>
          </div>
          /**
            * 1.使用一個隱藏div裝載有滾動條的div.innerHTML
            * 2.隱藏div使用position: absolute, z-index: -999, left: -9999px, width: 900px 控制讓用戶無感知
            * 3.根據需要覆寫隱藏div內html樣式(例如textarea多行顯示有問題, 可以新增一個隱藏的div
            *    包裹textarea的綁定值, 然后在打印樣式中覆寫樣式, 隱藏textarea并顯示對應div)
            */
          handleExport() {
             // 下面是VUE組件內獲取DOM元素代碼,將內容放置到打印區(定義的隱藏DIV)中
              const contentRef=this.$refs.contentRef as HTMLElement;
              const printContainerRef=this.$refs.printContainerRef as HTMLElement;
              // 打印區的需額外處理絕對定位值, 調整使得第一個元素的.top值為0, 以便于頁面計算
              printContainerRef.innerHTML=contentRef.innerHTML;	
              
              // 所有葉子div元素加上 print-item 樣式名, 脫離文檔流的額外添加 print-out-flow
              handlePrintItem(printContainerRef);  // 解決多頁內容可能被切割問題
              
              html2canvas(printContainerRef, {allowTaint: false, useCORS: true}).then((canvas: any)=> {
                const contentHeight=canvas.height;
                const contentWidth=canvas.width;
                // pdf每頁顯示的內容高度
                const pageHeight=contentWidth / 595.28 * 841.89;
                // 未生成pdf的頁面高度
                let offsetHeight=contentHeight;
                // 頁面偏移值
                let position=0;
                // a4紙的尺寸[595.28, 841.89], canvas圖片按a4紙大小縮放后的寬高
                const imgWidth=595.28;
                const imgHeight=595.28 / contentWidth * contentHeight;
          
                const dataURL=canvas.toDataURL('image/jpeg', 1.0);
                const doc=new jsPDF('p', 'pt', 'a4');
          
                if (offsetHeight < pageHeight) {
                  doc.addImage(dataURL, 'JPEG', 0, 0, imgWidth, imgHeight);
                } else {
                  while (offsetHeight > 0) {
                    doc.addImage(dataURL, 'JPEG', 0, position, imgWidth, imgHeight);
                    offsetHeight -=pageHeight;
                    position -=841.89;
          
                    if (offsetHeight > 0) {
                      doc.addPage();
                    }
                  }
                }
          
                doc.save(this.generateReportFileName());
                printContainerRef.innerHTML='';
              });
          }

          上干貨代碼:上面分頁導出PDF可能網上能看到類型代碼,但絕對找不到下面的代碼,純手搓解決分頁元素被切開問題(思路:獲取自身定位,如自己剛好在被分頁處,則加上一定的margin-top值將內容向下移)

          /** 
           * 處理打印元素項, 修復分頁后被切割的元素
           * @param printContainerRef 打印內容div容器
           * @param itemClassName 打印最小元素標識類名
           * @param outFlowClassName 脫離文檔流的元素標識類名
           */
          export function handlePrintItem(
            printContainerRef: HTMLElement,
            itemClassName: string='print-item',
            outFlowClassName: string='print-out-flow'
          ): void {
            const rootClientRect=printContainerRef.getBoundingClientRect();
            // 初始化頁面相關數據
            const totalHeight=rootClientRect.height;  // 內容總高度
            const a4PageHeight=(printContainerRef.clientWidth / 595.28) * 841.89; // a4紙高度
            let pageNum=Math.ceil(totalHeight / a4PageHeight);  // 總頁數
            let addPageHeight=0;  // 修正被分割元素而增加的頁面高度總和
            let currentPage=1;  // 當前正在處理切割的頁面
            const splitItemObj: { [key: number]: HTMLElement[] }={};  // 內容中各頁被切割元素存儲對象
          
            const printItemNodes: NodeListOf<HTMLElement>=printContainerRef.querySelectorAll(`.${itemClassName}`);
            for (let item of printItemNodes) {
              // 如果當前頁已經是最后一頁, 則中斷判斷
              if (currentPage >=pageNum) {
                break;
              }
          
              // 獲取元素絕對定位數據
              const clientRect=item.getBoundingClientRect();
              let top=clientRect.top;
              const selfHeight=clientRect.height;
              // 如果當前元素距離頂部高度大于當前頁面頁腳高度, 則開始判斷下一頁頁腳被切割元素
              if (top > currentPage * a4PageHeight) {
                // 換頁前修正上一頁被切割元素
                addPageHeight +=fixSplitItems(currentPage, a4PageHeight, splitItemObj[currentPage], outFlowClassName);
                pageNum=Math.ceil((totalHeight + addPageHeight) / a4PageHeight);
                top=item.getBoundingClientRect().top;
                currentPage++;
              }
              // 如果元素剛好處于兩頁之間, 則記錄該元素
              if (top > (currentPage - 1) * a4PageHeight && top < currentPage * a4PageHeight && top + selfHeight > currentPage * a4PageHeight) {
                if (!splitItemObj[currentPage]) {
                  splitItemObj[currentPage]=[];
                }
                splitItemObj[currentPage].unshift(item);
                // 如果當前元素是最后一個元素, 則直接處理切割元素, 否則交由處理下一頁元素時再處理切割
                if (item===printItemNodes[printItemNodes.length - 1]) {
                  fixSplitItems(currentPage, a4PageHeight, splitItemObj[currentPage], outFlowClassName);
                }
              }
            }
          }
          
          /**
            * 修復當前頁所有被切割元素
            * @param currentPage 當前頁
            * @param pageHeight 每頁高度
            * @param splitElementItems 當前被切割元素數組
            * @param outFlowClassName 脫離文檔流的樣式類名
            */
          function fixSplitItems(
            currentPage: number,
            pageHeight: number,
            splitElementItems: HTMLElement[],
            outFlowClassName: string
          ): number {
            if (!splitElementItems || !splitElementItems.length) {
              return 0;
            }
          
            const yMargin=5;  // y方向距離頁眉的距離
            const splitItemsMinTop=getSplitItemsMinTop(splitElementItems);
            if (!splitItemsMinTop) {
              return 0;
            }
          
            let fixHeight=currentPage * pageHeight - splitItemsMinTop + yMargin;
            const outFlowElement=splitElementItems.find((item)=> item.classList.contains(outFlowClassName));
            if (outFlowElement && outFlowElement.parentElement) {
              const parentPreviousElement=outFlowElement.parentElement.previousElementSibling as HTMLElement;
              fixHeight +=getMarinTopNum(parentPreviousElement, outFlowElement.parentElement);
              outFlowElement.parentElement.style.marginTop=`${fixHeight}px`;
              return fixHeight;
            }
          
            splitElementItems.forEach((splitElement)=> {
              splitElement.style.marginTop=`${fixHeight}px`;
            });
            return fixHeight;
          }
          
          /**
            * 獲取被切割元素數組中最小高度值(如一行有多個元素被切割,則選出距離頂部最小的高度值)
            * @param splitElementItems 當前被切割元素數組
            */
          function getSplitItemsMinTop(
            splitElementItems: HTMLElement[]
          ): number | undefined {
            // 獲取元素中最小top值作為基準進行修正
            let minTop: number | undefined;
            let minElement: HTMLElement | undefined;
            splitElementItems.forEach((splitElement)=> {
              let top=splitElement.getBoundingClientRect().top;
              if (minTop) {
                minTop=top < minTop ? top : minTop;
                minElement=top < minTop ? splitElement : minElement;
              } else {
                minTop=top;
                minElement=splitElement;
              }
            });
          
            // 修正當前節點及其前面同層級節點的margin值
            if (minTop && minElement) {
              const previousElement=splitElementItems[splitElementItems.length - 1].previousElementSibling as HTMLElement;
              minTop -=getMarinTopNum(previousElement, minElement);
            }
            return minTop;
          }
          
          /**
            * 通過前一個兄弟元素和元素自身的位置確認一個距離頂部高度修正值
            * @param previousElement 前一個兄弟元素
            * @param curElement 當前元素
            */
          function getMarinTopNum(previousElement: HTMLElement, curElement: HTMLElement): number {
            let preMarginNum=0;
            let curMarginNum=0;
            if (previousElement) {
              // 獲取外聯樣式需要getComputedStyle(), 直接.style時對象的值都為空
              const previousMarginBottom=window.getComputedStyle(previousElement).marginBottom;
              preMarginNum=previousMarginBottom ? Number(previousMarginBottom.replace('px', '')) : 0;
            }
            const marginTop=window.getComputedStyle(curElement).marginTop;
            curMarginNum=marginTop ? Number(marginTop.replace('px', '')) : 0;
            return preMarginNum > curMarginNum ? preMarginNum : curMarginNum;
          }

          以上純原創!歡迎加關注、加收藏、點贊、轉發、分享(代碼閑聊站)~

          論使用Nginx URL中攜帶的變量,并根據變量值進行轉發的實現方式。

          應用場景

          在Web開發中,URL參數經常用于傳遞信息和控制行為。例如,一個URL可能包含?from=spic這樣的參數,表示請求的來源為“spic”,可以根據這些參數值進行轉發。

          實踐步驟

          1. 安裝Nginx

          請參考官方文檔或其他資源完成安裝步驟。

          2. 編輯Nginx配置文件

          打開Nginx的配置文件(通常是`nginx.conf`),添加如下配置:

          server {
              listen 80;
              server_name example.com;
          
              location / {
                  # 檢查from參數是否為spic,如果是,則轉發到192.168.1.101:8080/spic
                  if ($arg_from="spic") {
                      proxy_pass http://192.168.1.101:8080/spic;
                  }
                  # 默認轉發到192.168.1.102:8080/
                  proxy_pass http://192.168.1.102:8080/;
              }
          }
          

          在上述配置中,使用$arg_from變量來獲取URL中的from參數,并根據參數值進行轉發。如果參數值為“spic”,則請求會被轉發至http://192.168.1.101:8080/spic;否則,請求將轉發至默認的http://192.168.1.102:8080/。

          3. 重啟Nginx

          保存配置文件后,使用以下命令重啟Nginx使配置生效:

          sudo  nginx -t;sudo  nginx -s reload

          測試驗證

          nginx配置
          server {
                  listen 9090;
                  location / {
                        # 如果from參數等于spic,則設置不同的后端服務器  
                          if ($arg_from="spic") {  
                              proxy_pass http://10.2.2.215:8090; 
                          } 
                          proxy_pass http://10.2.2.162:8090/;
                  }
          }
          ### 模擬請求
          [root@localhost conf.d]# curl -I http://10.2.2.227:9090 
          HTTP/1.1 200 OK
          Server: nginx
          Date: Tue, 07 May 2024 09:17:54 GMT
          Content-Type: text/html; charset=utf-8
          Content-Length: 995
          Connection: keep-alive
          
          [root@node1 ~]# python -m http.server 8090
          Serving HTTP on 0.0.0.0 port 8090 (http://0.0.0.0:8090/) ...
          10.2.2.227 - - [07/May/2024 17:17:54] "HEAD / HTTP/1.0" 200 -
          
            
          
          [root@localhost conf.d]# curl -I http://10.2.2.227:9090/?from=spic
          HTTP/1.1 200 OK
          Server: nginx
          Date: Tue, 07 May 2024 09:18:32 GMT
          Content-Type: text/html; charset=utf-8
          Content-Length: 1274
          Connection: keep-alive
            
           [root@node2 ~]# python -m http.server 8090
          Serving HTTP on 0.0.0.0 port 8090 (http://0.0.0.0:8090/) ...
          10.2.2.227 - - [07/May/2024 17:18:32] "HEAD /?from=spic HTTP/1.0" 200 -
          

          測試結果配置生效


          主站蜘蛛池模板: 精品无码AV一区二区三区不卡 | 精品乱码一区二区三区在线| 无码人妻久久一区二区三区| 日韩在线不卡免费视频一区| 午夜DV内射一区二区| 日韩好片一区二区在线看| 爆乳无码AV一区二区三区| 亚洲片一区二区三区| 日本一区二三区好的精华液| 国产精品被窝福利一区| 2018高清国产一区二区三区| 一区二区三区AV高清免费波多| 精品一区二区三区无码免费视频| 午夜视频在线观看一区二区| 国产在线不卡一区二区三区| 狠狠做深爱婷婷久久综合一区 | 成人精品一区二区三区不卡免费看| 韩国美女vip福利一区| 无码视频一区二区三区在线观看| 日本一区二区三区中文字幕| 日韩一区二区三区在线| 91在线一区二区| 久久久无码一区二区三区| 精品一区二区三区中文字幕| 人妻少妇精品一区二区三区| 一区二区高清视频在线观看| 国内精品视频一区二区八戒| 国产手机精品一区二区| 精品国产一区在线观看| 国产日韩综合一区二区性色AV| 一区二区视频传媒有限公司| 一区二区三区人妻无码| 国产一区二区三区不卡AV| 在线精品自拍亚洲第一区| 琪琪see色原网一区二区| 亚洲一区二区三区丝袜| 中文字幕一区二区人妻性色| 国产在线一区二区三区av| 亚洲一区二区三区成人网站| 亚洲国产精品综合一区在线| 亚洲一区二区三区在线视频|