技术栈:JavaScript + Vue + Print.js
简介
后台管理系统中,会常用到打印功能,打印一些表格数据、信息之类的。
此处做个简单的打印示例。
此功能模块,主要分为三个部分
- 打印模板文件,提前编写好的数据展示模板
- 打印组件,将模板文件名跟模板文件映射起来
- 应用demo,实际应用
实现
打印模板文件
根据实际需求,绘制好需要的展示效果,布局、css样式等实现。
有个按钮,模拟模板的点击打印事件。
<template>
<div>
<div v-if="record" id="theidforvprint">
<h2>Title</h2>
<table :style="bindStyle.tableCom" class="showBorder">
<tr>
<td class="td-label">姓名</td>
<td class="td-value">{{ record.name }}</td>
<td class="td-label">年龄</td>
<td class="td-value">{{ record.age }}</td>
</tr>
<tr>
<td class="td-label">性别</td>
<td class="td-value">{{ record.sex }}</td>
<td class="td-label">电话</td>
<td class="td-value">{{ record.tel }}</td>
</tr>
<tr>
<td class="td-label">住址</td>
<td class="td-value">{{ record.address }}</td>
</tr>
<tr class="border-none-bottom">
<td class="td-label">公司</td>
<td class="td-value">{{ record.company }}</td>
<td class="td-label">工号</td>
<td class="td-value">{{ record.code }}</td>
</tr>
<tr class="border-none-vertical">
<td class="td-label">部门</td>
<td class="td-value">{{ record.dep }}</td>
</tr>
<tr>
<td class="td-label">描述</td>
<td class="td-value">{{ record.desc }}</td>
</tr>
</table>
</div>
<button ref="printBtn" v-print="'#theidforvprint'" style="opacity: 0">
导出
</button>
</div>
</template>
<script>
export default {
name: "TableCase",
data() {
return {
record: null,
bindStyle: {
tableCom: {
borderCollapse: "collapse",
borderSpacing: "0",
tableLayout: "fixed",
width: "100%",
fontFamily: "sans-serif",
},
},
};
},
created() {
console.log("created");
},
mounted() {
console.log("mounted");
},
methods: {
handleRecord(record) {
for (let key in record) {
let obj = {
label: key,
value: record[key],
col: key === "tel" ? 2 : 1,
};
this.renderConfigList.push(obj);
}
},
async print(record) {
this.record = record;
await this.$refs.printBtn.click();
this.record = null; //hide dom
this.renderConfigList = null;
},
},
};
</script>
<style lang="scss" scoped>
@media print {
#theidforvprint {
width: 100%;
h2 {
text-align: center;
}
td {
font-size: 16px;
line-height: 1.5;
color: #999999;
}
.td-label {
font-weight: bold;
}
// 正确代码
.showBorder tr {
border: 1px solid #999999;
}
.showBorder tr:first-child {
border-bottom: 0;
}
.showBorder tr:nth-child(-n + 3):nth-child(n + 2) {
border-top: 0;
border-bottom: 0;
}
.showBorder .border-none-vertical {
border-top: 0;
border-bottom: 0;
}
.showBorder .border-none-bottom {
border-bottom: 0;
}
}
}
</style>
打印组件
将涉及到的模板文件都引入到此组件中,组件根据模板文件名自动匹配、渲染指定打印模板。
<template>
<div>
<component ref="certainTemp" :is="templateName" id="theidforvprint" />
</div>
</template>
<script>
import tableCase from './case/tableCase'
export default {
name: "TemplateRoot",
components: {
tableCase
},
data() {
return {
templateName: '',
}
},
methods: {
async print(record, templateName) {
this.templateName = templateName;
await this.$forceUpdate();
this.$refs.certainTemp.print(record);
},
},
}
</script>
应用
测试案例
点击“导出数据按钮”,调用浏览器打印功能。
<template>
<div>
<button @click="toPrint">导出数据</button>
<templateRoot ref="templateRoot" />
</div>
</template>
<script>
import templateRoot from './printTemplate/templateRoot.vue';
export default {
name: "PrintData",
components: {
templateRoot,
},
data() {
return {
list: {
name: "name",
age: "20",
sex: "women",
address: "xx省xx市xxx路xxxx号",
company: "zxcvbnasd",
code: "110",
dep: "叽里呱啦部门",
tel: "1234567",
desc: "叽里呱啦能力,工作内容为啊吧啊吧,擅长嘻嘻哈哈,时常7788"
}
}
},
methods: {
toPrint() {
this.$refs.templateRoot.print(this.list, "tableCase");
}
}
}
</script>
效果
附: Print工具源码
index.js
print 入口文件
import Print from './packages/print.js';
Print.install = function(Vue) {
Vue.directive('print', Print);
};
export default Print;
print.js
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _printarea = require('./printarea.js');
var _printarea2 = _interopRequireDefault(_printarea);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.default = {
directiveName: 'print',
bind: function bind(el, binding, vnode) {
var vue = vnode.context;
var closeBtn = true;
el.addEventListener('click', function (event) {
if (binding.value) {
localPrint();
} else {
window.print();
}
});
var localPrint = function localPrint() {
vue.$nextTick(function () {
if (closeBtn) {
closeBtn = false;
var print = new _printarea2.default({
el: binding.value,
endCallback: function endCallback() {
closeBtn = true;
}
});
}
});
};
},
// update: function update(el, binding) {},
// unbind: function unbind(el) {}
};
printarea.js
packages/printarea.js
'use strict'
Object.defineProperty(exports, '__esModule', {
value: true
})
var _assign = require('babel-runtime/core-js/object/assign')
var _assign2 = _interopRequireDefault(_assign)
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck')
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2)
var _createClass2 = require('babel-runtime/helpers/createClass')
var _createClass3 = _interopRequireDefault(_createClass2)
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj }
}
var _class = (function () {
function _class(option) {
;(0, _classCallCheck3.default)(this, _class)
this.standards = {
strict: 'strict',
loose: 'loose',
html5: 'html5'
}
this.counter = 0
this.settings = {
standard: this.standards.html5,
extraHead: '',
extraCss: '',
popTitle: '',
endCallback: null,
el: ''
}
;(0, _assign2.default)(this.settings, option)
this.init()
}
;(0, _createClass3.default)(_class, [
{
key: 'init',
value: function init() {
this.counter++
this.settings.id = 'printArea_' + this.counter
var box = document.getElementById(this.settings.id)
if (box) {
box.parentNode.removeChild(box)
}
var PrintAreaWindow = this.getPrintWindow()
this.write(PrintAreaWindow.doc)
this.settings.endCallback()
}
},
{
key: 'print',
value: function print(PAWindow) {
var paWindow = PAWindow
console.log('---调用打印 focus-----')
paWindow.focus()
paWindow.print()
console.log('---调用打印 print-----')
}
},
{
key: 'write',
value: function write(PADocument, $ele) {
PADocument.open()
PADocument.write(this.docType() + '<html>' + this.getHead() + this.getBody() + '</html>')
PADocument.close()
}
},
{
key: 'docType',
value: function docType() {
if (this.settings.standard === this.standards.html5) {
return '<!DOCTYPE html>'
}
var transitional = this.settings.standard === this.standards.loose ? ' Transitional' : ''
var dtd = this.settings.standard === this.standards.loose ? 'loose' : 'strict'
return (
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01' +
transitional +
'//EN" "http://www.w3.org/TR/html4/' +
dtd +
'.dtd">'
)
}
},
{
key: 'getHead',
value: function getHead() {
var extraHead = ''
var links = ''
var style = ''
if (this.settings.extraHead) {
this.settings.extraHead.replace(/([^,]+)/g, function (m) {
extraHead += m
})
}
;[].forEach.call(document.querySelectorAll('link'), function (item, i) {
if (item.href.indexOf('.css') >= 0) {
links += '<link type="text/css" rel="stylesheet" href="' + item.href + '" >'
}
})
for (var i = 0; i < document.styleSheets.length; i++) {
let flag = false
try {
flag = document.styleSheets[i].cssRules || document.styleSheets[i].rules
} catch (err) {
let flag = false
console.error(err)
}
if (flag) {
var rules = document.styleSheets[i].cssRules || document.styleSheets[i].rules
for (var b = 0; b < rules.length; b++) {
try {
style += rules[b].cssText
} catch (err) { console.log(err) }
}
}
}
if (this.settings.extraCss) {
this.settings.extraCss.replace(/([^,\s]+)/g, function (m) {
links += '<link type="text/css" rel="stylesheet" href="' + m + '">'
})
}
return (
'<head><title>' +
this.settings.popTitle +
'</title>' +
extraHead +
links +
'<style type="text/css">' +
style +
'</style></head>'
)
}
},
{
key: 'getBody',
value: function getBody() {
var ele = this.getFormData(document.querySelector(this.settings.el))
var htm = ele.outerHTML
console.log('htm', htm)
return '<body>' + htm + '</body>'
}
},
{
key: 'getFormData',
value: function getFormData(ele) {
var copy = ele.cloneNode(true)
var allElements = copy.querySelectorAll('*')
;[].forEach.call(allElements, function (item) {
var attr = item.getAttribute('ignore-print')
attr = attr == null ? item.getAttribute('ignoreprint') : attr
if (attr != null && attr.toString() === 'true') {
item.outerHTML = ''
}
})
var copiedInputs = copy.querySelectorAll('input,select,textarea')
;[].forEach.call(copiedInputs, function (item, i) {
var typeInput = item.getAttribute('type')
var copiedInput = copiedInputs[i]
if (typeInput == null) {
typeInput = item.tagName === 'SELECT' ? 'select' : item.tagName === 'TEXTAREA' ? 'textarea' : ''
}
if (typeInput === 'radio' || typeInput === 'checkbox') {
copiedInput.setAttribute('checked', item.checked)
} else if (typeInput === 'select') {
;[].forEach.call(copiedInput.querySelectorAll('option'), function (op, b) {
if (op.selected) {
op.setAttribute('selected', true)
}
})
} else if (typeInput === 'textarea') {
copiedInput.innerHTML = item.value
} else {
copiedInput.value = item.value
copiedInput.setAttribute('value', item.value)
}
})
var sourceCanvas = ele.querySelectorAll('canvas')
var copyCanvas = copy.querySelectorAll('canvas')
;[].forEach.call(copyCanvas, function (item, i) {
var url = sourceCanvas[i].toDataURL()
item.outerHTML = '<img src="' + url + '" style="width:100%;"/>'
})
return copy
}
},
{
key: 'getPrintWindow',
value: function getPrintWindow() {
var f = this.Iframe()
return {
win: f.contentWindow || f,
doc: f.doc
}
}
},
{
key: 'Iframe',
value: function Iframe() {
var frameId = this.settings.id
var iframe = void 0
var that = this
try {
iframe = document.createElement('iframe')
document.body.appendChild(iframe)
iframe.style.border = '0px'
iframe.style.position = 'absolute'
iframe.style.width = '0px'
iframe.style.height = '0px'
iframe.style.right = '0px'
iframe.style.top = '0px'
iframe.setAttribute('id', frameId)
iframe.setAttribute('src', new Date().getTime())
iframe.doc = null
iframe.onload = function () {
var win = iframe.contentWindow || iframe
that.print(win)
}
iframe.doc = iframe.contentDocument
? iframe.contentDocument
: iframe.contentWindow
? iframe.contentWindow.document
: iframe.document
} catch (e) {
throw new Error(e + '. iframes may not be supported in this browser.')
}
if (iframe.doc == null) {
throw new Error('Cannot find document.')
}
return iframe
}
}
])
return _class
})()
exports.default = _class