1.使用Adobe Acrobat XI Pro编辑pdf模板

2.公共类代码

3.service层调用

4.Controller层

5.前端(Vue)

因为print.js不支持宋体,所以打算用后台netcore生成pdf打印,最开始生成pdf后向前台返回pdf的base64编码,但是响应时间太长,不太适合需求。

后来想着直接返回文件流给前端,前端收到pdf直接使用浏览器页面浏览,浏览器自带打印功能,省了很多事。话不多说,上代码,如有错误,请指出!!!

代码大都是网络上搜的,但多多少少有些问题,自己结合业务也做了修改

1.使用Adobe Acrobat XI Pro编辑pdf模板

Adobe Acrobat XI Pro安装包

链接:https://pan.baidu.com/s/1gLtZaGOBBx-lY_QwWTMJCg
提取码:21um

可以先使用world编辑样式

使用Adobe Acrobat XI Pro 进行编辑

以下是效果图

2.公共类代码

  1     /// <summary>
2 /// PDF导出类
3 /// </summary>
4 public class PdfLeadingHelper
5 {
6
7 private static readonly ILog log = LogManager.GetLogger(typeof(PdfLeadingHelper));
8 /// <summary>
9 /// 根据路径获取模板
10 /// </summary>
11 /// <param name="pdfTemplate"></param>
12 /// <returns></returns>
13 public static Dictionary<string, string> ReadForm(string pdfTemplate)
14 {
15 //if (Directory.Exists(PathHelper.OutPutPDFPath))
16 //{
17 // DelectDir(PathHelper.OutPutPDFPath);
18 //}
19
20 DeleteAllPdf(PathHelper.OutPutPDFPath);
21
22 Dictionary<string, string> dic = new Dictionary<string, string>();
23
24 PdfReader pdfReader = null;
25 try
26 {
27 pdfReader = new PdfReader(pdfTemplate);
28 AcroFields pdfFormFields = pdfReader.AcroFields;
29 foreach (DictionaryEntry de in pdfFormFields.Fields)
30 {
31 dic.Add(de.Key.ToString(), "");
32 }
33 }
34 catch (Exception ex)
35 {
36 dic = null;
37 //记录日志 注释
38 // LogHelper.Logger(LogLevel.Error, "pdf导出类发生异常:根据路径获取模板时异常" + ex.ToString(), ex);
39 log.Error($"{ex.Message}");
40 }
41 finally
42 {
43 if (pdfReader != null)
44 {
45 pdfReader.Close();
46 }
47 }
48 return dic;
49 }
50
51 public static void DelectDir(string srcPath)
52 {
53 try
54 {
55 DirectoryInfo dir = new DirectoryInfo(srcPath);
56 FileSystemInfo[] fileinfo = dir.GetFileSystemInfos(); //返回目录中所有文件和子目录
57 foreach (FileSystemInfo i in fileinfo)
58 {
59 if (i is DirectoryInfo) //判断是否文件夹
60 {
61 DirectoryInfo subdir = new DirectoryInfo(i.FullName);
62 subdir.Delete(true); //删除子目录和文件
63 }
64 else
65 {
66 File.Delete(i.FullName); //删除指定文件
67 }
68 }
69 }
70 catch (Exception ex)
71 {
72 log.Error($"{ex.Message}");
73 }
74 }
75
76
77 ///
78 /// 向pdf模版填充内容,并生成新的文件
79 ///
80 /// 模版路径
81 /// 生成文件保存路径
82 /// 标签字典(即模版中需要填充的控件列表)
83 public static bool FillForm(string pdfTemplate, string newFile, Dictionary<string, string> dic)
84 {
85 bool rsBool = true;
86 PdfReader pdfReader = null;
87 PdfStamper pdfStamper = null;
88 FileStream fileStream = null;
89 try
90 {
91 fileStream = new FileStream(newFile, FileMode.Create);
92 pdfReader = new PdfReader(pdfTemplate);
93 pdfStamper = new PdfStamper(pdfReader, fileStream);
94 AcroFields pdfFormFields = pdfStamper.AcroFields;
95 //设置支持中文字体
96 BaseFont baseFont = BaseFont.CreateFont("C:\\WINDOWS\\FONTS\\STSONG.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
97 pdfFormFields.AddSubstitutionFont(baseFont);
98
99 foreach (var de in dic)
100 {
101 pdfFormFields.SetField(de.Key, de.Value + "");
102 }
103 pdfStamper.FormFlattening = true;
104 }
105 catch (Exception ex)
106 {
107 //记录日志 注释
108 // LogHelper.Logger(LogLevel.Error, "pdf导出类发生异常:向pdf模版填充内容,并生成新的文件时异常"+ex.ToString(), ex);
109 log.Error($"{ex.Message}");
110 rsBool = false;
111 }
112 finally
113 {
114 if (pdfStamper != null)
115 {
116 pdfStamper.Close();
117 }
118 if (pdfReader != null)
119 {
120 pdfReader.Close();
121 }
122 if (fileStream!=null)
123 {
124 fileStream.Close();
125 fileStream.Dispose();
126 }
127 }
128 return rsBool;
129 }
130
131
132
133 /// <summary>
134 /// 根据模板导出PDF
135 /// </summary>
136 /// <param name="PDFTemplatePath">PDF模板</param>
137 /// <param name="keyValuePairs">文本的键值对</param>
138 /// <param name="ImageKeyValue">图片键值对</param>
139 /// <param name="fontPath">文本</param>
140 /// <returns></returns>
141 public static byte[] PDFTemplate(string PDFTemplatePath, Dictionary<string, string> keyValuePairs, string fontPath = @"C:\Windows\Fonts\simfang.ttf")
142 {
143 //Image image = Image.GetInstance(@"C:\Users\Administrator\Desktop\二维码图片_8月5日09时58分55秒.png");
144 //return null;
145 //获取中文字体,第三个参数表示为是否潜入字体,但只要是编码字体就都会嵌入。
146 BaseFont baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
147 //读取模板文件
148 PdfReader reader = new PdfReader(PDFTemplatePath);
149
150 //创建文件流用来保存填充模板后的文件
151 MemoryStream stream = new MemoryStream();
152
153 PdfStamper stamp = new PdfStamper(reader, stream);
154 //设置表单字体,在高版本有用,高版本加入这句话就不会插入字体,低版本无用
155 stamp.AcroFields.AddSubstitutionFont(baseFont);
156
157 AcroFields form = stamp.AcroFields;
158
159 //表单文本框是否锁定
160 stamp.FormFlattening = true;
161
162 //填充表单,para为表单的一个(属性-值)字典
163 foreach (KeyValuePair<string, string> parameter in keyValuePairs)
164 {
165 //要输入中文就要设置域的字体;
166 form.SetFieldProperty(parameter.Key, "textfont", baseFont, null);
167 //为需要赋值的域设置值;
168 form.SetField(parameter.Key, parameter.Value);
169 }
170
171 //按顺序关闭io流
172
173 stamp.Close();
174 reader.Close();
175
176 return stream.ToArray();
177 }
178
179
180 /// <summary>
181 /// 读取合并的pdf文件名称
182 /// </summary>
183 /// <param name="Directorypath">目录</param>
184 /// <param name="outpath">导出的路径</param>
185 public static bool MergePDF(string Directorypath, string outpath)
186 {
187 try
188 {
189 List<string> filelist2 = new List<string>();
190 System.IO.DirectoryInfo di2 = new System.IO.DirectoryInfo(Directorypath);
191 FileInfo[] ff2 = di2.GetFiles("*.pdf");
192 BubbleSort(ff2);
193 foreach (FileInfo temp in ff2)
194 {
195 filelist2.Add(Directorypath + temp.Name);
196 }
197 mergePDFFiles(filelist2, outpath);
198
199 //DeleteAllPdf(Directorypath);
200 return true;
201 }
202 catch (Exception ex)
203 {
204 log.Error($"{ex.Message}");
205 return false;
206 }
207
208 }
209
210
211 /// <summary>
212 /// 冒泡排序
213 /// </summary>
214 /// <param name="arr">文件名数组</param>
215 public static void BubbleSort(FileInfo[] arr)
216 {
217 for (int i = 0; i < arr.Length; i++)
218 {
219 for (int j = i; j < arr.Length; j++)
220 {
221 if (arr[i].LastWriteTime > arr[j].LastWriteTime)//按创建时间(升序)
222 {
223 FileInfo temp = arr[i];
224 arr[i] = arr[j];
225 arr[j] = temp;
226 }
227 }
228 }
229 }
230
231 /// <summary>
232 /// 合成pdf文件
233 /// </summary>
234 /// <param name="fileList">文件名list</param>
235 /// <param name="outMergeFile">输出路径</param>
236 public static void mergePDFFiles(List<string> fileList, string outMergeFile)
237 {
238 PdfReader reader;
239 // Rectangle rec = new Rectangle(1660, 1000);
240 PdfWriter writer;
241 Document document = new Document();
242 FileStream fileStream = new FileStream(outMergeFile, FileMode.Create);
243 writer = PdfWriter.GetInstance(document, fileStream);
244 document.Open();
245 PdfContentByte cb = writer.DirectContent;
246 PdfImportedPage newPage;
247 for (int i = 0; i < fileList.Count; i++)
248 {
249 reader = new PdfReader(fileList[i]);
250 int iPageNum = reader.NumberOfPages;
251 for (int j = 1; j <= iPageNum; j++)
252 {
253 document.NewPage();
254 newPage = writer.GetImportedPage(reader, j);
255 cb.AddTemplate(newPage, 0, 0);
256 }
257 }
258 document.Close();
259 fileStream.Close();
260 fileStream.Dispose();
261 }
262
263
264
265 /// <summary>
266 /// 删除一个文件里所有的文件
267 /// </summary>
268 /// <param name="Directorypath">文件夹路径</param>
269 public static void DeleteAllPdf(string Directorypath)
270 {
271 System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(Directorypath);
272 if (di.Exists)
273 {
274 FileInfo[] ff = di.GetFiles("*.pdf");
275 foreach (FileInfo temp in ff)
276 {
277 File.Delete(Directorypath + "\\" + temp.Name);
278 }
279 }
280 }
281 }

3.service层调用

按照模板生成dictionary,然后数据库查询后赋值,如果数据过多,需要生成多个pdf,然后合并

最后一次数据循环,需要清空dictionary的value值,因为可能没有覆盖,显示的还是之前的值

 1  public string CreateVeteranDirectory(int dType, List<TBase_VeteranDirectory> veteranDirectors)
2 {
3 List<TBase_VeteranDirectory> veteranDirectoryList = new List<TBase_VeteranDirectory>();
4 Dictionary<string, string> dicData = PdfLeadingHelper.ReadForm(PathHelper.VeteranDirectoryPDF);
5 dicData["DocumentType"] = dType == 0 ? "一" : dType == 1 ? "二" : "三";
6 int cnt = veteranDirectors.Count / 19 + 1;
7 for (int j = 0; j < cnt; j++)
8 {
9 if (j + 1 == cnt)
10 {
11 veteranDirectoryList = veteranDirectors.Skip(j * 19).Take(veteranDirectors.Count).ToList();
12 string[] keys = dicData.Keys.ToArray();
13 for (int i = 0; i < keys.Length; i++)
14 {
15 dicData[keys[i]] = "";
16 }
17 dicData["DocumentType"] = dType == 0 ? "一" : dType == 1 ? "二" : "三";
18 }
19 else
20 {
21 veteranDirectoryList = veteranDirectors.Skip(j * 19).Take(19 * (j + 1)).ToList();
22 }
23 for (int i = 1; i <= veteranDirectoryList.Count; i++)
24 {
25 dicData["MaterialName_" + i] = veteranDirectoryList[i - 1].MaterialName;
26 dicData["MaterialYear_" + i] = veteranDirectoryList[i - 1].MaterialYear.ToString();
27 dicData["MaterialMonth_" + i] = veteranDirectoryList[i - 1].MaterialMonth.ToString();
28 dicData["MaterialDay_" + i] = veteranDirectoryList[i - 1].MaterialDay.ToString();
29 dicData["Page_" + i] = veteranDirectoryList[i - 1].Page.ToString();
30 dicData["Collator_" + i] = veteranDirectoryList[i - 1].Collator.ToString();
31 }
32 PdfLeadingHelper.FillForm(PathHelper.VeteranDirectoryPDF, $"{ PathHelper.OutPutPDFPath}VeteranDirectory{j}.pdf", dicData);
33 }
34 bool res = PdfLeadingHelper.MergePDF(PathHelper.OutPutPDFPath, PathHelper.VeteranDirectoryOutPutPDF);
35 return res ? PathHelper.VeteranDirectoryOutPutPDF : "";
}

4.Controller层

返回File就可以了

 1         /// <summary>
2 /// pdf下载
3 /// </summary>
4 /// <returns></returns>
5 [HttpGet]
6 public async Task<IActionResult> DownloadDocumentPDFByType(int vId, int dType)
7 {
8 Expression<Func<TBase_VeteranDirectory, bool>> whereExpression = s => s.IsDelected == 0 && s.VId == vId && s.DType == dType;
9 var veteranDirectoryList = await _tVeteranDiretoryService.Query(whereExpression);
10 string filepath = _tVeteranDiretoryService.CreateVeteranDirectory(dType, veteranDirectoryList);
11 var provider = new FileExtensionContentTypeProvider();
12 FileInfo fileInfo = new FileInfo(filepath);
13 var ext = fileInfo.Extension;
14 new FileExtensionContentTypeProvider().Mappings.TryGetValue(ext, out var contenttype);
15 return File(System.IO.File.ReadAllBytes(filepath), contenttype ?? "application/octet-stream", fileInfo.Name);
16 }
17
18
19 /// <summary>
20 /// 返回pdf路径
21 /// </summary>
22 /// <returns></returns>
23 [HttpGet]
24 public async Task<MessageModel<string>> GetDocumentPDFPathByType(int vId, int dType)
25 {
26 Expression<Func<TBase_VeteranDirectory, bool>> whereExpression = s => s.IsDelected == 0 && s.VId == vId && s.DType == dType;
27 var veteranDirectoryList = await _tVeteranDiretoryService.Query(whereExpression);
28 string filepath = _tVeteranDiretoryService.CreateVeteranDirectory(dType, veteranDirectoryList);
29 var serverPathList = await _tImgServerPathService.Query();
30 string serverPath = serverPathList.FirstOrDefault().ServerPath + "\\pdf\\VeteranDirectory.pdf";
31 string browserPath = serverPathList.FirstOrDefault().BrowserPath + "/pdf/VeteranDirectory.pdf";
32 System.IO.File.Copy(filepath, serverPath,true);
33 return new MessageModel<string>()
34 {
35 msg = "获取成功",
36 success = true,
37 response = browserPath
38 };
39 }

 5.前端(Vue)

 <el-dialog
title="预览"
:visible.sync="pdfVisible"
:close-on-click-modal="false"
:append-to-body="true"
width="80%"
top="3vh"
:before-close="handleClose"
:close-on-press-escape="true">
<span>
<iframe :src="pdfUrl" frameborder="0" width="100%" height="550px"></iframe>
</span>
</el-dialog> data(){
      pdfUrl: '', //预览地址    
  },
methods:{
      // 第一部分打印
      firstPrint: function() {
        getDocumentPDFPathByType({
          vId: this.id,
          dType: 0,
        }).then(res=>{
          if (res.data.status == 200) {
            // console.log(res);
            this.pdfVisible = true;
            this.pdfUrl = res.data.response; //请求完接口赋值pdfUrl 
          }
        })     
      },

}

netcore3.1 + vue (前后端分离)生成PDF(多pdf合并)返回前端打印的更多相关文章

  1. netcore3.1 + vue (前后端分离) ElementUI多文件带参数上传

    vue前端代码 前端主要使用了ElementUI的el-uploda插件,除去业务代码需要注意的是使用formdata存储片上传时所需的参数 <el-upload class="upl ...

  2. netcore3.1 + vue (前后端分离)Excel导入

    1.前端(vue)代码 2.公共类ExcelHelper 3.后端(netcore)代码 思路:导入类似于上传,将excel上传后将流转换为数据 1.前端(Vue)代码 这里使用的是ElementUI ...

  3. netcore3.1 + vue (前后端分离) IIS 部署

    1.安装 aspnetcore-runtime-3.1.1-win-x64.exe 2.安装dotnet-hosting-3.1.1-win.exe 3.安装urlrewrite和applicatio ...

  4. SpringBoot + Vue前后端分离图片上传到本地并前端访问图片

    同理应该可用于其他文件 图片上传 application.yml 配置相关常量 prop: upload-folder: E:/test/ # 配置SpringMVC文件上传限制,默认1M.注意MB要 ...

  5. Flask + vue 前后端分离的 二手书App

    一个Flask + vue 前后端分离的 二手书App 效果展示: https://blog.csdn.net/qq_42239520/article/details/88534955 所用技术清单 ...

  6. 解决Django+Vue前后端分离的跨域问题及关闭csrf验证

      前后端分离难免要接触到跨域问题,跨域的相关知识请参:跨域问题,解决之道   在Django和Vue前后端分离的时候也会遇到跨域的问题,因为刚刚接触Django还不太了解,今天花了好长的时间,查阅了 ...

  7. Spring Boot + Vue 前后端分离开发,前端网络请求封装与配置

    前端网络访问,主流方案就是 Ajax,Vue 也不例外,在 Vue2.0 之前,网络访问较多的采用 vue-resources,Vue2.0 之后,官方不再建议使用 vue-resources ,这个 ...

  8. 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  9. 两个开源的 Spring Boot + Vue 前后端分离项目

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

随机推荐

  1. Django使用Ace实现在线编辑器

    前言:最近自己开发SQL工单功能,期间接触到了Ace在线编辑器,折腾一下,感觉功能挺多,特意去了解学习一下分享跟大家. ACE 是一个功能非常强大的编辑器,实现语法高亮.代码补全功能,还有很多主题,支 ...

  2. spring模板

    applicationContext <?xml version="1.0" encoding="UTF-8"?> <beans xmlns= ...

  3. Docker删除某个容器时失败解决方案

    删除某个容器时,报错 ocker rm 容器id   image is being used by stopped container e11efb30362a   该报错的原因是要删除的该镜像,被某 ...

  4. 向量算子优化Vector Operation Optimization

    向量算子优化Vector Operation Optimization 查看MATLAB命令View MATLAB Command 示例显示Simulink编码器 ,将生成向量的块输出,设置为标量,优 ...

  5. GPU编程和流式多处理器(三)

    GPU编程和流式多处理器(三) 3. Floating-Point Support 快速的本机浮点硬件是GPU的存在理由,并且在许多方面,它们在浮点实现方面都等于或优于CPU.全速支持异常可以根据每条 ...

  6. 多实例gpu_MIG技术快速提高AI生产率

    多实例gpu_MIG技术快速提高AI生产率 Ride the Fast Lane to AI Productivity with Multi-Instance GPUs 一.平台介绍 NVIDIA安培 ...

  7. 嵌入式Linux设备驱动程序:在运行时读取驱动程序状态

    嵌入式Linux设备驱动程序:在运行时读取驱动程序状态 Embedded Linux device drivers: Reading driver state at runtime 在运行时了解驱动程 ...

  8. python----日志模块loggin的使用,按日志级别分类写入文件

    1.日志的级别 日志一共分为5个等级,从低到高分别是: 级别 说明 DEBUG 输出详细的运行情况,主要用于调试. INFO 确认一切按预期运行,一般用于输出重要运行情况. WARNING 系统运行时 ...

  9. 通过Dapr实现一个简单的基于.net的微服务电商系统(十七)——服务保护之动态配置与热重载

    在上一篇文章里,我们通过注入sentinel component到apigateway实现了对下游服务的保护,不过受限于目前变更component需要人工的重新注入配置以及重启应用更新componen ...

  10. python+requests接口用例

    本实例通过请求接口登录系统,获取了配置项的ID,并最终实现了对配置项的默认值进行修改 使用到的接口请求方法有:get(查询) ,post(新增),put(修改) 遇到的阻碍点见下面具体代码处的详解 编 ...