前端實現列印

跟著姐姐走發表於2021-08-30

  之前開發專案中使用的列印的外掛是easy-print,這個外掛是基於以hiprint(http://hiprint.io/)的列印外掛,我們的專案中主要列印的是表格資料,在測試階段發現了呼叫列印會使頁面卡死,排查問題,發現原因是因為這個hiprint列印表格型別的時候,如果一行資料展示不下會分頁,不會中間截斷,但是出現某個欄位值太長的情況,一條資料一頁放不下,找了半天也沒找到相關配置?(可能是自己的原因沒找到吧。。),後來還加了qq群,但是群裡也是,怎麼說呢,就是問題不一定會得到答覆,不過好像還有VIP群,要money才能進?,我也不確定會不會解決我的問題,所以就,決定還是自己寫個簡單的。

  使用的實現方法是基於window.print,實現思路大概是在當前頁面建立一個iframe(這裡的iframe使用絕對定位或者固定定位寬高等方式讓它在使用者可視區域看不到的地方,當然如果說需要做頁面預覽的可以先生成一個預覽頁面),將列印的html內容放到iframe裡,使iframe作為列印的容器,呼叫列印,之後再刪除iframe。提前說一聲,我這裡主要用的是檔案寫入的方式,所以裡邊的內容都是使用字串拼接這樣的,寫的只要是實現表格,所以別的列印內容做了簡單的處理,這裡如果說需要做統一處理另一種格式的,可以自定義再新增type~

  實現列印具體的程式碼:

 1 export const getPrintDom = (data) => {
 2   const printDom = [];
 3   data.forEach((itemObj) => {
 4     let itemDom = '';
 5     switch (itemObj.type) {
 6       case 'domstr':
 7         itemDom = `<div style=${itemObj.renderStyle}>${itemObj.printElement.domStr}</div>`;
 8         break;
 9       case 'table':
10         const tableHeadTh = itemObj.printElement.columns.map(item => `<th style='border:1px solid #000;border-top:none;border-left:none;width:${item.width}'>${item.title}</th>`); // 表頭
11         const tableData = itemObj.printElement.data.map((item) => {
12           const itemTd = itemObj.printElement.columns.map((citem) => {
13             return `<td style='border:1px solid #000;border-top:none;border-left:none;width:${citem.width};word-break:break-all'>${citem.formatter ? citem.formatter(citem.field, item[citem.field], item) : item[citem.field]}</td>`;
14           });
15           const itemTr = `<tr>${itemTd.join('')}</tr>`;
16           return itemTr;
17         }); // 表資料
18         itemDom = `<table style='font-size:12px;border:1px solid #000;border-bottom:none;border-right:none;text-align:center;${itemObj.renderStyle}' cellspacing='0'>
19           <tr>${tableHeadTh.join('')}</tr>
20           ${tableData.join('')}
21         </table>`;
22         break;
23       default:
24         break;
25     }
26     printDom.push(itemDom);
27   });
28   let printDomStr = ''; // 最終列印的結構及樣式
29   printDom.forEach((item, index) => {
30     if (data[index].displayInBlock && data[index].displayInBlock.begin) {
31       printDomStr += `<div style='display:flex;align-items:center;flex-wrap:wrap;justify-content:space-between;width:100%;text-align:center;font-size:12px'>${item}`;
32     } else if (data[index].displayInBlock && data[index].displayInBlock.end) {
33       printDomStr += `${item}</div>`;
34     } else {
35       printDomStr += item;
36     }
37   });
38   const iframe = document.createElement('IFRAME'); // 建立iframe
39   let doc = null; // 列印的文件
40   iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:0;top:0;'); // 設定iframe的樣式
41   document.body.appendChild(iframe); // 將iframe追加進body
42   doc = iframe.contentWindow.document; // 獲取iframe的文件
43   doc.open(); // 開始寫入iframe
44   doc.write(printDomStr); // 寫入列印的dom資料
45   data.forEach((item) => {
46     if (item.type === 'domstr' && item.printElement.id) {
47       const domEl = doc.getElementById('barCode'); // 獲取dom元素
48       domEl.setAttribute('width', '100%'); // 調整dom的寬度(可能是因為條形碼比較特殊,如果不設定寬度就和在頁面是一樣的寬度,不會自適應,所以這裡調整一下)
49     }
50   });
51   doc.close(); // 結束寫入,關閉文件
52   iframe.contentWindow.focus(); // 使iframe作為列印的容器
53   iframe.contentWindow.print(); // 調起列印
54   setTimeout(() => {
55     document.body.removeChild(iframe); // 刪除iframe,這裡寫非同步是因為有的時候還沒有呼叫列印就刪除了
56   },200)
57 };

  使用列印方法的時候資料傳參是有參考之前的hiprint,總體大的引數是陣列型別,引數結構如下:

 1 const renderData = [
 2   {
 3     displayInBlock: {
 4       begin: boolean  // end同行的最後一個元素,begin同行的第一個元素
 5     } // 是否和某個列印的資料在同一行展示 非必傳
 6     type: 'table', // 列印的元素的型別,是domstr/table,目前只定義了這兩種,必傳
 7     printElement: {
 8       columns: columnsData, // 表格型別必傳,表頭
 9       data: tableData: // 表格型別必傳,表格資料
10       domStr: htmlStr // html字串 domstr型別必傳
11     },
12     renderStyle: string // 額外的一些渲染樣式,非必傳
13   }   
14 ]

  columnsData的格式:

const columnsData = [
  {
      title: '性別', // 表頭
      width: '25%', // 列寬
      field: 'sex', // 對應的資料欄位的key
      formatter: (field, value, row) => {} // 自定義展示函式,若不寫,則預設展示欄位的值
  }
]

  具體使用case參考:

const testTableColumns = [
        {
          title: '賬號',
          width: '25%',
          field: 'id',
        }, {
          title: '姓名',
          width: '25%',
          field: 'name',
        }, {
          title: '性別',
          width: '25%',
          field: 'sex',
          formatter: (field, value, row) => {
            let domStr = `<p style='color: red'>女</p>`;
            if (value === 0) {
              domStr = `<p>男</p>`
            }
            return domStr;
          }
        }, {
          title: '年齡',
          width: '25%',
          field: 'age'
        }
      ];
      const testTableData = [
        {
          id: '001',
          name: 'Andy',
          sex: 1,
          age: '22'
        },
        {
          id: '002',
          name: 'Bob',
          sex: 0,
          age: '23'
        },
        {
          id: '003',
          name: 'Tom',
          sex: 0,
          age: '20'
        },
      ];
      const printDatas = [
        {
          type: 'domstr',
          printElement: { domStr: `<p>學校:市實驗中學 班級:高274</p>` },
          renderStyle: 'width: 50%;',
          displayInBlock: { begin: true },
        }, {
          type: 'domstr',
          printElement: { domStr: '<div style="display: flex; justify-content: space-between;color:red"><p style="border: 1px solid #000; padding: 4px 10px">班主任</p><p style="border: 1px solid #000;padding: 4px 10px">胡辣辣</p></div>' },
          renderStyle: 'width: 50%;',
          displayInBlock: { end: true },
        }, {
          type: 'table',
          printElement: {
            columns: testTableColumns,
            data: testTableData
          },
          renderStyle: 'width:100%;margin-top:20px;'
        }
      ]
      getPrintDom(printDatas);

 

  效果:

 

 

   暫時就是這些,寫的比較簡陋,主要是用來展示列印表格。如果有更好的方法和外掛或者意見,歡迎評論~

 

相關文章