当前位置: 首页 > news >正文

03、SpringBoot + 微信支付 ---- 创建订单、保存二维码url、显示订单列表

目录

  • Native 下单
    • 1、创建课程订单保存到数据库
      • 1-1:需求:
      • 1-2:代码:
      • 1-3:测试结果:
    • 2、保存支付二维码的url
      • 2-1:需求:
      • 2-2:代码:
      • 2-3:测试:
      • 2-4:完整代码:
        • 后端:
          • WxPayController
          • WxPayService
          • WxPayServiceImpl
          • OrderInfoService
          • OrderInfoServiceImpl
    • 3、显示订单列表
      • 3-1:需求:
      • 3-2:代码:
        • 前端:
        • 后端:
      • 3-3:测试:
        • 查看swagger
        • 查看订单列表
      • 3-4:完整代码
        • 后端:
          • OrderInfoController
          • OrderInfoService
          • OrderInfoServiceImpl

Native 下单

1、创建课程订单保存到数据库

1-1:需求:

之前的下单,只是获取支付二维码,但是并没有将订单存到数据库

需求1:点击确认支付后,创建商品的订单存到数据库

需求2:每次确认支付之前,要判断这个人是否存在已下单未支付的订单,有的话就不用再创建订单了,把他的订单查询出来给他就行。

在这里插入图片描述

1-2:代码:

在这里插入图片描述

需求1:点击确认支付后,创建商品的订单存到数据库

在这里插入图片描述

需求2:每次确认支付之前,要判断这个人是否存在已下单未支付的订单,有的话就不用再创建订单了,把他的订单查询出来给他就行。

在这里插入图片描述

1-3:测试结果:

成功在数据库添加订单,并且多次点击确认下单,并不会重复添加订单到数据库

在这里插入图片描述

2、保存支付二维码的url

2-1:需求:

上面创建订单的时候,是没有存二维码的url到数据库的,这里需要在创建订单的时候,把url存进去。
Native调起支付
在这里插入图片描述

2-2:代码:

解释:

因为获取支付二维码url的代码在创建订单之后,所以第一次创建订单是没有支付二维码的url的。

所以在往下的代码中,添加了保存二维码的代码。
在这里插入图片描述

在这里插入图片描述

2-3:测试:

保存二维码成功,并且在重复访问的时候,因为存在二维码,所以不会再去调用微信的下单接口。

因为二维码有效期为2小时,所以后面还需要优化,如果二维码过期,需要再次更新数据库中的二维码。

在这里插入图片描述

2-4:完整代码:

包含创建订单和保存支付二维码的代码

后端:
WxPayController
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{@Resourceprivate WxPayService wxPayService;//调用统一下单API,生成支付二维码的链接和订单号//swagger注解@ApiOperation("调用统一下单API,生成支付二维码")@PostMapping("/native/{productId}")public R nativePay(@PathVariable Long productId) throws Exception{log.info("发起支付请求");//返回支付二维码的链接和订单号Map<String,Object> map = wxPayService.nativePay(productId);return R.ok().setData(map);}
}
WxPayService
public interface WxPayService
{//调用统一下单API,生成支付二维码的链接和订单号Map<String, Object> nativePay(Long productId) throws  Exception;
}
WxPayServiceImpl

