1. 程式人生 > 其它 >vue+java建立word和pdf文件(XWPFDocument)

vue+java建立word和pdf文件(XWPFDocument)

A前端程式碼

1.線上檢視按鈕和方法

在診斷報告管理中,新增線上檢視的按鈕,新增點選事件@click="lookUp"

<template slot="menuLeft">
  <!--線上檢視的按鈕-->
  <el-button size="small"
             class="el-button--primary"
             v-if="see"
             @click="lookUp"
             icon="el-icon-view">{{$t('button.reportView')}}</el-button>
</template>

後面的methods裡,有線上查件具體的方法:this.$router.resolve是固定寫法,就是開啟一個新頁面。

//線上檢視方法
lookUp:function(){
  if (!this.radio) {
    this.$message.error(this.$t('tips.messageDel'));
  }else{
    let routeData = this.$router.resolve({
      path:'/showDiagnose',//因為後面配置了路由,所以就這樣寫就行
      query:{ id : this.radio }//這個是傳遞的引數,ID值
    });
    window.open(routeData.href, '_blank')
  }
},

2.配置路由路徑

像這種開啟新頁面,要配置路由路徑

{
  path: '/forgetpassword',
  name: '忘記密碼',
  component: () =>
    import( /* webpackChunkName: "page" */ '@/page/login/forgetpassword'),
  meta: {
    keepAlive: true,
    isTab: false,
    isAuth: false
  }
},
{
  path: '/showDiagnose',
  name: '診斷報告',
  component: () =>
    import( /* webpackChunkName: "page" */ '@/page/showDiagnose'),
  meta: {
    keepAlive: true,
    isTab: false,
    isAuth: false
  }
},

3.開啟的新頁面接受引數

新頁面showDiagnose,新寫一個新頁面並接受之前頁面傳遞過來的ID

新頁面:

<template>
  <div class="dashboard-editor-container">//右側滾動條
    <!--報告正文內容-->
      //左上角的logo圖片
    <img src="../../public/img/logo.png" crossorigin="anonymous" height="114" width="147" alt=""  style="text-align: left">
      
    <!--兩個下載按鈕,放在了文章前面-->
    <el-row style="text-align:center;margin-left: 1200px">
      //注意,el-row中的內容如果居中的話,用style="text-align:center;
      
      <el-button type="danger" size="small" @click="downLoadWord">WORD下載</el-button>
      <el-button type="danger" size="small" @click="downLoadPdf">PDF下載</el-button>
    </el-row>
      
    <h1 style="text-align: center;
               font-size: 38px;
               color: rgba(17,19,19,0.77);">裝置診斷報告書</h1>
    <br/>

    <h2 style="margin-left: 20px;
               font-size: 30px;
               color: rgba(17,19,19,0.77)">Observations of failure 問題描述:</h2>
    <ul>
       
       /*裝置名稱後面接的form是底下return {}裡面的form表單,但是這個form表單沒有資料,
      	因為後面頁面建立的時候,就會走getDetail(id)這個方法,查出來的資料,和
       */
      <li style="font-size:20px; margin-bottom: 5px;">裝置SN:{{form.sn}}</li>
      <li style="font-size:20px; margin-bottom: 5px;">裝置名稱:{{form.deviceName}}</li>
      <li style="font-size:20px; margin-bottom: 5px;">問題描述:{{form.problemDescription}}</li>
    </ul>

    <h2 style="margin-left: 20px;
               font-size: 30px;
               color: rgba(17,19,19,0.77)">Estimation of reason原因分析:</h2>
    <ul>
      <li style="font-size:20px; margin-bottom: 5px;">{{form.estimationReason}}</li>
    </ul>

    <h2 style="margin-left: 20px;
               font-size: 30px;
               color: rgba(17,19,19,0.77)">Spare parts備件:</h2>
    <ul>
      <li style="font-size:20px; margin-bottom: 5px;">{{form.spareParts}}</li>
    </ul>

    <h2 style="margin-left: 20px;
               font-size: 30px;
               color: rgba(17,19,19,0.77)">Final result結果/反饋:</h2>
    <ul>
      <li style="font-size:20px; margin-bottom: 5px;">{{form.finalResult}}</li>
    </ul>

    <h2 style="margin-left: 20px;
               font-size: 30px;
               color: rgba(17,19,19,0.77)">Recommendation建議:</h2>
    <ul>
      <li style="font-size:20px; margin-bottom: 5px;">{{form.recommendation}}</li>
    </ul>

  </div>
