欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS自定義打印及靜默打印的實(shí)現(xiàn)方法

 更新時間:2024年01月02日 11:31:05   作者:epoos  
在瀏覽器上打印應(yīng)該一個比較常見的操作, 最簡單的打印方式就是直接點(diǎn)擊瀏覽器右上角,找到“打印”按鈕或者調(diào)用window.print(), 然而,實(shí)際情況下大多數(shù)需求都不會如此簡單,所以本文給大家介紹了使用JS自定義打印及靜默打印的方法實(shí)現(xiàn),需要的朋友可以參考下

在瀏覽器上打印應(yīng)該一個比較常見的操作。 最簡單的打印方式就是直接點(diǎn)擊瀏覽器右上角,找到“打印”按鈕或者調(diào)用window.print()。 通過此方式,就能將當(dāng)前頁面整個打印出來了。

然而,實(shí)際情況下大多數(shù)需求都不會如此簡單。 更多的可能是需要打印頁面中的某一段“特定”內(nèi)容或者自定義內(nèi)容。 這就需要用到自定義打印了。

一、自定義打印兩個方法

實(shí)現(xiàn)自定義打印的方法網(wǎng)上能找到很多不同的實(shí)現(xiàn)方案和 js 庫。 其中有兩個用的最多的方法:

1)直接調(diào)用window.print()。 在調(diào)用此方法之前將不需要被打印的元素先通過display='none'隱藏掉,打印執(zhí)行完畢后再通過display='block'還原頁面顯示。

2)創(chuàng)建臨時 Iframe 進(jìn)行打印。 臨時的 Iframe 標(biāo)簽創(chuàng)建完成后將需要打印的內(nèi)容拼接成 html 字符串渲染到 Iframe 里面,再執(zhí)行iframe.contentWindow.print()

方法 1 操作起來方便快捷,適合簡單的頁面,對于稍微復(fù)雜一點(diǎn)的頁面就很不方便了。 方法 2 適合復(fù)雜的打印需求,幾乎可以滿足所有的打印需求,本人在后文中設(shè)計(jì)的自定義打印方案就是基于此方法實(shí)現(xiàn)。

1.iframe 打印基本使用

Iframe 打印和直接頁面打印其實(shí)是一樣的,最終也是調(diào)用 window.print()。 只不過我們是將打印的內(nèi)容渲染在 iframe 內(nèi)部,后在 iframe 內(nèi)部調(diào)用而已。

打印方法實(shí)現(xiàn)如下:

/**
 * 打印方法實(shí)現(xiàn)
 */
const handlePrintByLocalIframe = ({ printHtml }) => {
  // 判斷是否已經(jīng)存在該iframe
  let iframe: any = document.getElementById('J_printIframe');
  if (!iframe) {
    // 新建一個隱藏起來的iframe,并將其添加到當(dāng)前頁面的dom里面
    iframe = document.createElement('IFRAME');
    iframe.setAttribute('id', 'J_printIframe');
    iframe.setAttribute('style', 'position: absolute; width: 0px; height: 0px;left:-5000px;top:-5000px;');
    document.body.appendChild(iframe);
  }
  const doc = iframe.contentWindow.document;
  // 將需要打印的html字符串寫入iframe
  doc.write(printHtml);
  doc.close();
  iframe.contentWindow.focus();
  setTimeout(function () {
    // 對iframe執(zhí)行打印操作
    //延遲50ms是為了解決第一次樣式不生效的問題
    iframe.contentWindow.print();
  }, 50);

  // 網(wǎng)上有人加了這一段代碼,應(yīng)該是為了兼容ie,這個看個人需求添加上。
  if (navigator.userAgent.indexOf('MSIE') > 0) {
    document.body.removeChild(iframe);
  }
};

html 字符串拼接方法實(shí)現(xiàn)如下:

/**
 * 生成 Iframe 內(nèi)嵌頁面字符串并執(zhí)行打印
 * 為了將業(yè)務(wù)和打印功能分開,這里將打印的 html 頁面做成了一個 html 模板,并上傳至 cdn。
 * 后分別拉取 html 模板、接口數(shù)據(jù)、然后通過第三方庫 mustache 來組裝生成 html 字符串。
 * 最后將其傳入前面的打印方法進(jìn)行打印
 */