//创建订单,调用 Native 支付接口
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService
{@Resourceprivate WxPayConfig wxPayConfig;/* 原本应该注 入WxPayConfig 这个类,然后调用 getWxPayClient() 方法获取 HttpClient请求对象* 但是因为 getWxPayClient() 方法加了@Bean注解,交给了spring容器管理,所以项目启动的时候就会执行这个方法,* 就会存在返回值为 CloseableHttpClient 类型的 HttpClient请求对象* 所以这里可以直接注入这个 CloseableHttpClient 对象*/@Resourceprivate CloseableHttpClient wxPayClient;@Resourceprivate OrderInfoService orderInfoService;/*** 创建订单,调用 Native 支付接口** @param productId 商品id* @return code_url 和 订单号* @throws Exception*///调用统一下单API,生成支付二维码的链接和订单号@Overridepublic Map<String, Object> nativePay(Long productId) throws Exception{//生成订单OrderInfo orderInfo = orderInfoService.createOrderInfoByProduct(productId);//获取二维码url-----如果是第一次生成订单,那么这个订单是没有二维码url的String codeUrl = orderInfo.getCodeUrl();//判断--如果订单存在,并且二维码的url也存在,那么就不需要再去调用微信的下单接口来获取支付二维码了if (orderInfo != null && !StringUtils.isEmpty(codeUrl)){//创建一个包含url和订单号的返回值Map<String, Object> map = new HashMap<>();map.put("codeUrl", codeUrl);map.put("orderNo", orderInfo.getOrderNo());return map;}/** 官方提供的 Native下单 接口* 支持商户:【普通商户】* 请求方式:【POST】/v3/pay/transactions/native* 请求域名:【主域名】https://api.mch.weixin.qq.com* "https://api.mch.weixin.qq.com/v3/pay/transactions/native" 改成* wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType())*/log.info("调用统一下单API.....");//调用统一下单API---拷贝官网的实例代码进行修改---统一下单的接口地址//封装统一下单API 的urlHttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));// 请求body参数---------调用接口需要的参数Gson gson = new Gson();//数据类型不固定,所以就不写泛型了Map paramsMap = new HashMap();//设置参数 --- 根据官网要求设置对应的参数paramsMap.put("appid", wxPayConfig.getAppid()); //公众号IDparamsMap.put("mchid", wxPayConfig.getMchId()); //直连商户号paramsMap.put("description", orderInfo.getTitle()); // 商品描述paramsMap.put("out_trade_no", orderInfo.getOrderNo()); //商户订单号paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType())); //通知地址//订单金额有两个参数--嵌套数据Map amountMap = new HashMap();amountMap.put("total", orderInfo.getTotalFee()); //总金额amountMap.put("currency", "CNY"); //货币类型paramsMap.put("amount", amountMap); // 订单金额//将参数转成字符串String jsonParams = gson.toJson(paramsMap);log.info("支付的请求参数:" + jsonParams);//把参数设置到请求体当中StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);//希望得到的响应类型httpPost.setHeader("Accept", "application/json");//完成签名并执行请求CloseableHttpResponse response = wxPayClient.execute(httpPost);//这些就是对调用下单方法的响应结果的处理了try{//字符串形式的响应体String bodyAsString = EntityUtils.toString(response.getEntity());//响应状态码int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200){ //处理成功System.out.println("成功, 返回结果  = " + bodyAsString);} else if (statusCode == 204){ //处理成功,无返回BodySystem.out.println("成功");} else{System.out.println("下单失败, 响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);throw new IOException("请求失败 request failed");}//响应结果---如果下单成功,获取响应结果,   gson.fromJson()用于将 JSON 字符串转换为 Java 对象Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);//二维码---从返回的结果中获取二维码的url, 从官网看出 code_url 是 二维码的keycodeUrl = resultMap.get("code_url");//保存二维码String orderNo = orderInfo.getOrderNo();orderInfoService.saveCodeUrl(orderNo,codeUrl);//创建一个包含url和订单号的返回值Map<String, Object> map = new HashMap<>();map.put("codeUrl", codeUrl);map.put("orderNo", orderInfo.getOrderNo());return map;} finally{response.close();}}
}
OrderInfoService

public interface OrderInfoService extends IService<OrderInfo> {/*** 根据商品id创建商品订单* @param productId 商品id* @return 订单对象*/OrderInfo createOrderInfoByProduct(Long productId);/*** 将支付二维码的url存到订单中* @param orderNo 订单编号* @param codeUrl 支付二维码的地址url*/void saveCodeUrl(String orderNo, String codeUrl);}
OrderInfoServiceImpl

