printarea.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. const isIE = () =>{
  2. if (!!window.ActiveXobject || "ActiveXObject" in window) {
  3. return true;
  4. } else {
  5. return false;
  6. }
  7. }
  8. /**
  9. * 判断是否是IE11
  10. * @returns boolean
  11. */
  12. const isIE11 = () => {
  13. if((/Trident\/7\./).test(navigator.userAgent)) {
  14. return true;
  15. } else {
  16. return false;
  17. }
  18. }
  19. const isRemove = (dom) => {
  20. if (isIE() || isIE11()) {
  21. dom.removeNode(true)
  22. } else {
  23. dom.remove()
  24. }
  25. return dom
  26. }
  27. export default class {
  28. constructor(option) {
  29. this.standards = {
  30. strict: 'strict',
  31. loose: 'loose',
  32. html5: 'html5'
  33. };
  34. this.previewBody = null;
  35. this.close = null;
  36. this.previewBodyUtilPrintBtn = null;
  37. this.selectArray = []; // 存储select的
  38. this.counter = 0;
  39. this.settings = {
  40. standard: this.standards.html5,
  41. };
  42. Object.assign(this.settings, option);
  43. this.init();
  44. }
  45. init () {
  46. this.counter++;
  47. this.settings.id = `printArea_${this.counter}`;
  48. let url = ''
  49. if (this.settings.url && !this.settings.asyncUrl) {
  50. url = this.settings.url
  51. }
  52. let _this = this
  53. // 如果是异步的
  54. if (this.settings.asyncUrl) {
  55. _this.settings.asyncUrl(function (url) {
  56. let PrintAreaWindow = _this.getPrintWindow(url); // 创建iframe
  57. if (_this.settings.preview) {
  58. // 打开预览弹窗
  59. _this.previewIfrmaeLoad()
  60. } else {
  61. // 直接打印
  62. _this.print(PrintAreaWindow);
  63. }
  64. }, _this.settings.vue)
  65. return
  66. }
  67. let PrintAreaWindow = this.getPrintWindow(url); // 创建iframe
  68. if (!this.settings.url) {
  69. this.write(PrintAreaWindow.doc); // 写入内容
  70. }
  71. if (this.settings.preview) {
  72. // 打开预览弹窗
  73. this.previewIfrmaeLoad()
  74. } else {
  75. // 直接打印
  76. this.print(PrintAreaWindow);
  77. }
  78. }
  79. addEvent (element, type, callback) {
  80. if (element.addEventListener) {
  81. element.addEventListener(type, callback, false);
  82. } else if (element.attachEvent) {
  83. element.attachEvent('on' + type, callback);
  84. } else {
  85. element['on' + type] = callback;
  86. }
  87. }
  88. previewIfrmaeLoad () {
  89. let box = document.getElementById('vue-pirnt-nb-previewBox')
  90. if (box) {
  91. let _this = this
  92. let iframe = box.querySelector('iframe')
  93. this.settings.previewBeforeOpenCallback()
  94. this.addEvent(iframe, 'load', function () {
  95. _this.previewBoxShow()
  96. _this.removeCanvasImg()
  97. _this.settings.previewOpenCallback()
  98. })
  99. this.addEvent(box.querySelector('.previewBodyUtilPrintBtn'), 'click', function () {
  100. _this.settings.beforeOpenCallback()
  101. _this.settings.openCallback();
  102. iframe.contentWindow.print();
  103. _this.settings.closeCallback()
  104. })
  105. }
  106. }
  107. // 删除所有canva转换的图片
  108. removeCanvasImg () {
  109. let _this = this
  110. try {
  111. if (_this.elsdom) {
  112. // 删除canva转变图片的dom节点
  113. let canvasList = _this.elsdom.querySelectorAll('.canvasImg')
  114. for (let i = 0; i < canvasList.length; i++) {
  115. isRemove(canvasList[i])
  116. }
  117. }
  118. } catch (e) {
  119. console.log(e);
  120. }
  121. }
  122. print (ifrmae) {
  123. var _this = this;
  124. let iframe = document.getElementById(this.settings.id) || ifrmae.f;
  125. let iframeWin = document.getElementById(this.settings.id).contentWindow || ifrmae.f.contentWindow;
  126. var _loaded = function () {
  127. iframeWin.focus();
  128. _this.settings.openCallback();
  129. iframeWin.print();
  130. isRemove(iframe)
  131. _this.settings.closeCallback()
  132. _this.removeCanvasImg()
  133. }
  134. _this.settings.beforeOpenCallback()
  135. _this.addEvent(iframe, 'load', function () {
  136. _loaded()
  137. })
  138. }
  139. write (PADocument) {
  140. PADocument.open();
  141. PADocument.write(`${this.docType()}<html>${this.getHead()}${this.getBody()}</html>`);
  142. PADocument.close();
  143. }
  144. docType () {
  145. if (this.settings.standard === this.standards.html5) {
  146. return '<!DOCTYPE html>';
  147. }
  148. var transitional = this.settings.standard === this.standards.loose ? ' Transitional' : '';
  149. var dtd = this.settings.standard === this.standards.loose ? 'loose' : 'strict';
  150. return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01${transitional}//EN" "http://www.w3.org/TR/html4/${dtd}.dtd">`;
  151. }
  152. getHead () {
  153. let extraHead = '';
  154. let links = '';
  155. let style = '';
  156. if (this.settings.extraHead) {
  157. this.settings.extraHead.replace(/([^,]+)/g, (m) => {
  158. extraHead += m;
  159. });
  160. }
  161. // 复制所有link标签
  162. [].forEach.call(document.querySelectorAll('link'), function (item) {
  163. if (item.href.indexOf('.css') >= 0) {
  164. links += `<link type="text/css" rel="stylesheet" href="${item.href}" >`;
  165. }
  166. });
  167. // 循环获取style标签的样式
  168. let domStyle = document.styleSheets;
  169. if (domStyle && domStyle.length > 0) {
  170. for (let i = 0; i < domStyle.length; i++) {
  171. try {
  172. if (domStyle[i].cssRules || domStyle[i].rules) {
  173. let rules = domStyle[i].cssRules || domStyle[i].rules;
  174. for (let b = 0; b < rules.length; b++) {
  175. style += rules[b].cssText;
  176. }
  177. }
  178. } catch (e) {
  179. console.log(domStyle[i].href + e);
  180. }
  181. }
  182. }
  183. if (this.settings.extraCss) {
  184. this.settings.extraCss.replace(/([^,\s]+)/g, (m) => {
  185. links += `<link type="text/css" rel="stylesheet" href="${m}">`;
  186. });
  187. }
  188. return `<head><title>${this.settings.popTitle}</title>${extraHead}${links}<style type="text/css">${style}</style></head>`;
  189. }
  190. getBody () {
  191. let ids = this.settings.ids;
  192. ids = ids.replace(new RegExp("#", "g"), '');
  193. this.elsdom = this.beforeHanler(document.getElementById(ids));
  194. let ele = this.getFormData(this.elsdom);
  195. let htm = ele.outerHTML;
  196. return '<body>' + htm + '</body>';
  197. }
  198. // 处理canva转成图片
  199. beforeHanler (elsdom) {
  200. let canvasList = elsdom.querySelectorAll('canvas');
  201. // canvas转换png图片
  202. for (let i = 0; i < canvasList.length; i++) {
  203. if (!canvasList[i].style.display) {
  204. let _parent = canvasList[i].parentNode
  205. let _canvasUrl = canvasList[i].toDataURL('image/png')
  206. let _img = new Image()
  207. _img.className = 'canvasImg'
  208. _img.style.display = 'none'
  209. _img.src = _canvasUrl
  210. _parent.appendChild(_img)
  211. }
  212. }
  213. return elsdom
  214. }
  215. // 根据type去处理form表单
  216. getFormData (ele) {
  217. let copy = ele.cloneNode(true);
  218. let copiedInputs = copy.querySelectorAll('input,select,textarea');
  219. let canvasImgList = copy.querySelectorAll('.canvasImg,canvas');
  220. let selectCount = -1;
  221. // 处理所有canvas
  222. for (let i = 0; i < canvasImgList.length; i++) {
  223. let _parent = canvasImgList[i].parentNode
  224. let item = canvasImgList[i]
  225. // 删除克隆后的canvas节点
  226. if (item.tagName.toLowerCase() === 'canvas') {
  227. _parent.removeChild(item)
  228. } else {
  229. item.style.display = 'block'
  230. }
  231. }
  232. // 处理所有输入框
  233. for (let i = 0; i < copiedInputs.length; i++) {
  234. let item = copiedInputs[i];
  235. let typeInput = item.getAttribute('type');
  236. let copiedInput = copiedInputs[i];
  237. // 获取select标签
  238. if (!typeInput) {
  239. typeInput = item.tagName === 'SELECT' ? 'select' : item.tagName === 'TEXTAREA' ? 'textarea' : '';
  240. }
  241. // 处理input框
  242. if (item.tagName === 'INPUT') {
  243. // 除了单选框 多选框比较特别
  244. if (typeInput === 'radio' || typeInput === 'checkbox') {
  245. if (item.checked) {
  246. copiedInput.setAttribute('checked', item.checked);
  247. }
  248. } else {
  249. copiedInput.value = item.value;
  250. copiedInput.setAttribute('value', item.value);
  251. }
  252. // 处理select
  253. } else if (typeInput === 'select') {
  254. selectCount++;
  255. for (let b = 0; b < ele.querySelectorAll('select').length; b++) {
  256. let select = ele.querySelectorAll('select')[b]; // 获取原始层每一个select
  257. !select.getAttribute('newbs') && select.setAttribute('newbs', b) // 添加标识
  258. if (select.getAttribute('newbs') == selectCount) {
  259. let opSelectedIndex = ele.querySelectorAll('select')[selectCount].selectedIndex;
  260. item.options[opSelectedIndex].setAttribute('selected', true);
  261. }
  262. }
  263. // 处理textarea
  264. } else {
  265. copiedInput.innerHTML = item.value;
  266. copiedInput.setAttribute('html', item.value);
  267. }
  268. }
  269. return copy;
  270. }
  271. getPrintWindow (url) {
  272. var f = this.Iframe(url);
  273. return {
  274. f: f,
  275. win: f.contentWindow || f,
  276. doc: f.doc
  277. };
  278. }
  279. previewBoxShow () {
  280. let box = document.getElementById('vue-pirnt-nb-previewBox')
  281. if (box) {
  282. document.querySelector('html').setAttribute('style', 'overflow: hidden')
  283. box.style.display = 'block'
  284. }
  285. }
  286. previewBoxHide () {
  287. let box = document.getElementById('vue-pirnt-nb-previewBox')
  288. if (box) {
  289. document.querySelector('html').setAttribute('style', 'overflow: visible;')
  290. box.querySelector('iframe') && isRemove(box.querySelector('iframe'))
  291. box.style.display = 'none'
  292. }
  293. }
  294. previewBox () {
  295. let box = document.getElementById('vue-pirnt-nb-previewBox')
  296. let previewBodyClass = 'previewBody'
  297. if (box) {
  298. box.querySelector('iframe') && isRemove(box.querySelector('iframe'))
  299. return {
  300. close: box.querySelector('.previewClose'),
  301. previewBody: box.querySelector(`.${previewBodyClass}`)
  302. }
  303. }
  304. let previewContent = document.createElement('div');
  305. previewContent.setAttribute('id', "vue-pirnt-nb-previewBox")
  306. previewContent.setAttribute('style', 'position: fixed;top: 0px;left: 0px;width: 100%;height: 100%;background: white;display:none')
  307. previewContent.style.zIndex = this.settings.zIndex
  308. // 打印预览弹窗的header
  309. let previewHeader = document.createElement('div');
  310. previewHeader.setAttribute('class', "previewHeader")
  311. previewHeader.setAttribute('style', "padding: 5px 20px;")
  312. previewHeader.innerHTML = this.settings.previewTitle
  313. previewContent.appendChild(previewHeader)
  314. // close关闭按钮
  315. this.close = document.createElement('div');
  316. let close = this.close
  317. close.setAttribute('class', "previewClose")
  318. close.setAttribute('style', "position: absolute;top: 5px;right: 20px;width: 25px;height: 20px;cursor: pointer;")
  319. let closeBefore = document.createElement('div');
  320. let closeAfter = document.createElement('div');
  321. closeBefore.setAttribute('class', "closeBefore")
  322. closeBefore.setAttribute('style', "position: absolute;width: 3px;height: 100%;background: #040404;transform: rotate(45deg); top: 0px;left: 50%;")
  323. closeAfter.setAttribute('class', "closeAfter")
  324. closeAfter.setAttribute('style', "position: absolute;width: 3px;height: 100%;background: #040404;transform: rotate(-45deg); top: 0px;left: 50%;")
  325. close.appendChild(closeBefore)
  326. close.appendChild(closeAfter)
  327. previewHeader.appendChild(close)
  328. // 打印预览弹窗的body
  329. this.previewBody = document.createElement('div');
  330. let previewBody = this.previewBody
  331. previewBody.setAttribute('class', previewBodyClass)
  332. previewBody.setAttribute('style', "display: flex;flex-direction: column; height: 100%;")
  333. previewContent.appendChild(previewBody)
  334. // 打印预览弹窗的body的工具栏
  335. let previewBodyUtil = document.createElement('div');
  336. previewBodyUtil.setAttribute('class', "previewBodyUtil")
  337. previewBodyUtil.setAttribute('style', "height: 32px;background: #474747;position: relative;")
  338. previewBody.appendChild(previewBodyUtil)
  339. // 打印的按钮
  340. this.previewBodyUtilPrintBtn = document.createElement('div');
  341. let previewBodyUtilPrintBtn = this.previewBodyUtilPrintBtn
  342. previewBodyUtilPrintBtn.setAttribute('class', 'previewBodyUtilPrintBtn')
  343. previewBodyUtilPrintBtn.innerHTML = this.settings.previewPrintBtnLabel
  344. previewBodyUtilPrintBtn.setAttribute('style', 'position: absolute;padding: 2px 10px;margin-top: 3px;left: 24px;font-size: 14px;color: white;cursor: pointer;background-color: rgba(0,0,0,.12);background-image: linear-gradient(hsla(0,0%,100%,.05),hsla(0,0%,100%,0));background-clip: padding-box;border: 1px solid rgba(0,0,0,.35);border-color: rgba(0,0,0,.32) rgba(0,0,0,.38) rgba(0,0,0,.42);box-shadow: inset 0 1px 0 hsla(0,0%,100%,.05), inset 0 0 1px hsla(0,0%,100%,.15), 0 1px 0 hsla(0,0%,100%,.05);')
  345. previewBodyUtil.appendChild(previewBodyUtilPrintBtn)
  346. // 添加整个预览到body
  347. document.body.appendChild(previewContent);
  348. return {
  349. close: this.close,
  350. previewBody: this.previewBody
  351. }
  352. }
  353. iframeBox (frameId, url) {
  354. let iframe = document.createElement('iframe');
  355. iframe.style.border = '0px';
  356. iframe.style.position = 'absolute';
  357. iframe.style.width = '0px';
  358. iframe.style.height = '0px';
  359. iframe.style.right = '0px';
  360. iframe.style.top = '0px';
  361. iframe.setAttribute('id', frameId);
  362. iframe.setAttribute('src', url);
  363. return iframe
  364. }
  365. Iframe (url) {
  366. let frameId = this.settings.id;
  367. // 局部打印 用当前的时间做ifrmae的url
  368. url = !url ? new Date().getTime() : url
  369. let _this = this
  370. let iframe = this.iframeBox(frameId, url)
  371. // let that = this
  372. try {
  373. // 直接打印 不预览
  374. if (!this.settings.preview) {
  375. document.body.appendChild(iframe);
  376. } else {
  377. iframe.setAttribute('style', 'border: 0px;flex: 1;')
  378. // 预览打印
  379. let previewBox = this.previewBox()
  380. let previewBody = previewBox.previewBody
  381. let close = previewBox.close
  382. // 添加ifrmae到预览弹窗
  383. previewBody.appendChild(iframe);
  384. this.addEvent(close, 'click', function () {
  385. _this.previewBoxHide()
  386. })
  387. }
  388. iframe.doc = null;
  389. iframe.doc = iframe.contentDocument ? iframe.contentDocument : (iframe.contentWindow ? iframe.contentWindow.document : iframe.document);
  390. } catch (e) {
  391. throw new Error(e + '. iframes may not be supported in this browser.');
  392. }
  393. if (iframe.doc == null) {
  394. throw new Error('Cannot find document.');
  395. }
  396. return iframe;
  397. }
  398. }