</template>
<script>

import {getDetail, downloadWord, downloadPdf} from '../api/monitor/diagnose'

export default {
  data() {
    return {
      htmlTitle: "裝置診斷報告書",
      form: {
        
      }
    }
  },


  //頁面建立的時候,會走這個裡面,生命週期方法,也就是說這個頁面剛建立的時候,ID就傳過來了,
  //同時也會走getDetail(id)這個方法
  created() {
      //用this.$route點後面的引數,接受上一個頁面傳遞過來的id
    let id = this.$route.query.id
      /*把ID傳到getDetail方法中,得到結果res,把查詢到的內容附到
        form: {
        
        }
      這個表單中,相當於這個表單中轉一下。
      */
    getDetail(id).then(res => {
      this.form = res.data.data;
    })
  },

  methods: {
    //下載word的方法
    downLoadWord() {
        //先是把id引過來
      let id = this.$route.query.id
      downloadWord(id).then(res => {
    	//先判斷res.data有沒有這個資料,然後判斷code等不等於200,因為成功是200,失敗是400
        if (res.data && res.data.code === 200) {
            
          window.location.href = "/api" + res.data.data;
          /*windows.location.href="/url" 當前頁面開啟URL頁面
            詳細記錄是:https://blog.csdn.net/sdta25196/article/details/78799338
            經過測試:res.data.data: /upload/1596791395547.docx,也就是說後端傳回來的就是:
            /upload/1596791395547.docx這個路徑
            
            而開啟的路徑就是:/api/upload/1596791395547.docx
            在vue.config.jswe檔案中設定:
              devServer: {
                    port: 1888,
                    proxy: {
                      '/api': {
                        //本地服務介面地址
                        target: 'http://localhost:8088',
                        //遠端演示服務地址,可用於直接啟動專案
                        //target: 'https://saber.bladex.vip/api',
                        ws: true,
                        pathRewrite: {
                          '^/api': '/'
                        }
                      }
                    }
                  }
          */
            後臺的路徑,前臺轉換成/api
        }
        //
      })
    },
    // 下載pdf,和之前下載word也是一樣的
    downLoadPdf() {
      let id = this.$route.query.id
      downloadPdf(id).then(res => {
        console.log(res)
        if (res.data && res.data.code === 200) {
          window.location.href = "/api" + res.data.data;
        }
        //
      })
    }

  }
}
</script>
<!--右側滾動條-->
 //這是網上查到的右側滾動條的程式碼
<style rel="stylesheet/scss" lang="scss" scoped>
  .dashboard-editor-container {//這個是滾動條的類的設定
    padding: 15px;
    background-color: #ffffff;

    overflow-y: auto;
    height: 672px;

    .chart-wrapper {
      background: #ffffff;
      padding: 16px 16px 0;
      margin-bottom: 32px;
    }
  }
</style>

4.在src/api中

//get方法,引數只有一個,即id

export const downloadWord = (id) => {
  return request({
    url: '/api/diagnose/downloadWord',
    method: 'get',
    params: {id}
  })
};

export const downloadPdf = (id) => {
  return request({
    url: '/api/diagnose/downloadPdf',
    method: 'get',
    params: {id}
  })
};

5.vue.config.js檔案

          devServer: {
                port: 1888,
                proxy: {
                  '/api': {
                    //本地服務介面地址
                    target: 'http://localhost:8088',
                    //遠端演示服務地址,可用於直接啟動專案
                    //target: 'https://saber.bladex.vip/api',
                    ws: true,
                    pathRewrite: {//反向代理
                      '^/api': '/'
                    }
                  }
                }
              }

B後端程式碼

1.在控制器層:DiagnoseController

返回的型別是String型別

而返回的是路徑

/**
 * 下載報告word版本
 */
@GetMapping("/downloadWord")
public R<String> downloadWord(@RequestParam Long id) {
    String path = diagnoseService.downloadWord(id);
    //返回的是地址
    return R.data(path);
}

/**
 * 下載報告PDF版本
 */
@GetMapping("/downloadPdf")
public R<String> downloadPdf(@RequestParam Long id) {
    String path = diagnoseService.downloadPdf(id);
    return R.data(path);
}

點選方法,進入業務層介面