// 從cdn上獲取html字符串
const htmlStr = await fetchRemoteData('這里填寫html模板字符串的cdn地址');
// 從服務(wù)端獲取數(shù)據(jù)
const data = await fetchRemoteData('這里獲取接口數(shù)據(jù),用于打印文件的數(shù)據(jù)');
// 使用mustache模板語法進(jìn)行渲染(需要和html模板字符串模板一致,可以使用其他模板如 handlebars)
const printHtml = mustache.render(htmlStr, data);
// 執(zhí)行打印
handlePrintByLocalIframe(printHtml);

至此,一個基本的打印功能就完成了,針對單頁打印、普通文本的打印場景已經(jīng)足夠了。

只是,這就結(jié)束了嗎?

當(dāng)然不會,實(shí)際需求中還有很多復(fù)雜的打印場景,比如報表打印。 打印報表的時候往往會涉及到分頁、頁頭、頁眉、頁腳等比較復(fù)雜的場景。

比如:

<!-- 文末有示例 -->
首頁頁需要頁頭其它也不需要
首頁都需要表頭,末頁需要簽名其它也不需要
...

很顯然,面對這些“有理”要求,僅靠上面這個方案還做不到。

二、定制化的自定義打印

上文實(shí)現(xiàn)的打印,其實(shí)現(xiàn)原理就是拼接 html 字符串,然后將字符串傳入 iframe,然后進(jìn)行打印。 而作為一名前端開發(fā),操作 html 就像呼吸一樣簡單,想要在網(wǎng)頁上畫出來分頁、表頭、頁眉、頁腳這些根本沒什么難度可言。 因此,理論上只需要在原方案基礎(chǔ)上做“億點(diǎn)優(yōu)化”就可以解決了。

下面介紹一下本人的設(shè)計(jì)實(shí)現(xiàn)方案,其核心在于自定義分頁。

具體打印方案

首先從接口拿到數(shù)據(jù)并將其轉(zhuǎn)換成下面的數(shù)據(jù)結(jié)構(gòu)。 其核心在于 pageList,這個 pageList 保存的就是打印的時候各個打印頁需要用到的數(shù)據(jù)和配置。 我們?yōu)槊恳豁摱ㄖ飘?dāng)頁渲染所需要的特定的數(shù)據(jù)和特定配置。

1)約定的數(shù)據(jù)格式示例

const data = {
  pageTitle: '多頁模板的數(shù)據(jù)',
  pageList: [
    {
      // 只有第一頁有head,后面的頁沒有
      pageHead: true,
      pageNum: 1, // 當(dāng)前頁屬于第1頁
      list: [
        {
          dataId: 1,
          dataName: 'dataName1',
          dataNum: 8,
        },
        //...第一頁的其他數(shù)據(jù) 28 條
      ],
    },
    {
      pageHead: false, // 除了第1頁其他頁面都不需要標(biāo)題信息。
      pageNum: 2, // 當(dāng)前頁屬于第2頁
      list: [
        {
          dataId: 2,
          dataName: 'dataName2',
          dataNum: 6,
        },
        //...第2頁的其他數(shù)據(jù) 28 + 2 條,多了pageHead 的空間所以多兩條
      ],
    },
  ],
};

這份數(shù)據(jù)屬于是定制化數(shù)據(jù),具體數(shù)據(jù)格式與需要打印的 html 模板文件有關(guān)。 每一頁的數(shù)據(jù)都是通過手動計(jì)算出來的,計(jì)算方法示例如下:

/**
 * serverDataList 為接口返回的原始數(shù)組數(shù)據(jù)
 * 此方法將原始的數(shù)據(jù)轉(zhuǎn)換成每一頁單獨(dú)需要的特定數(shù)據(jù)格式
 * 這里僅是一個示例,具體復(fù)雜度跟其打印的業(yè)務(wù)和模板文件有關(guān)
 * 理論上可以實(shí)現(xiàn)任何打印
 */
