目录
- 需求描述
- 方案1:直接打印页面
- 方案2:使用css媒体查询控制打印内容
- 方案3:第三方插件print-js
- 方案4:将要打印的节点内容写入iframe
- 方案5:将要打印的html写入iframe(推荐)
需求描述
最近有一个需求,是让用户通过浏览器可以打印表格内容
环境:
- node v16.14.0
- vue2.js
- element-ui
表格本身是使用了的el-table实现的,浏览器显示的时候很正常,没有什么异常。
创建vue2项目
$ node -v v16.14.0 // 2022-10-14 最新版本 5.0.0 pnpm install -g @vue/cli 选择 - vue 2.6.14 - vuex 3.6.2 - vue-router 3.5.1 - less 4.0.0
表格数据 data.js
export const tableData = [ { date: "2016-05-02", name: "王小虎", address: "上海市普陀区金沙江路 1518 弄", }, { date: "2016-05-04", name: "王小虎", address: "上海市普陀区金沙江路 1517 弄", }, { date: "2016-05-01", name: "王小虎", address: "上海市普陀区金沙江路 1519 弄", }, { date: "2016-05-03", name: "王小虎", address: "上海市普陀区金沙江路 1516 弄", }, ];
界面显示
直接ctrl+p
打印效果不是很好。于是,开始寻找解决方案,大致有以下几种思路
方案1:直接打印页面
js打印页面
// 快捷键 ctrl + p window.print()
vue代码
<template> <div> <el-table :data="tableData" border style="width: 100%" > <el-table-column prop="date" label="日期" width="180" > </el-table-column> <el-table-column prop="name" label="姓名" width="180" > </el-table-column> <el-table-column prop="address" label="地址" > </el-table-column> </el-table> <el-button @click="handlePrint" type="primary" style="margin-top: 20px" >打印</el-button > </div> </template> <script> import { tableData } from './data.js' export default { data() { return { tableData, } }, methods: { handlePrint() { window.print() }, }, } </script> <style lang="less"></style>
打印预览
发现,直接打印是不行的
当使用ctrl+p 拉起打印的时候,发现总有一些小问题,比如
- 表格后面的竖线缺失
- 表格的表头错位
- 表格的列显示不全
方案2:使用css媒体查询控制打印内容
该方案适用于页面简单,打印效果过要求不高的场景
我们可以通过css媒体查询,将打印按钮隐藏
使用css媒体查询控制打印区域
@media print { /* 隐藏不需要打印的部分 */ .not-print{ display: none !important; } .print{ background-color: transparent; } }
html中设置不打印的内容
<div class="not-print">不打印此内容</div>
vue 完整代码
<template> <div> <el-table :data="tableData" border style="width: 100%" > <el-table-column prop="date" label="日期" width="180" > </el-table-column> <el-table-column prop="name" label="姓名" width="180" > </el-table-column> <el-table-column prop="address" label="地址" > </el-table-column> </el-table> <el-button class="not-print" @click="handlePrint" type="primary" style="margin-top: 20px" >打印</el-button > </div> </template> <script> import { tableData } from './data.js' export default { data() { return { tableData, } }, methods: { handlePrint() { window.print() }, }, } </script> <style lang="less"> @media print { /* 隐藏不需要打印的部分 */ .not-print { display: none !important; } .print { background-color: transparent; } } </style>
打印预览
方案3:第三方插件print-js
适用于简单输出
文档
- https://github.com/crabbly/print.js
- https://printjs.crabbly.com/
- https://www.npmjs.com/package/print-js
安装
npm install print-js --save
使用示例
printJS({ header: "表格标题", type: "json", properties: [ { field: "date", displayName: "日期" }, { field: "name", displayName: "姓名" }, { field: "address", displayName: "地址" }, ], printable: this.tableData, });
vue 完整代码
<template> <div> <el-table :data="tableData" border style="width: 100%" > <el-table-column prop="date" label="日期" width="180" > </el-table-column> <el-table-column prop="name" label="姓名" width="180" > </el-table-column> <el-table-column prop="address" label="地址" > </el-table-column> </el-table> <el-button class="not-print" @click="handlePrint" type="primary" style="margin-top: 20px" >打印</el-button > </div> </template> <script> import { tableData } from './data.js' import printJS from 'print-js' export default { data() { return { tableData, } }, methods: { handlePrint() { printJS({ header: '表格标题', type: 'json', properties: [ { field: 'date', displayName: '日期' }, { field: 'name', displayName: '姓名' }, { field: 'address', displayName: '地址' }, ], printable: this.tableData, }) }, }, } </script> <style lang="less"></style>
打印预览
方案4:将要打印的节点内容写入iframe
可以直接将要打印的节点内容设置给iframe标签,此方法可能会出现样式不一致的问题
关键代码 utils.js
export function printHTML(html) { // 创建iframe元素 const iframe = document.createElement('iframe') iframe.setAttribute('style', 'display:none') document.body.appendChild(iframe) let doc = iframe.contentWindow.document // 写入html doc.write(html) doc.close() // 调用打印 iframe.contentWindow.focus() iframe.contentWindow.print() // 打印完毕后移除 iframe.parentNode.removeChild(iframe) }
vue 完整代码
<template> <div> <el-table id="print-table" :data="tableData" border style="width: 100%" > <el-table-column prop="date" label="日期" width="180" > </el-table-column> <el-table-column prop="name" label="姓名" width="180" > </el-table-column> <el-table-column prop="address" label="地址" > </el-table-column> </el-table> <el-button class="not-print" @click="handlePrint" type="primary" style="margin-top: 20px" >打印</el-button > </div> </template> <script> import { tableData } from './data.js' import { printHTML } from './utils.js' export default { data() { return { tableData, } }, methods: { handlePrint() { let printElementHtml = document.querySelector('#print-table').innerHTML printHTML(printElementHtml) }, }, } </script> <style lang="less"></style>
打印结果
方案5:将要打印的html写入iframe(推荐)
适用于复杂输出,需要自定义,复杂度也是最高的
我们应该换个思路:
打印的页面往往都是黑白显示,不一定要和原页面一样,很多元素可能不一样,可能还会增加额外的一些信息,索引应该单独构建html页面
我们可以使用后端模板渲染的思路,通过twig 模板引擎来渲染一个html文件,通过容器隔离的iframe来进行独立完成渲染和打印工作
安装用到的依赖
pnpm i twig twig-loader -D
模板代码
<template> <div style="text-align: center"> <el-table id="print-table" :data="tableData" border style="width: 100%" > <el-table-column prop="date" label="日期" width="180" > </el-table-column> <el-table-column prop="name" label="姓名" width="180" > </el-table-column> <el-table-column prop="address" label="地址" > </el-table-column> </el-table> <el-button @click="handlePrint" type="primary" style="margin-top: 20px" >打印</el-button > </div> </template> <script> import { tableData } from './data.js' import { printHTML } from './utils.js' import printTemplate from './print-template.twig' import dayjs from 'dayjs' export default { data() { return { tableData, } }, methods: { handlePrint() { let html = printTemplate({ title: '员工信息表', list: this.tableData, now_time: dayjs().format('YYYY-MM-DD'), }) printHTML(html) }, }, } </script> <style lang="less"></style>
模板 print-template.twig
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> * { margin: 0; padding: 0; } table, td, th { border: 1px solid #000; border-collapse: collapse; } th, td { padding: 3px 10px; font-size: 12px; line-height: 1.5; } table { width: 100%; margin: 0 auto; } .title { font-size: 14px; line-height: 1.5; text-align: center; margin-bottom: 20px; font-weight: normal; } .footer-wrap { margin-top: 20px; display: flex; } .footer { margin-left: auto; padding-right: 100px; font-size: 12px; line-height: 1.5; text-align: left; } </style> </head> <body> <h1 class="title">{{ title }}</h1> <table> <thead> <tr> <th>日期</th> <th>姓名</th> <th>地址</th> </tr> </thead> <tbody> {% for row in list %} <tr> <td>{{ row.date }}</td> <td>{{ row.name }}</td> <td>{{ row.address }}</td> </tr> {% endfor %} </tbody> </table> <div class="footer-wrap"> <div class="footer"> <div>打印日期:{{ now_time }}</div> </div> </div> </body> </html>
打印预览
至此,完整的打印了表格内容,还附带了一些必要的信息
需要配置 vue.config.js
// vue.config.js module.exports = { configureWebpack: { resolve: { fallback: { path: require.resolve('path-browserify'), }, }, }, chainWebpack: (config) => { // twig rule loader const twigRule = config.module.rule('twig') twigRule.exclude.add(/node_modules/) // 添加新的loader处理 twigRule .test(/\.twig$/) .use('twig-loader') .loader('twig-loader') .end() }, }
依赖 package.json
{ "name": "vue-demo", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build" }, "dependencies": { "dayjs": "^1.11.5", "element-ui": "^2.15.10", "path-browserify": "^1.0.1", "vue": "^2.6.14" }, "devDependencies": { "@vue/cli-service": "~5.0.0", "less": "^4.0.0", "less-loader": "^8.0.0", "twig": "^1.15.4", "twig-loader": "^0.5.5", "vue-template-compiler": "^2.6.14" } }
打印属性
/* 打印文档时修改某些 CSS 属性 */ @page { /* 设置纸张及其方向 portrait:纵向; landscape: 横向 */ size: A4 landscape; }
完整代码:https://github.com/mouday/vue-print/
在线体验:https://mouday.github.io/vue-print/
参考