2.在業務層介面:IDiagnoseService

    /**
     * 下載word版診斷報告
     * @param id 診斷報告id
     * @return 診斷報告word檔案路徑
     */
    String downloadWord(Long id);

    /**
     * 下載Pdf版診斷報告
     * @param id 診斷報告id
     * @return 診斷報告Pdf檔案路徑
     */
    String downloadPdf(Long id);

點選抽象方法,進入業務層實現類

3.在業務層實現類DiagnoseServiceImpl

package com.tcb.monitor.service.impl;


/**
 * 診斷報告業務層實現類
 *
 * @author Charles
 * @since 2020/07/21
 */
@Service
@Slf4j
public class DiagnoseServiceImpl extends BaseServiceImpl<DiagnoseMapper, Diagnose> implements IDiagnoseService {

    @Resource
    IDepartmentService departmentService;

    @Resource
    DiagnoseMapper diagnoseMapper;

    @Value("${upload.path}")
    private String uploadPath;
	
    //下載word 的方法
    @Override
    public String downloadWord(Long id) {
        //用之前的查詢詳情的方法,根據id返回的是VO
        DiagnoseDetailVo vo = getDiagDetail(id);
        //generateWordFile為自己寫的方法,在後面
        //根據VO,返回的是檔名
        String fileName = generateWordFile(vo);
        
        // 3. 將檔案路徑返回給伺服器
        return "/upload/" + fileName;
    }