const calculatePageNum = (serverDataList) => {
  // 這里的數(shù)值需要手動測量,畢竟每一行的高度都不一樣,需要根據(jù)實(shí)際情況測試出來
  const firstPageMaxNum = 36;
  const otherPageMaxNum = 40;

  const pageList = [];
  let currentPage = 0; // 當(dāng)前遍歷到第幾頁
  serverDataList.forEach((item, index) => {
    const { dataId, dataName, dataNum } = item;
    currentPage = index < firstPageMaxNum ? 1 : 1 + Math.ceil((index + 1 - firstPageMaxNum) / otherPageMaxNum);

    if (!pageList[currentPage - 1]) {
      pageList[currentPage - 1] = {
        pageHead: currentPage === 1,
        pageNum: currentPage,
        list: [item],
      };
    } else {
      pageList[currentPage - 1].list.push(item);
    }
  });
  return pageList;
};

上述方法最終輸出的是一個大的 pageList, 內(nèi)部有一個小的 list。 pageList 包含的是各個頁面的數(shù)據(jù),而 list 包含的是某一頁的列表數(shù)據(jù)。 除此之外,還有當(dāng)前頁面的頁碼,是否應(yīng)該包含頭部信息等。

可以看出,這份數(shù)據(jù)就是為分頁服務(wù)的,有了這份數(shù)據(jù),我們只需要同步設(shè)計(jì)出相應(yīng)的 html 模板. 然后將對應(yīng)的數(shù)據(jù)傳入模板進(jìn)行渲染就能得到相應(yīng)的分頁 html 字符串了。

2)對應(yīng)的 html 模板

html模板可以是任何模板語法,這里我們采用的最簡單的mustache語法

