netcore3.1 + vue (前后端分离)生成PDF(多pdf合并)返回前端打印

时间:2024-03-17 14:20:10

 

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 
          }
        })     
      },

}