@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService
{@Resourceprivate ProductMapper productMapper;@Resourceprivate OrderInfoMapper orderInfoMapper;//创建商品订单@Overridepublic OrderInfo createOrderInfoByProduct(Long productId){//用户点击确认支付,要先查找该用户是否存在已下单未支付的订单OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);if (orderInfo != null){//表示该用户已经下单了,还没有支付,那就直接把他的订单返回回去就行了,否则就创建新订单return orderInfo;}//根据商品的id获取到该商品对象数据Product product = productMapper.selectById(productId);orderInfo = new OrderInfo();orderInfo.setTitle(product.getTitle()); //订单标题orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //生成订单号orderInfo.setProductId(productId); //商品idorderInfo.setTotalFee(1); //单位是:分orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType()); //支付状态//把商品订单存到数据库orderInfoMapper.insert(orderInfo);return orderInfo;}/*** 根据商品id查询已下单未支付的订单,防止重复创建订单* @param productId 商品id* @return 订单对象*/private OrderInfo getNoPayOrderByProductId(Long productId){//QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();//相当于封装查询对象,这是查询条件//就是查询该表中,是否有 列名 product_id 对应的值等于这个 productId ,有就查询出来queryWrapper.eq("product_id", productId);queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());//把 queryWrapper 作为查询条件对象//selectOne 就是查询出一条,如果查询出多条,则会报错OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);return orderInfo;}/*** 将支付二维码的url存到订单中* @param orderNo 订单编号* @param codeUrl 支付二维码的地址url*/@Overridepublic void saveCodeUrl(String orderNo, String codeUrl){//QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();//组装查询条件queryWrapper.eq("order_no",orderNo);//要修改的字段,存到这个 orderInfo 对象里面OrderInfo orderInfo = new OrderInfo();orderInfo.setCodeUrl(codeUrl);//执行sqlorderInfoMapper.update(orderInfo,queryWrapper);}}

3、显示订单列表

3-1:需求:

在我的订单页面按时间倒序显示订单列表
目前是有订单,但是没展示出来。
在这里插入图片描述

3-2:代码:

前端:

调用后端接口的是api模块

<script> 脚本模块
<template> 模板,是用来定义组件的模板部分,用于描述组件的结构和布局

在这里插入图片描述

将后端返回的list商品订单列表赋值给 orders.vue 这个类后,就需要对这个数据进行渲染。
在这里插入图片描述

后端:

创建一个订单的controller
在这里插入图片描述

3-3:测试:

查看swagger

在这里插入图片描述

查看订单列表

成功显示
在这里插入图片描述

3-4:完整代码

后端:
OrderInfoController
@CrossOrigin //开放前端的跨域访问
@RestController
@RequestMapping(value = "/api/order-info")
@Api(tags = "商品订单管理")
public class OrderInfoController
{//依赖注入@Resourceprivate OrderInfoService orderInfoService;@ApiOperation("显示商品订单列表")@GetMapping("/list")public R getOrderInfoList(){List<OrderInfo> list =orderInfoService.getOrderInfoListByCreateTimeDesc();return R.ok().data("list",list);}
}
OrderInfoService
    /*** 获取商品订单列表,并按时间倒序显示* @return 商品订单列表,倒序显示*/List<OrderInfo> getOrderInfoListByCreateTimeDesc();
OrderInfoServiceImpl
    /*** 获取商品订单列表,并按时间倒序显示* @return 商品订单列表,倒序显示*/@Overridepublic List<OrderInfo> getOrderInfoListByCreateTimeDesc(){//QueryWrapper 是 MyBatis-Plus 提供的一个用于构建查询条件的工具类QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();//组装查询条件queryWrapper.orderByDesc("create_time");//查询List<OrderInfo> list = orderInfoMapper.selectList(queryWrapper);return list;}

相关文章:

03、SpringBoot + 微信支付 ---- 创建订单、保存二维码url、显示订单列表

目录 Native 下单1、创建课程订单保存到数据库1-1&#xff1a;需求&#xff1a;1-2&#xff1a;代码&#xff1a;1-3&#xff1a;测试结果&#xff1a; 2、保存支付二维码的url2-1&#xff1a;需求&#xff1a;2-2&#xff1a;代码&#xff1a;2-3&#xff1a;测试&#xff1a;…...

【echarts基础】在柱形图上设置文本

一、需求描述 在柱状图上设置文本标签&#xff0c;按需修改它的颜色、大小、边框、阴影等&#xff0c;如下。 二、代码展示 series:[{name:"螺蛳粉",type:"bar",data:data.data.chartData.chartData.num.螺蛳粉,label:{//图形上显示文本标签formatter:&q…...

小户型工业风,陌生上开花知书香。福州中宅装饰,福州装修

漫步陌上 只因陌上花开 花是自然的那种 朴素而恬淡&#xff0c;不落尘俗。—徐志摩 小户型工业风格 满足业主需求 筑造书香押韵家 从动线、色彩、选材、定制等各个环节 与业主一起畅谈家的构造 形成别“居”一格的温暖品质家 以书做墙 告别电视墙 这是一个实用性很强的…...

Gorm 中的迁移指南

探索使用 GORM 在 Go 中进行数据库迁移和模式更改的世界 在应用程序开发的不断变化的景观中&#xff0c;数据库模式更改是不可避免的。GORM&#xff0c;强大的 Go 对象关系映射库&#xff0c;通过迁移提供了一种无缝的解决方案来管理这些变化。本文将作为您全面的指南&#xf…...

基于.NET、Uni-App开发支持多平台的小程序商城系统 - CoreShop

前言 小程序商城系统是当前备受追捧的开发领域&#xff0c;它可以为用户提供一个更加便捷、流畅、直观的购物体验&#xff0c;无需下载和安装&#xff0c;随时随地轻松使用。今天给大家推荐一个基于.NET、Uni-App开发支持多平台的小程序商城系统&#xff08;该商城系统完整开源…...

[python] 在多线程中将`logging.info`输出到不同的文件中 (生产者消费者)

在多线程中将logging.info输出到不同的文件中&#xff0c;可以使用Python标准库中的Queue和Thread模块。具体实现步骤如下&#xff1a; 创建多个Queue队列用于不同线程的日志输出&#xff0c;每个队列对应一个日志文件。 import queue# 创建三个队列用于不同线程的日志输出 l…...

MySQL进阶_5.逻辑架构和SQL执行流程

文章目录 第一节、逻辑架构剖析1.1、服务器处理客户端请求1.2、Connectors1.3、第1层&#xff1a;连接层1.4、第2层&#xff1a;服务层1.5、 第3层&#xff1a;引擎层1.6、 存储层1.7、小结 第二节、SQL执行流程2.1、查询缓存2.2、解析器2.3、优化器2.4、执行器 第三节、数据库…...

【油猴脚本】学习笔记

目录 新建用户脚本模板源注释 测试代码获取图标 Tampermonkey v4.19.0 原教程&#xff1a;手写油猴脚本&#xff0c;几分钟学会新技能——王子周棋洛   Tampermonkey首页   面向 Web 开发者的文档   Greasy Fork 新建用户脚本 打开【管理面板】 点击【】&#xff0c;即…...

宝塔面板使用Supervisor进程守护插件,配置守护Mysql的操作教程。

本篇文章主要讲解&#xff0c;在宝塔面板中使用Supervisor进程守护插件&#xff0c;配置守护Mysql的操作教程。 作者&#xff1a;任聪聪 日期&#xff1a;2023年11月5日 一、安装守护进程插件 安装插件一、进程守护插件 安装说明&#xff1a;在软件商店中搜索“进程守护”&am…...

Electron[2] Electron使用准备

1 背景 介绍一个技术栈的入门基础&#xff0c;往往要以该技术栈的入门案例作为开始比较合适&#xff0c;更能诱惑到刚需的粉丝&#xff0c;深度的学习。Electron的入门也不例外。在入门案例的讲解过程中&#xff0c;我们会学习到Electron引入需要的准备工作有哪些。 2 入门案例…...

npm create vue@latest 原理

文章目录 使用实际调用流程 使用 npm create vitelatest当执行上述命令时&#xff0c;会通过一个可交互的命令行终端下载模版&#xff0c;实际最终是调用 create-vue 库实现的 实际调用流程 npm create、innit 实际是 npm init 别名 ,npm init 后面加包名时&#xff0c;实际…...

【Unity基础】7.动画状态参数

【Unity基础】7.动画状态参数 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;创建动画状态 (1) 创建动画状态 不好意思各位~最近工作比较忙&#xff0c;稍微耽误了这两周的博客。话…...

C语言映射表在串口数据解析中的应用

一、映射表在串口数据解析中的应用 1、数据结构 typedef struct {char CMD[CMDLen];unsigned char (*cmd_operate)(char *data); }Usart_Tab; 2、指令、函数映射表 static const Usart_Tab InstructionList[CMDMax] {{"PWON",PowOn},{"PWOFF",PowOff}…...

叁[3],感兴趣区域ROI

1&#xff0c;简介 ROI&#xff0c;感兴趣区域&#xff08;region of interest)&#xff0c;截取图像 2&#xff0c;获取方法 方法1&#xff1a;使用Rect cv::Mat srccv::imread("*.bmp");//读取原图 cv::Mat matROI src(cv::Rect(100,200,50,100));//截取原图&am…...

文件数据交换格式说明

对于文件的说明 二进制文件和文本文件的对比 对比项二进制文件文本文件定义二进制文件直接由二进制数字0和1组成&#xff0c;不存在统一的字符编码。文本文件是基于字符编码的文件&#xff0c;一般采用定长编码方式&#xff0c;如ASCII编码、UNICODE编码。优势1. 存储利用率高…...

2023NOIP A层联测24 总结

T1 给出树的一度点和三度点的数量&#xff0c;构造树的形态&#xff0c;节点数不超过 2000 2000 2000。我考虑先构造出三度点&#xff0c;发现这一度点至少是三度点2&#xff0c;打完后测样例不对&#xff0c;发现加一度点时要特判是否为三度点&#xff0c;花 5min 打完&#…...

vue3 项目如何配置测试环境打包

vue3 项目如何配置测试环境打包 根目录下创建.env.staging # 测试环境 NODE_ENV staging VUE_APP_MODE staging VUE_APP_TITLE 系统名称# 测试环境API接口地址 VUE_APP_API_URL 接口地址package.json文件中 scripts配置中添加以下代码 "scripts": {"serve&q…...

【CSS】样式的计算过程

标签的 CSS 样式 现在有这么一段 HTML 代码&#xff1a; <div class"test"><h1>Hello World</h1> </div>目前我们没有给 h1 设置任何样式&#xff0c;可以看到 h1 自带了一些样式&#xff0c;eg&#xff1a;font-size、font-weight、margi…...

【ArcGIS微课1000例】0076:KMZ转换KML的方法

文章目录 ArcGIS转kmzkmz转kmlArcGIS转kmz ArcGIS可以很方便的将dwg,shp、等矢量数据转为kmz。 拓展阅读: 【ArcGIS微课1000例】0075:将AutoCAD(Dwg、Dxf)文件转换为shp、KML(kml、kmz)文件...

Python基础入门例程46-NP46 菜品的价格(条件语句)

最近的博文&#xff1a; Python基础入门例程45-NP45 禁止重复注册&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程44-NP44 判断列表是否为空&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程43-NP43 判断布尔值&#xff08;条件语句&#xff0…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...

leetcode73-矩阵置零

leetcode 73 思路 记录 0 元素的位置&#xff1a;遍历整个矩阵&#xff0c;找出所有值为 0 的元素&#xff0c;并将它们的坐标记录在数组zeroPosition中置零操作&#xff1a;遍历记录的所有 0 元素位置&#xff0c;将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...

echarts使用graphic强行给图增加一个边框(边框根据自己的图形大小设置)- 适用于无法使用dom的样式

pdf-lib https://blog.csdn.net/Shi_haoliu/article/details/148157624?spm1001.2014.3001.5501 为了完成在pdf中导出echarts图&#xff0c;如果边框加在dom上面&#xff0c;pdf-lib导出svg的时候并不会导出边框&#xff0c;所以只能在echarts图上面加边框 grid的边框是在图里…...