VUE3+Springboot 实现导出word文档带图片

导出的图片在页面中已经初始化好了,需要拿到图片的base64数据编码传到后端

Base64 是一种将二进制数据编码为可打印 ASCII 字符的方式,常用于在文本协议(如 HTTP、JSON)中安全传输二进制数据(如图片、文件)。以下是关于图片 Base64 编码的详细解释:


1. Base64 的核心原理


2. Base64 编码图片的优缺点

优点缺点
✅ 可直接嵌入 HTML/CSS/JSON,无需额外文件请求❌ 体积比原始二进制大 33%(每 3 字节变 4 字符)
✅ 避免跨域问题(CORS)❌ 不适合大文件(如高清图片)
✅ 简化数据传输(如 API 接口直接传字符串)❌ 解码需要额外计算资源
  • 二进制转文本:计算机中的图片本质是二进制数据(0 和 1 的组合),但直接传输二进制可能因字符集、协议限制导致错误。Base64 将二进制按规则映射到 64 个可打印字符(A-Za-z0-9+/),确保数据安全传输。
  • . 图片 Base64 的常见形式

    Base64 编码的图片通常以 ​Data URL 形式出现,例如:

    data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...(省略后续字符)
  • 前缀data:image/png;base64, 表示数据类型和编码方式。
  • 数据部分iVBORw0KGgo... 是图片的 Base64 编码内容。
  • echarts.init 方法用于初始化图表,传入的参数 chartDom 是图表容器的 DOM 元素。
  • 返回值 myChart 是一个 ECharts 实例对象,后续可用于设置图表选项、更新数据等操作。
  • 调用myChart.getDataURL方法生成图表的数据URL。
  • 设置导出图片的类型为png,像素比为2,以提高清晰度。
  • 排除toolbox组件,确保导出图片不包含工具箱。
  • 设置背景颜色为#404040,保证导出图片的背景颜色一致。

在当前导出按钮,通过

导出的url就是图片的base64位编码

 将base64位传入数组中传给后端

 chartDataArray.push(chartDataURLC.value);
 chartDataArray.push(chart3DDataURL.value);
 const startTime= moment(timeLine.value[0]).format("YYYY-MM-DD");
 const endTime= moment(timeLine.value[1]).format("YYYY-MM-DD");
 const stationNum= searchData.value;