    @Override
    public String downloadPdf(Long id) {
        //根據id返回VO類的物件
        DiagnoseDetailVo vo = getDiagDetail(id);
        //這裡是因為丟擲異常
        String fileName = "";
        try {
            fileName = generatePdfFile(vo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 3. 將檔案路徑返回給伺服器
        return "/upload/" + fileName;
    }

    private String generatePdfFile(DiagnoseDetailVo vo) throws DocumentException, IOException, FontFormatException {
        // 1. 生成pdf文件
/*https://www.runoob.com/java/java-bytearrayoutputstream.html
位元組陣列輸出流在記憶體中建立一個位元組陣列緩衝區,所有傳送到輸出流的資料儲存在該位元組陣列緩衝區中。建立位元組陣列輸出流物件有以下幾種方式。下面的構造方法建立一個32位元組(預設大小)的緩衝區。
OutputStream bOut = new ByteArrayOutputStream();*/
        
        //位元組陣列輸出流,輸出流名稱:stream
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        
        //此處根據excel大小設定pdf紙張大小
        Document document = new Document(PageSize.A4);
        //將PDF文件物件寫入到流
        PdfWriter.getInstance(document, stream);
        //設定頁邊距
        document.setMargins(30, 30, 15, 15);
        /*
        Write物件建立之後
        首先開啟documet(這個過程就像我們建立一個空的pdf檔案,然後開啟來創作一樣)
        然後開始寫入資料
        設定文件屬性
        最後關閉
        */
        document.open();
        //設定基本字型
        URL resource = DiagnoseServiceImpl.class.getClassLoader().getResource("font/simsun.ttc");
        //字尾是.ttc的都是字型檔案
        //simsun.ttc:這個字型是宋體,ttc字尾的文bai件一般是一套字型,多數就是du包含了一種字型的正常zhi體,黑體,和斜體。
        BaseFont baseFont = BaseFont.createFont(Objects.requireNonNull(resource).toString() + ",0", BaseFont.IDENTITY_H,BaseFont.EMBEDDED);

        PdfPTable table = new PdfPTable(1);
        table.setWidthPercentage(20f);
        table.setHorizontalAlignment(Element.ALIGN_LEFT);
        InputStream logo = DiagnoseServiceImpl.class.getClassLoader().getResourceAsStream("logo.png");
        BufferedImage image = null;
        if (logo != null) {
            image = ImageIO.read(logo);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ImageIO.write(image, "png", out);
            byte[] data = out.toByteArray();
            PdfPCell cell = new PdfPCell(Image.getInstance(data), true);
            cell.setBorder(0);
            table.addCell(cell);
            document.add(table);
        }


        // 標題
        Font pdFont = new Font(baseFont, 20, Font.BOLD, BaseColor.BLACK);
        Paragraph p = new Paragraph("裝置診斷報告書", pdFont);
        p.setAlignment(Element.ALIGN_CENTER);
        document.add(p);

        // 問題描述
        pdFont = new Font(baseFont, 18, Font.BOLD, BaseColor.BLACK);
        p = new Paragraph("Observations of failure 問題描述:", pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph("裝置SN:" + vo.getSn(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph("裝置名稱:" + vo.getDeviceName(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph("問題描述:" + vo.getProblemDescription(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        // 原因分析
        pdFont = new Font(baseFont, 18, Font.BOLD, BaseColor.BLACK);
        p = new Paragraph("Estimation of reason原因分析:", pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph(vo.getEstimationReason(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        // 備件
        pdFont = new Font(baseFont, 18, Font.BOLD, BaseColor.BLACK);
        p = new Paragraph("Spare parts備件:", pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph(vo.getSpareParts(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        // 結果/反饋
        pdFont = new Font(baseFont, 18, Font.BOLD, BaseColor.BLACK);
        p = new Paragraph("Final result結果/反饋:", pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph(vo.getFinalResult(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        // 建議
        pdFont = new Font(baseFont, 18, Font.BOLD, BaseColor.BLACK);
        p = new Paragraph("Recommendation建議:", pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);

        pdFont = new Font(baseFont, 14, Font.NORMAL, BaseColor.BLACK);
        p = new Paragraph(vo.getRecommendation(), pdFont);
        p.setAlignment(Element.ALIGN_LEFT);
        document.add(p);


        document.add(p);
        document.newPage();
        document.close();

        // 2. 將文件輸出到本地
        String fileName = System.currentTimeMillis() + ".pdf";
        String filePath = uploadPath + fileName;
        writeToFile(filePath, stream);
        return fileName;
    }


    private static void writeToFile(String pdfPath, ByteArrayOutputStream stream) throws IOException {
        byte[] pdfByte = stream.toByteArray();
        stream.flush();
        stream.reset();
        stream.close();

        FileOutputStream outputStream = new FileOutputStream(pdfPath);
        outputStream.write(pdfByte);
        outputStream.flush();
        outputStream.close();
    }

    private String generateWordFile(DiagnoseDetailVo vo) {
        // 1. 根據模板生成word文件


        // logo
        //建立word檔案,document是“檔案”的英文。建立Pdf檔案的是,括號內就要設定PageSize.A4
        //而這裡如果括號內加入PageSize.A4,則報錯
        XWPFDocument document = new XWPFDocument();
        //新建一個段落p
        XWPFParagraph p = document.createParagraph();
        // 設定段落的對齊方式,ParagraphAlignment,中文意思是段落對其,是列舉類,在後續有記錄
        //此處為左側對齊
        p.setAlignment(ParagraphAlignment.LEFT);
        //建立段落文字
        XWPFRun run = p.createRun();
        
        //位元組輸入流InputStream
        /*
        class是指當前類的class物件.
        getClassLoader()是獲取當前的類載入器,什麼是類載入器?簡單點說,就是用來載入java類的,類載入器負責把class檔案載入進記憶體中,並建立一個java.lang.Class類的一個例項,也就是class物件,並且每個類的類載入器都不相同。
        getResourceAsStream(path)是用來獲取資源的,而類載入器預設是從classPath下獲取資源的,因為這下面有class檔案,所以這段程式碼總的意思是通過類載入器在classPath目錄下獲取資源.並且是以流的形式。
        */
        //logo
        try (InputStream logo = DiagnoseServiceImpl.class.getClassLoader().getResourceAsStream("logo.png")) {
        //Class.getClassLoader.getResourceAsStream(String path) :預設則是從ClassPath根下獲取,path不能以’/'開頭,因為是相對路徑,是相對的,如果前面加/則是絕對路徑,最終是由ClassLoader獲取資源。
        
        //說明classpath和resources是一個位置
            
            //原始碼:public XWPFPicture addPicture(InputStream pictureData, int pictureType, String filename, int width, int height),引數有 輸入流、圖片型別、檔名、寬度、高度
            run.addPicture(logo, org.apache.poi.xwpf.usermodel.Document.PICTURE_TYPE_PNG, "a.png", Units.toEMU(147), Units.toEMU(114));
        } catch (InvalidFormatException | IOException e) {
            e.printStackTrace();
        }

        // 標題
        // 新建一個段落
        p = document.createParagraph();
        //這個是居中對齊
        p.setAlignment(ParagraphAlignment.CENTER);
        
        generateRun(p, "裝置診斷報告書", true, 20);
        
        /*generateRun是自己生產的一個方法,把重複的幾個方法放在了一個方法裡,如下:
private XWPFRun generateRun(XWPFParagraph p, String text, boolean bold, int fontSize) {
        這個方法中,
        XWPFRun run = p.createRun();//為建立段落文字
        run.setText(text);//為輸入 word中的文字
        run.setBold(bold);//設定為粗體,bold是boolean屬性
        run.setFontSize(fontSize);//應該是設定字號,即設定字型大小,font即字型的意思
        run.setFontFamily("微軟雅黑");//設定字型樣式
        return run;
    }
        */

        // 問題描述
        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "Observations of failure 問題描述:", true, 18);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "裝置SN:" + vo.getSn(), false, 14);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "裝置名稱:" + vo.getDeviceName(), false, 14);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "問題描述:" + vo.getProblemDescription(), false, 14);

        // 原因分析
        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "Estimation of reason原因分析:", true, 18);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, vo.getEstimationReason(), false, 14);

        // 備件
        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "Spare parts備件:", true, 18);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, vo.getSpareParts(), false, 14);

        // 結果/反饋
        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "Final result結果/反饋:", true, 18);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, vo.getFinalResult(), false, 14);

        // 建議
        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, "Recommendation建議:", true, 18);

        p = document.createParagraph();
        p.setAlignment(ParagraphAlignment.LEFT);
        generateRun(p, vo.getRecommendation(), false, 14);

        // 2. 將word輸出到本地目錄
        
        String fileName = System.currentTimeMillis() + ".docx";
        //檔名 = 當前系統時間+docx字尾
        
        //檔案地址 =uploadPath +filename
        	/*uploadPath就是前面的:  
        	@Value("${upload.path}")
        	private String uploadPath;
        	應該是對應的配置檔案的  path: E:/upload/。
        	所以檔案地址 = E:/upload/+時間+docx字尾*/
        String filePath = uploadPath + fileName;//這個是電腦上地址
        
        //根據電腦上的檔案地址建立檔案
        File file = new File(filePath);
        try {
            //建立檔案
            document.write(new FileOutputStream(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
		//返回檔名
        return fileName;
    }
	
    //自己生成的一個方法,因為只是這個類用,所以許可權為private
    private XWPFRun generateRun(XWPFParagraph p, String text, boolean bold, int fontSize) {
        XWPFRun run = p.createRun();
        run.setText(text);
        run.setBold(bold);
        run.setFontSize(fontSize);
        run.setFontFamily("微軟雅黑");
        return run;
    }


}

在網上的建立PDF筆記

https://cloud.tencent.com/developer/article/1636609

建立Document物件,三種方式:

Document document =new Document(); // 預設頁面大小是A4
Document document =new Document(PageSize.A4); // 指定頁面大小為A4
Document document =new Document(PageSize.A4,50,50,30,20); // 指定頁面大小為A4,且自定義頁邊距(marginLeft、marginRight、marginTop、marginBottom)

4.application.yml:

在配置中,有(application:應用)

upload:
  path: E:/upload/

5.BladeConfiguration檔案

@Value("${upload.path}")
private String uploadPath;

附:

ParagraphAlignment:段落對齊列舉類

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.poi.xwpf.usermodel;

import java.util.HashMap;
import java.util.Map;

public enum ParagraphAlignment {
    LEFT(1),
    CENTER(2),
    RIGHT(3),
    BOTH(4),
    MEDIUM_KASHIDA(5),
    DISTRIBUTE(6),
    NUM_TAB(7),
    HIGH_KASHIDA(8),
    LOW_KASHIDA(9),
    THAI_DISTRIBUTE(10);

    private static Map<Integer, ParagraphAlignment> imap = new HashMap();
    private final int value;

    private ParagraphAlignment(int val) {
        this.value = val;
    }

    public static ParagraphAlignment valueOf(int type) {
        ParagraphAlignment err = (ParagraphAlignment)imap.get(type);
        if (err == null) {
            throw new IllegalArgumentException("Unknown paragraph alignment: " + type);
        } else {
            return err;
        }
    }

    public int getValue() {
        return this.value;
    }
                                                                                                                                                    
    static {
        ParagraphAlignment[] arr$ = values();
        int len$ = arr$.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            ParagraphAlignment p = arr$[i$];
            imap.put(p.getValue(), p);
        }

    }
}