<body class="a4-body">
  <!-- pageList的數(shù)組長度就是當(dāng)前頁數(shù),這里是一個遍歷循環(huán) -->
  {{#pageList}}
  <section class="a4-page">
    {{#pageHead}}
    <header class="head">
      <h2>{{pageTitle}}</h2>
    </header>
    {{/pageHead}}
    <table class="a4-table">
      <tr>
        <th>數(shù)據(jù)ID</th>
        <th>數(shù)據(jù)名稱</th>
        <th>數(shù)據(jù)數(shù)量</th>
      </tr>
      <!-- 這里list就是當(dāng)前頁面的數(shù)據(jù),每一頁的長度可以不一樣,如果有header這里就少幾行 -->
      {{#list}}
      <tr>
        <td>{{dataId}}</td>
        <td>{{dataName}}</td>
        <td>{{dataNum}}</td>
      </tr>
      {{/list}}
    </table>
  </section>
  <ul class="a4-footer">
    <li>第{{pageNum}}頁 總{{pageList.length}}頁</li>
  </ul>
  {{/pageList}}
</body>

不難看出,當(dāng)我們將前面格式化出來的 pageList 數(shù)據(jù)渲染到如上模板就能得到多個 pageList。 每個 pageList 又包含多個數(shù)據(jù)行l(wèi)ist,最終輸出的就是一個完整的分頁 html 字符串結(jié)構(gòu)了。

當(dāng)然,僅僅有對應(yīng)的結(jié)構(gòu)是不夠的,作為 html 頁面,還需要配合對應(yīng)的 css 樣式。

所以,我們還需要用 css 來做一些布局來保證 pageList 里面的一個 item 的總高度為 A4 的高度。 只要保證這個高度,其內(nèi)部樣式如何變化都沒關(guān)系,多一個 header、或者某個特殊頁面多一個特殊元素都無所謂。 無非是在計(jì)算 pageList 的時候?qū)?shù)據(jù)進(jìn)行增減即可。

因此,根據(jù)上文的 html 模板,對模板里面的元素設(shè)置其 a4-body 容器和 a4-page 容器的高度,使其每一頁高度固定。 這樣我們打印出來的內(nèi)容就是我們最終期望的分頁數(shù)據(jù)了。(這里主要介紹A4紙張,其它的原理類似)。

CSS 核心代碼如下:

/* css全部使用mm作為單位 */
.a4-body {
  width: 208mm; /** 這里的寬度就是A4紙的寬度 */
  margin: 0 auto;
  text-align: center;
}

.a4-page {
  width: 100%;
  padding: 6mm;
  /** 這里高度 + a4-footer 的高度就是整張A4紙的高度(297mm) */
  height: 288mm;
  margin: 0 auto;
  box-sizing: border-box;
}
.a4-footer {
  line-height: 9mm;
}

至此,有了 html 模板和 css 負(fù)責(zé)處理 ui 和布局,傳入分割好的數(shù)據(jù),最終就能渲染出固定樣式的 html 頁面內(nèi)容了。 后面不論需要打印的內(nèi)容如何變化,只需要處理好這幾部分之間的關(guān)系,我們就能得到對應(yīng)的 html 頁面,將其塞入 iframe 就能打印任意內(nèi)容。

三、靜默打印

前面我們都是調(diào)用的瀏覽器自帶的打印能力,即 window.print()方法觸發(fā)的瀏覽器預(yù)覽打印。 這種方式非常簡單,接入也不麻煩。

然而,它有一個不容疏忽的缺點(diǎn)(也不算缺點(diǎn),畢竟瀏覽器并不是專業(yè)打印設(shè)備,需要考慮到安全性和通用性),那就是打印觸發(fā)之前它一定會彈出一個“預(yù)覽”大彈窗。

而有時候我們的需求是點(diǎn)擊按鈕就實(shí)現(xiàn)打印,直接給打印機(jī)發(fā)出打印指令,不要彈出打印“預(yù)覽”彈窗。

通過各種途徑了解到,這是無法實(shí)現(xiàn)的,至少純“瀏覽器前端”,通過瀏覽器端的 js 無法實(shí)現(xiàn)。

那就沒有辦法了嗎?

當(dāng)然有,那就是自己開發(fā)一個打印App。

瀏覽器本身其實(shí)也可以看做是一個特殊的“打印App”。 瀏覽器能調(diào)用打印機(jī),自定義打印App當(dāng)然可以。

1、如何設(shè)計(jì)打印App的功能

打印App就一個PC端的應(yīng)用,用 Electron 就能很輕松的做出來。 其需要實(shí)現(xiàn)兩個核心功能:

1.連接和管理電腦設(shè)備上的打印機(jī)
2.能夠與瀏覽器進(jìn)行通信。

連接和管理電腦設(shè)備上的打印機(jī)這個這里不做詳細(xì)介紹,網(wǎng)上有成熟實(shí)現(xiàn)方案,使用 electron 自帶的 API 即可實(shí)現(xiàn)。

至于如何與瀏覽器進(jìn)行通信,也不是麻煩,這里簡單介紹下實(shí)現(xiàn)思路。 其實(shí)也很簡單,無非就是一個 Socket 通信。

我們只需要在此應(yīng)用上啟用一個 Socket Server 服務(wù),此服務(wù)監(jiān)聽一個端口,比如:18877。 這個 Socket 服務(wù)和我們服務(wù)器上啟動的服務(wù)是一樣的,只不過此服務(wù)是直接部署到我們用戶的本地機(jī)器上的,只給當(dāng)前用戶使用的。

之后我們只需要在瀏覽器端啟動一個 Websocket 本地客戶端,然后建立與 ws://127.0.0.1:18877 的連接即可。

當(dāng)我們需要打印的時候,只需發(fā)送 Socket 信息給打印 App,將打印事件、打印文本及其他相關(guān)打印信息發(fā)送給打印控件服務(wù)。 打印控件接收到請求之后再調(diào)用電腦的打印功能,調(diào)用打印機(jī)即可。

至此,一套最基本的打印控件打印方案就算完成了。

最終實(shí)現(xiàn)整體架構(gòu)圖

打印結(jié)果示例

以上就是JS分頁打印及靜默打印的方法實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于JS分頁打印及靜默打印的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論