前端报表如何实现无预览打印解决方案或静默打印

在前端开发中,除了将数据呈现后,我们往往需要为用户提供,打印,导出等能力,导出是为了存档或是二次分析,而打印则因为很多单据需要打印出来作为主要的单据来进行下一环节的票据支撑, 而前端打印可以说是非常令人头疼的一件事。

为什么令大家头疼呢?

因为前端打印,要强依赖与浏览器的打印预览页面,会天然存在以下弊端:

每一次打印都要弹出来打印预览对话框,如果前端需要批量打印,那么意味着客户要点击无数个关闭按钮,才能实现批量打印,如果一次性打印几百张上千张的报表,则会成为“NightMare”。

前端打印强依赖于浏览器,主流的思路是先将内容转换为PDF文件,再调用浏览器的打印功能进行打印,而生成PDF文件是依赖于浏览器对于字体,边线等的处理,因此浏览器的异同则直接导致打印出来的效果差距很大,有的边线加粗,有的1页数据,打印出来呈现2页,也是让开发者十分苦恼的事情,对于一些打印要求比较高的行业,这就是灾难。

因此如何在前端实现无预览打印,也就是用户点击打印之后直接就使用默认打印机打印出来。针对这个需求,我们验证了一个解决该问题的方案,本贴就来介绍该方案如何实现。

实现思路如下:

后端实现一个接口,接收Blob类型PDF流,然后调用系统默认打印机,将PDF进行静默打印。

前端利用ACTIVEREPORTSJS自带的导出PDF,导出Blob类型,然后通过POST请求调用后端接口将Blob流传给后端进行打印。

具体实现步骤:

前端实现方法:

前端利用ActivereportsJS的PDF.exportDocument无预览导出PDF,该接口返回的result包含data属性和download方法,然后调用后端接口,将result.data传递给后端。

functionprintPDF() {
    varACTIVEREPORTSJS = GC.ActiveReports.Core;
    varPDF = GC.ActiveReports.PdfExport;

    var settings = {
      info: {
        title: "test",
        author: "GrapeCity inc.",
      },
      pdfVersion: "1.7",
    };

    var pageReport = newACTIVEREPORTSJS.PageReport();
    pageReport
      .load("1.rdlx-json")
      .then(function () {
        return pageReport.run();
      })
      .then(function (pageDocument) {
        returnPDF.exportDocument(pageDocument, settings);
      })
      .then(function (result) {
        let formData = newFormData();
        formData.append("file", result.data);
        fetch("http://localhost:8088/print", {
            method: 'POST',
            mode: 'cors',
            body: formData
        })
      });
  }

后端实现方式:

我这边是采用python实现了一个接口,接收前端传递的Blob文件流,然后调用后端部署的服务器默认打印机直接进行静默打印。

后端程序可以部署到服务器上,如果是windows服务器,可以直接下载exe,在服务器上运行。

下载下来是2个exe程序,需要放在同一个文件夹,然后运行PrintAgent.exe,切记这两个程序需要放在同一个文件夹。

注意:如果exe只给服务器上部署,那么前端在打印时调用服务器地址接口打印,最终都会从服务器上连接的打印机打出来。

如果exe给客户端部署了,那么前端打印就可以代码调用localhost地址去打印,最终就会从客户端所连接的默认打印机打印出来;

切换打印机的话,就调整windows的默认打印机就可以。

Linux服务器的话需要将源码拷贝到服务器去运行。

在实现 cli 的过程中会涉及到组件名称命名方式的转换、执行cmd命令等操作,所以在开始实现创建组件前,先准备一些工具类。

cli/src/util/ 目录上一篇文章中已经创建了一个 log-utils.ts 文件,现继续创建下列四个文件:cmd-utils.tsloading-utils.tsname-utils.tstemplate-utils.ts

1.1 name-utils.ts

该文件提供一些名称组件转换的函数,如转换为首字母大写或小写的驼峰命名、转换为中划线分隔的命名等:

/**
 * 将首字母转为大写
 */exportconst convertFirstUpper = (str: string): string => {
  return`${str.substring(0, 1).toUpperCase()}${str.substring(1)}`
}
/**
 * 将首字母转为小写
 */exportconst convertFirstLower = (str: string): string => {
  return`${str.substring(0, 1).toLowerCase()}${str.substring(1)}`
}
/**
 * 转为中划线命名
 */exportconst convertToLine = (str: string): string => {
  returnconvertFirstLower(str).replace(/([A-Z])/g, '-$1').toLowerCase()
}
/**
 * 转为驼峰命名(首字母大写)
 */exportconst convertToUpCamelName = (str: string): string => {
  let ret = ''const list = str.split('-')
  list.forEach(item => {
    ret += convertFirstUpper(item)
  })
  returnconvertFirstUpper(ret)
}
/**
 * 转为驼峰命名(首字母小写)
 */exportconst convertToLowCamelName = (componentName: string): string => {
  returnconvertFirstLower(convertToUpCamelName(componentName))
}

1.2 loading-utils.ts

在命令行中创建组件时需要有 loading 效果,该文件使用 ora 库,提供显示 loading 和关闭 loading 的函数:

import ora from'ora'letspinner: ora.Ora | null = nullexportconstshowLoading = (msg: string) => {
  spinner = ora(msg).start()
}

exportconstcloseLoading = () => {
  if (spinner != null) {
    spinner.stop()
  }
}

1.3 cmd-utils.ts

该文件封装 shelljs 库的 execCmd 函数,用于执行 cmd 命令:

import shelljs from'shelljs'import { closeLoading } from'./loading-utils'exportconstexecCmd = (cmd: string) => newPromise((resolve, reject) => {
  shelljs.exec(cmd, (err, stdout, stderr) => {
    if (err) {
      closeLoading()
      reject(newError(stderr))
    }
    returnresolve('')
  })
})

1.4 template-utils.ts

由于自动创建组件需要生成一些文件,template-utils.ts 为这些文件提供函数获取模板。由于内容较多,这些函数在使用到的时候再讨论。

2 参数实体类

执行 gen 命令时,会提示开发人员输入组件名、中文名、类型,此外还有一些组件名的转换,故可以将新组件的这些信息封装为一个实体类,后面在各种操作中,传递该对象即可,从而避免传递一大堆参数。

2.1 component-info.ts

src 目录下创建 domain 目录,并在该目录中创建 component-info.ts ,该类封装了组件的这些基础信息:

import * as path from'path'import { convertToLine, convertToLowCamelName, convertToUpCamelName } from'../util/name-utils'import { Config } from'../config'exportclassComponentInfo {
  /** 中划线分隔的名称,如:nav-bar */lineName: string/** 中划线分隔的名称(带组件前缀) 如:yyg-nav-bar */lineNameWithPrefix: string/** 首字母小写的驼峰名 如:navBar */lowCamelName: string/** 首字母大写的驼峰名 如:NavBar */upCamelName: string/** 组件中文名 如:左侧导航 */zhName: string/** 组件类型 如:tsx */type: 'tsx' | 'vue'/** packages 目录所在的路径 */parentPath: string/** 组件所在的路径 */fullPath: string/** 组件的前缀 如:yyg */prefix: string/** 组件全名 如:@yyg-demo-ui/xxx */nameWithLib: stringconstructor (componentName: string, description: string, componentType: string) {
    this.prefix = Config.COMPONENT_PREFIXthis.lineName = convertToLine(componentName)
    this.lineNameWithPrefix = `${this.prefix}-${this.lineName}`this.upCamelName = convertToUpCamelName(this.lineName)
    this.lowCamelName = convertToLowCamelName(this.upCamelName)
    this.zhName = description
    this.type = componentType === 'vue' ? 'vue' : 'tsx'this.parentPath = path.resolve(__dirname, '../../../packages')
    this.fullPath = path.resolve(this.parentPath, this.lineName)
    this.nameWithLib = `@${Config.COMPONENT_LIB_NAME}/${this.lineName}`
  }
}

2.2 config.ts

上面的实体中引用了 config.ts 文件,该文件用于设置组件的前缀和组件库的名称。在 src 目录下创建 config.ts

exportconstConfig = {
  /** 组件名的前缀 */COMPONENT_PREFIX: 'yyg',
  /** 组件库名称 */COMPONENT_LIB_NAME: 'yyg-demo-ui'
}

3 创建新组件模块

3.1 概述

上一篇开篇讲了,cli 组件新组件要做四件事:

  1. 创建新组件模块;

  1. 创建样式 scss 文件并导入;

  1. 在组件库入口模块安装新组件模块为依赖,并引入新组件;

  1. 创建组件库文档和 demo。

本文剩下的部分分享第一点,其余三点下一篇文章分享。

src 下创建 service 目录,上面四个内容拆分在不同的 service 文件中,并统一由 cli/src/command/create-component.ts 调用,这样层次结构清晰,也便于维护。

首先在 src/service 目录下创建 init-component.ts 文件,该文件用于创建新组件模块,在该文件中要完成如下几件事:

  1. 创建新组件的目录;

  1. 使用 pnpm init 初始化 package.json 文件;

  1. 修改 package.json 的 name 属性;

  1. 安装通用工具包 @yyg-demo-ui/utils 到依赖中;

  1. 创建 src 目录;

  1. 在 src 目录中创建组件本体文件 xxx.tsx 或 xxx.vue;

  1. 在 src 目录中创建 types.ts 文件;

  1. 创建组件入口文件 index.ts。

Vue前端实现静默打印的方法可以通过以下步骤实现: 1. 首先,引入一个隐藏的iframe元素,并将其设置为不可见。这可以通过在Vue组件的`template`中添加一个`<iframe>`元素,并设置属性`style="display: none;"`来实现。 2. 在需要进行静默打印的逻辑代码中,通过获取该iframe元素的引用,并设置其src属性为需要打印的内容。这可以通过在Vue组件的`methods`中添加一个方法,然后在需要打印的地方调用该方法来实现。 3. 通过在iframe元素的`load`事件中执行打印操作,实现静默打印。在Vue组件的`mounted`生命周期钩子中,监听iframe元素的`load`事件,并在事件触发时执行打印操作。 综上所述,以下是一个示例的Vue组件代码,实现静默打印的功能: ```vue <template> <div> <iframe ref="printIframe" style="display: none;"></iframe> <button @click="handlePrint">打印</button> </div> </template> <script> export default { methods: { handlePrint() { const printContent = '<h1>要打印的内容</h1>'; // 替换成需要打印的内容 const iframe = this.$refs.printIframe; iframe.srcdoc = printContent; } }, mounted() { const iframe = this.$refs.printIframe; iframe.onload = () => { iframe.contentWindow.print(); }; } }; </script> ``` 在上面的示例中,`handlePrint`方法用于设置iframe元素的srcdoc属性为需要打印的内容。`mounted`生命周期钩子监听iframe元素的load事件,事件触发后执行iframe.contentWindow.print()操作进行静默打印。通过点击"打印"按钮,即可实现静默打印功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盈梓的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值