const deviceType= selectCode.value;
 const params = {
 startTime: startTime, // 替换为实际的日期
 endTime: endTime,   // 替换为实际的日期
 stationNum: stationNum, // 替换为实际的站点编号
 deviceType: deviceType,      // 替换为实际的设备类型
   chartDataURLS: JSON.stringify(chartDataArray)
   };
   const response = await axios({
 method: 'post',
 url: import.meta.env.VITE_APP_BASE_API + `/qc-api/getEquipmentWord`, // 替换为你的实际API地址
 data: params,
 responseType: 'blob',
});

 后端接收数据用Requestbody接收

 public ResponseEntity<byte[]> getReportWord(
            @RequestBody ExportEquip exportEquip
            ) throws Exception {

 解析其中的base64位编码,并统一校验,去掉前缀,然后存入到集合中

  // 解析JSON数组字符串为List<String>
        List<String> chartDataURLSList;
        try {
            chartDataURLSList = objectMapper.readValue(exportEquip.getChartDataURLS(), new TypeReference<List<String>>() {
            });
        } catch (IOException e) {
            throw new IllegalArgumentException("无法解析chartDataURLSJson为List<String>", e);
        }
        //创建一个集合存放文件路径
        List<String> imagePaths = new ArrayList<>();
        List<byte[]> imageList = new ArrayList<>();
        for (String chartDataURL : chartDataURLSList) {
            // 移除前缀
            chartDataURL = chartDataURL.replaceAll("data:image/png;base64,", "");

            // 校验是否为空
            if (chartDataURL == null || chartDataURL.isEmpty()) {
                imagePaths.add("空");
                continue;
            }

            byte[] imageBytes = Base64.getDecoder().decode(chartDataURL);

            // 创建一个BufferedImage对象
            try (InputStream is = new ByteArrayInputStream(imageBytes)) {
                BufferedImage image = ImageIO.read(is);

                // 指定保存图片的路径和文件名
                String imagePath = Const.STATE_XLSX_TEMP_PATH + "chart_" + imageList.size() + ".png";
                imagePaths.add(imagePath);

                // 将图片保存到本地
                ImageIO.write(image, "png", new File(imagePath));
            } catch (IOException e) {
                throw new RuntimeException("保存图片失败", e);
            }

            imageList.add(imageBytes);
        }

 在渲染模版时,放入对应的图片位置和宽高

  // 设备报告模板路径
        String path = ApiConst.EQUIPMENT_DOC_TEMP_PATH_OTHER;

        // 根据设备类型代码获取设备名称,并设置到设备数据对象中
        equipmentTableDto.setEquipmentName(collectionToDeviceNameMap.get(type));
        equipmentTableDto.setPictureC(new PictureRenderData(538, 256, imagePaths.get(2)));
        equipmentTableDto.setPicture(new PictureRenderData(269, 335, imagePaths.get(1)));

之后读取生成的word文档

 // 创建File对象以访问生成的Word文档
        File docFile = new File(Const.STATE_XLSX_TEMP_PATH + fileName);

        // 初始化文档内容数组
        byte[] body = null;

        // 打开文件输入流以读取文档内容
        InputStream is = new FileInputStream(docFile);

        // 将文档内容读入字节数组
        body = new byte[is.available()];
        is.read(body);

        // 创建HTTP响应头对象
        HttpHeaders headers = new HttpHeaders();

        // 设置正确的 Content-Type 和文件名
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        String encodedFileName = URLEncoder.encode("report.docx", "UTF-8").replace("+", "%20");
        headers.add("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
        // 设置HTTP响应状态码为200 OK
        HttpStatus statusCode = HttpStatus.OK;

        // 创建并返回包含文档内容、响应头和状态码的ResponseEntity对象
        ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
        return entity;

返回一个二进制流文件数据

前端解析并下载

// 从 Content-Disposition 获取文件名
 const contentDisposition = response.headers['content-disposition'];
 let fileName =startTime + "-" + endTime + "数据质控报告.docx"; // 默认值
alert(1);
console.log(response.data);
console.log(response.headers);
console.log(response.status);
 // 创建 Blob 并下载
 const blob = new Blob([response.data], { type: response.headers['content-type'] });
 const url = window.URL.createObjectURL(blob);
 const link = document.createElement('a');
 link.href = url;
 link.download = fileName;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);
window.URL.revokeObjectURL(url);

完整

const downloadReportWord = async () => {
  chart3DDataURL.value = myChart.getDataURL({
    type: 'png',
    pixelRatio: 2,
    excludeComponents: ['toolbox'],
    backgroundColor: '#404040' // 确保导出的图片背景颜色一致
  });

  chartDataURLC.value = barChart.value.getDataURL({
    type: 'png',
    pixelRatio: 2,
    excludeComponents: ['toolbox'],
    backgroundColor: '#404040' // 确保导出的图片背景颜色一致
  });
  chartDataArray.push(chartDataURLC.value);
 chartDataArray.push(chart3DDataURL.value);
 const startTime= moment(timeLine.value[0]).format("YYYY-MM-DD");
 const endTime= moment(timeLine.value[1]).format("YYYY-MM-DD");
 const stationNum= searchData.value;
const deviceType= selectCode.value;
 const params = {
 startTime: startTime, // 替换为实际的日期
 endTime: endTime,   // 替换为实际的日期
 stationNum: stationNum, // 替换为实际的站点编号
 deviceType: deviceType,      // 替换为实际的设备类型
   chartDataURLS: JSON.stringify(chartDataArray)
   };
   const response = await axios({
 method: 'post',
 url: import.meta.env.VITE_APP_BASE_API + `/qc-api/getEquipmentWord`, // 替换为你的实际API地址
 data: params,
 responseType: 'blob',
});

 // 从 Content-Disposition 获取文件名
 const contentDisposition = response.headers['content-disposition'];
 let fileName =startTime + "-" + endTime + "数据质控报告.docx"; // 默认值
alert(1);
console.log(response.data);
console.log(response.headers);
console.log(response.status);
 // 创建 Blob 并下载
 const blob = new Blob([response.data], { type: response.headers['content-type'] });
 const url = window.URL.createObjectURL(blob);
 const link = document.createElement('a');
 link.href = url;
 link.download = fileName;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);
window.URL.revokeObjectURL(url);
};
    public ResponseEntity<byte[]> getReportWord(
            @RequestBody ExportEquip exportEquip
            ) throws Exception {

        // 解析JSON数组字符串为List<String>
        List<String> chartDataURLSList;
        try {
            chartDataURLSList = objectMapper.readValue(exportEquip.getChartDataURLS(), new TypeReference<List<String>>() {
            });
        } catch (IOException e) {
            throw new IllegalArgumentException("无法解析chartDataURLSJson为List<String>", e);
        }
        //创建一个集合存放文件路径
        List<String> imagePaths = new ArrayList<>();
        List<byte[]> imageList = new ArrayList<>();
        for (String chartDataURL : chartDataURLSList) {
            // 移除前缀
            chartDataURL = chartDataURL.replaceAll("data:image/png;base64,", "");

            // 校验是否为空
            if (chartDataURL == null || chartDataURL.isEmpty()) {
                imagePaths.add("空");
                continue;
            }

            byte[] imageBytes = Base64.getDecoder().decode(chartDataURL);

            // 创建一个BufferedImage对象
            try (InputStream is = new ByteArrayInputStream(imageBytes)) {
                BufferedImage image = ImageIO.read(is);

                // 指定保存图片的路径和文件名
                String imagePath = Const.STATE_XLSX_TEMP_PATH + "chart_" + imageList.size() + ".png";
                imagePaths.add(imagePath);

                // 将图片保存到本地
                ImageIO.write(image, "png", new File(imagePath));
            } catch (IOException e) {
                throw new RuntimeException("保存图片失败", e);
            }

            imageList.add(imageBytes);
        }

        // 根据开始时间和结束时间生成文件名
        String fileName = exportEquip.getStartTime() + "-" + exportEquip.getEndTime() + "数据质控报告.docx";

        // 调用服务方法生成Word文档
        qcService.exportEquipmentWord(Const.STATE_XLSX_TEMP_PATH + fileName, exportEquip.getStartTime(), exportEquip.getEndTime(), exportEquip.getDeviceType(), exportEquip.getStationNum(), imagePaths);
        // 读取生成的文档
        byte[] fileContent;
        try (InputStream is = new FileInputStream(Const.STATE_XLSX_TEMP_PATH + fileName)) {
            fileContent = IOUtils.toByteArray(is);
        } catch (IOException e) {
            // 处理异常
            throw new RuntimeException("读取Word文件失败", e);
        }
        // 创建File对象以访问生成的Word文档
        File docFile = new File(Const.STATE_XLSX_TEMP_PATH + fileName);

        // 初始化文档内容数组
        byte[] body = null;

        // 打开文件输入流以读取文档内容
        InputStream is = new FileInputStream(docFile);

        // 将文档内容读入字节数组
        body = new byte[is.available()];
        is.read(body);

        // 创建HTTP响应头对象
        HttpHeaders headers = new HttpHeaders();

        // 设置正确的 Content-Type 和文件名
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        String encodedFileName = URLEncoder.encode("report.docx", "UTF-8").replace("+", "%20");
        headers.add("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
        // 设置HTTP响应状态码为200 OK
        HttpStatus statusCode = HttpStatus.OK;

        // 创建并返回包含文档内容、响应头和状态码的ResponseEntity对象
        ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
        return entity;
    }

实现Spring BootVue导出Word文档,可以使用poi和docx4j这两个工具。 首先是后端Spring Boot实现: 1. 添加poi和docx4j依赖到pom.xml文件中: ```xml <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>11.3.3</version> </dependency> ``` 2. 创建Word导出接口: ```java @RestController @RequestMapping("/export") public class ExportController { @GetMapping("/word") public void exportWord(HttpServletResponse response) throws Exception { // 创建一个空白的Word文档 WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage(); // 添加段落 wordMLPackage.getMainDocumentPart().addParagraphOfText("Hello, World!"); // 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); response.setHeader("Content-Disposition", "attachment; filename=test.docx"); // 输出Word文档 wordMLPackage.save(response.getOutputStream()); } } ``` 3. 启动Spring Boot应用,访问http://localhost:8080/export/word即可下载导出Word文档。 然后是前端Vue实现: 1. 安装axios和file-saver依赖: ```bash npm install axios file-saver --save ``` 2. 创建导出Word的方法: ```js exportWord() { axios({ method: &#39;get&#39;, url: &#39;/export/word&#39;, responseType: &#39;blob&#39; }).then(response => { const blob = new Blob([response.data]); const fileName = &#39;test.docx&#39;; saveAs(blob, fileName); }); } ``` 3. 在Vue组件中添加一个按钮,并绑定导出Word的方法: ```html <template> <div> <button @click="exportWord">导出Word</button> </div> </template> <script> import axios from &#39;axios&#39;; import { saveAs } from &#39;file-saver&#39;; export default { name: &#39;Export&#39;, methods: { exportWord() { axios({ method: &#39;get&#39;, url: &#39;/export/word&#39;, responseType: &#39;blob&#39; }).then(response => { const blob = new Blob([response.data]); const fileName = &#39;test.docx&#39;; saveAs(blob, fileName); }); } } }; </script> ``` 4. 运行Vue应用,点击按钮即可下载导出Word文档。 以上就是Spring BootVue导出Word文档实现步骤,希望能对你有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值