Ruoyi-vue项目讲解

news2025/4/17 21:28:36

@[TOC]若依前后端调用接口解读
若依github官方下载地址
若依gitee官方下载地址

1.验证码时候的前端调用接口

调用前端登录界面的时候,调用的是login.vue这个文件中的created函数
调用created函数
这里我们查看getCode函数方法
getCode函数方法
可以看到,这里先调用了一个getCodeImg函数,然后接收到后端返回的值之后,再进行相应的处理,显示图片以及保存redis中的key,进入getCodeImg函数进行查看
getCodeImg函数
到api/login.js的文件下面查看getCodeImg函数
getCodeImg函数
可以看出,这里再次调用了一个request函数,查看request内容

import request from '@/utils/request'

查看request.js的文件
request.js中调用内容
这里的process.env.VUE_APP_BASE_API我们通过.env.development查看定义
在这里插入图片描述
可以看出,这里每次前端发出api的时候都会添加固定的前缀/dev-api前缀。

反向代理

除此之外,这里的url还调用了一次反向代理,在vue.config.js之中配置
反向代理配置
可以看出来这里跨域发送到后端的时候将process.env.VUE_APP_BASE_API转换为空字符串,并且将发送端口转为8080端口,通过浏览器我们也可以观察到前端发送的url请求内容前端的url调用调用的接口内容

2.验证码后端调用接口

发送完成之后,跨域转化完成之后就是localhost:8080/captchaImage接口,找到后端对应的调用部分,首先从数据库中查询是否开启验证码
查询是否开启验证码
接下来保存验证码在redis中保存的信息并且生成验证码,生成的字符串类似于4+1=?@5这种形式
生成验证码的部分
这里将4+1=?@5这块字符串拆分开来,将4+1生成图片,5保存在redis中为答案
最终redis的保存结果
将后端的结果返回到前端,前端保存图片内容以及redis的值

前端保存图片以及redis的值
这里我们可以看到this.codeUrl对应图片显示的部分

vue前端显示图片
对应的验证码部分图片如图所示
验证码部分图片
前端接收到后端返回的图片之后,成功显示图片

3.前端发起登录请求的调用

点击登录之后,从浏览器中查看发起的请求
浏览器中查看发起的请求
从上面可以看出,login、getInfo和getRouters三个请求在点击登录按钮之后同时被发送出去,我们首先看登录按钮调用的方法
登录按钮调用的方法
从这里可以看出,登录按钮调用handleLogin方法,查看方法的实现
登录方法的调用
这里首先查看是否需要记住密码,需要的时候保存在cookie之中,接下来会发生跳转

this.$store.dispatch("Login", this.loginForm).then(() => {...})

这里跳转到src/store/modules/user.js中的Login函数之中
跳转方法
这里又调用了login函数,查看login函数的部分
login函数
发出一个post请求,将username、password、code、uuid发送出去

4.后端接收请求内容

后端接收login请求
后端接收请求为四个变量,这里后端将四个变量打包成一个结构体,然后进入到loginService.login函数中进行查看
验证码判定部分
首先进行验证码的验证,进入validateCaptcha函数中
validateCaptcha方法
这里通过从redis中取出验证码数据来与自己输入的数据进行判断,如果验证码过期或者不相同的情况下抛出各种异常,而如果验证码通过的时候继续通过用户名和密码进行验证
调用用户名和密码进行验证
这里的authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));
就调用了UserDetailsService的实现类UserDetailsServiceImpl中的loadUserByUsername这个函数从数据库中取出来用户名和密码并且封装到LoginUser类之中
loadUserByUsername函数调用
最终如果验证成功的情况下,从authentication中取出LoginUser类,然后记录用户的登录信息并且生成token内容
在这里插入图片描述

5.前端发送/getInfo请求的调用

这两个调用确实比较难找,在permission.js文件之中调用beforeEach函数的时候出现
调用beforeEach函数
这里的beforeEach函数在跳转页面的时候进行调用
我们首先查看GetInfo函数的调用,GetInfo在store/modules/user.js之中

GetInfo函数调用
可以看出这里调用了getInfo方法,查找此方法
getInfo方法调用
这里的getInfo方法调用通过get发送了一个url的请求,该请求获取当前用户的信息并使用全局存储一下,接下来我们查看后端对于/getInfo请求的响应
后端getInfo接口的调用
这里查询用户以及权限信息返回给前端部分,然后我们继续查看前端接收到返回值之后的调用
GetInfo之后调用
可以看到GetInfo拿到角色权限等相关信息之后,才能够查找到可访问的路由表

6.前端发送/getRouter请求的调用

查看GenerateRoutes的调用
GenerateRoutes
这里查看调用的GenerateRoutes发出的请求,在src/store/modules/permission.js之中(实在找不到就采用全局搜索的方式)。
GenerateRoutes函数的调用
查看getRouters方法的调用

import { getRouters } from '@/api/menu'

调用getRouters请求
可以看出这里通过get发送/getRouters的url请求,回到后端查看getRouters方法的调用

7.后端接收getRouters请求

getRouters路由
这里采用的是一个不断延伸的树,进入到buildMenus函数之中,首先查看selectMenuTreeByUserId函数
selectMenuTreeByUserId函数图片
查看getChildPerms函数的调用,这一步是建树的关键
根据数据表的结构进行建立树
可以看出根节点为0,因此传入的parentId为0,进入到getChildPerms函数之中
getChildPerms函数的调用
这里可以看出,如果遍历节点的根节点为当前节点的时候,就继续深入遍历,然后将遍历的节点放入当前节点的list之中,这样就建立了一个不断深入的递归树。
getChildPerms只是进行了第一层,将指定的父节点与孩子节点建立联系,接下来继续深入需要递归调用,进入recursionFn函数的调用环节

private void recursionFn(List<SysMenu> list, SysMenu t)
{
    // 当前节点的子节点放入list之中
    List<SysMenu> childList = getChildList(list, t);
    //找一级菜单的子菜单
    t.setChildren(childList);
    for (SysMenu tChild : childList)
    //继续遍历孩子节点,二级菜单里面找三级菜单
    {
        if (hasChild(list, tChild))
        {
            recursionFn(list, tChild);
        }
    }
}

/**
 * 得到子节点列表
 *
 *
 * 从23条数据中把系统的子菜单找到
 */
private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
{
    List<SysMenu> tlist = new ArrayList<SysMenu>();
    Iterator<SysMenu> it = list.iterator();
    while (it.hasNext())
    {
        SysMenu n = (SysMenu) it.next();
        if (n.getParentId().longValue() == t.getMenuId().longValue())
        //找到当前节点的子节点并放入tlist中,n.getParentId()为当前的孩子节点,
        //t.getMenuId()为遍历的23个节点Id
        {
            tlist.add(n);
        }
    }
    return tlist;
}

接下来,再使用getRouters方法将buildMenus方法打包

@Override
public List<RouterVo> buildMenus(List<SysMenu> menus)
    ......

将List< SysMenu >转换为List< RouterVo>的类型,然后返回给前端,前端接收到之后进行处理
getRouters方法调用

8.登录之后的跳转界面发送getList请求方法

接下来我们需要看登录成功之后页面如何跳转的,回到login.vue之中
Login.vue登录之后跳转
可以看出登录成功之后往根路径去跳转

this.$router.push({ path: this.redirect || "/" }).catch(()=>{});

此时我们需要知道根路径"/"指的是哪个界面,打开src/router/index.js之中去查看
根路径对应的索引
这里我们找到了跟路径对应的界面,进入到’'之后我们会自动地往index路径去跳转,打开@/views/index查看页面,发现这里就是首页的各种内容。
并且这个首页还调用了Layout组件的布局
这里我们查看主页侧边栏的调用
在这里插入图片描述
从这里可以看到调用侧边栏sidebar-container,具体的Sidebar调用查看import内容

import {AppMain, Navbar, Settings, Sidebar, TagsView } from './components'

具体的内容在src/layout/components/Sidebar之中有index.vue,这里sidebar-item之中有v-for循环

 <sidebar-item
     v-for="(route, index) in sidebarRouters"
     :key="route.path  + index"
     :item="route"
     :base-path="route.path"
 />

这里遍历的是我们的router对象,可以把后台的数据迭代出来。对应路径在src/views/system/,这里跳转的方向在数据库中有所记录
数据库中记录跳转方向
比如第一个用户管理对应的路径在views之中的system/user/index.vue
点击侧边栏进入用户管理,发现页面分为两块
页面分界
一块是左边的树状侧边栏,另外一块是中间的查询结果显示,中间的数据属于list,而侧边栏的数据属于tree,现在需要读取这两块的数据。在views/system/user/index中去找created方法,发现正好出现了这两部分的内容
getList和getTreeselect方法调用
进入到getList()函数中查询用户列表

getList(){
  this.loading = true;
  //让前端页面有一个加速转圈圈的效果
  listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
      //查看listUser代码的入口
      //取到这两个变量之后view会自动加载相应的效果???
      this.userList = response.rows;
      this.total = response.total;
      //获取到了list和total数据
      this.loading = false;
   }
}

查看listUser函数的调用

// 查询用户列表
// 这里找到了listUser调用的请求方法
export function listUser(query) {
  return request({
    url: '/system/user/list',
    method: 'get',
    params: query
  })
}

9.后端处理/system/user/list方法

这里由于/system/user/list后端采用的是分层url的处理方式,因此我们先搜索list
/system/user/list接口的调用
这里先查看@ss.hasPermi(‘system:dept:list’)")的权限调用方法
权限调用方法
这里判断用户是否具有某种权限
判断是否具有某种权限
这里取出LoginUser之后采用的是切分的方式判断是否具有权限,查看hasPermissions函数的调用

private boolean hasPermissions(Set<String> permissions, String permission)
{
    return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}

这里permissions = * : * : * ,而permission = “system:user:list”,判断权限的时候如果为全权限 或者permissions字符串中包含permission,则可以获取到权限。
查看startPage函数调用,

protected void startPage()
{
    PageDomain pageDomain = TableSupport.buildPageRequest();
    //pageDomain一个新对象,专门用来获取分页信息的
    Integer pageNum = pageDomain.getPageNum();
    Integer pageSize = pageDomain.getPageSize();
    if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
    {
        String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
        Boolean reasonable = pageDomain.getReasonable();
        PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
        //PageHelper可以单独设置页数,把分页参数设置到PageHelper提供的对象中即可
        //reasonable对参数进行逻辑处理,保证参数的正确性,比如pageNum=0或-1,将自动将pageNum设置为1
    }
}

这里定义了PageDomain等参数都是为了从前端发回的请求取出pageNum和pageSize这两个参数的,orderBy用来定义排序的顺序,reasonable对参数进行逻辑处理,保证参数的正确性,最后调用PageHelper中startPage方法封装好参数,调用PageHelper中的startPage会保证下一条sql语句进行分页处理,并且将结果封装成Page类型对象。
然后调用查询语句查询,并且将结果返回给前端。
结果返回
前端取回到数据之后,就会自动加载界面
前端自动加载界面

10.登录之后点击用户管理发出的第二次请求

用户管理显示
登录之后点击用户管理之后,在src/views/system/user/index.vue之中查看created函数的调用,在getList之后会发出第二次请求getTreeselect获取侧边栏的显示图
获取侧边栏的显示图
侧边栏的显示图
查看treeselect函数的调用
treeselect函数的调用
找到treeselect函数发出的请求,这里为部门下拉树结构

export function treeselect() {
  return request({
    url: '/system/dept/treeselect',
    method: 'get'
  })
}

找到获取部门下拉树的后端调用
获取部门下拉树列表
这里selectDeptList选择出部门信息的表格,deptService.buildDeptTreeSelect(depts)将depts建成一层一层的树,查看其中的代码

@Override
public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts)
{
    List<SysDept> deptTrees = buildDeptTree(depts);
    //将10条记录组装成一个树状图
    return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
    //将deptTrees全部封装成TreeSelect,然后封装成集合,从SysDept封装成TreeSelect
    //需要转换的原因:SysDept中的字段过多,而TreeSelect中的字段较少,前端不需要SysDept
    //一样这么多的字段,本质上就是对字段进行复制
    //这里通过构造函数进行映射
    /***
     * public TreeSelect(SysDept dept)
     * {
     *    this.id = dept.getDeptId();
     *    this.label = dept.getDeptName();
     *    this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
     * }
     */
 }

这里buildDeptTree针对depts建立多层树,而由于SysDept类中包含了过多的内容,因此需要将SysDept转换成TreeSelect类,初始化类为

public class SysDept extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 部门ID */
    private Long deptId;

    /** 父部门ID */
    private Long parentId;

    /** 祖级列表 */
    private String ancestors;

    /** 部门名称 */
    private String deptName;

    /** 显示顺序 */
    private String orderNum;

    /** 负责人 */
    private String leader;

    /** 联系电话 */
    private String phone;

    /** 邮箱 */
    private String email;

    /** 部门状态:0正常,1停用 */
    private String status;

    /** 删除标志(0代表存在 2代表删除) */
    private String delFlag;

    /** 父部门名称 */
    private String parentName;
    ... 
 }

接着查看TreeSelect类调用

public class TreeSelect implements Serializable
{
    private static final long serialVersionUID = 1L;

    /** 节点ID */
    private Long id;

    /** 节点名称 */
    private String label;
    ......
}

也就是说前端不需要这么多的信息,所以选择TreeSelect这么多的内容即可
这里SysDept类的初始化方法为

public TreeSelect(SysDept dept)
{
    this.id = dept.getDeptId();
    this.label = dept.getDeptName();
    this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}

点击研发部门之后的内容

点击研发部门之后的内容
点击研发部门之后基于研发部门进行进一步的查询

在src/views/system/user/index.vue之中查看el-tree标签内容
el-tree标签内容
查看handleNodeClick的方法
节点单击调用方法
这里查看getList方法调用

getList() {
  this.loading = true;
  //让它有一个加载的效果,转圈圈
  listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
      //查看listUser代码的入口
      //取到这两个变量之后view会自动加载相应的效果???
      this.userList = response.rows;
      this.total = response.total;
      //获取到了list和total数据
      this.loading = false;
    }
  );
},

this.loading = true让它加载有一个转圈圈的功能,而listUser调用相应的请求

export function listUser(query) {
  return request({
    url: '/system/user/list',
    method: 'get',
    params: query
  })
}

这里调用/system/user/list与之前的区别在于这里的user加入了其他的参数,因此查找的时候是条件查找。

文件的导入和导出

文件的导入和导出界面
这里有文件的导入和导出两个按钮,导入是将文件的数据写入到数据库中,而导出则是将数据库的数据写出到文件中

前端点击新增方法

点击新增方法

提交按钮的方法

提交按钮的方法

点这个圈圈能把所有的断点都去掉。

异步任务管理器

异步任务管理器中的调用在ruoyi-framework中的src/main/java/com.ruoyi.framework.web.service.SysLoginService.java中的

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));

1.调用异步管理器

AsyncManager.me()

获取一个AsyncManager对象,AsyncManager是一个单例模式

2.执行execute方法,执行任务,传入的是一个Task对象,实现了Runnable接口,表示它是一个任务,由线程Thread去执行。

封装了登录信息,执行了添加操作,这里不会执行,而是交给线程对象来执行。
这里首先调用了异步线程池

/**
* 异步操作任务调度线程池
*/
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
......
/**
 * 执行任务
 * 
 * @param task 任务
 */
public void execute(TimerTask task)
{
    executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/***
* 这里是创建线程池的操作,corePoolSize为核心池的大小
* 异步任务管理器内部定义了一个线程池,然后根据业务创建
* 添加日志的任务,交给线程池来处理,这样做到来日志和业务
* 的抽象、解耦合,日志全部统一处理。
* 同步:登录成功之后必须记录日志,如果没有记录下来登录成功
* 这部分就需要等
***/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020602.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGLM Pytorch从0编写Transformer算法

预备工作 # !pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl numpy matplotlib spacy torchtext seaborn import numpy as np import torch import torch.nn as nn import torch.nn.functional as F import math, copy, tim…

八股整理(计网,os)

1.进程和线程的区别 1.1什么是进程和线程 1.进程是操作系统进行资源分配和调度的一个基本单位&#xff0c;资源包括cpu&#xff0c;内存&#xff0c;磁盘等等IO设备等等。每一个进程启动都会最先产生一个线程&#xff0c;即主线程&#xff0c;然后主线程会在创建其他的子线程…

深入理解WPF中MVVM的设计思想

近些年来&#xff0c;随着WPF在生产&#xff0c;制造&#xff0c;工业控制等领域应用越来越广发&#xff0c;很多企业对WPF开发的需求也逐渐增多&#xff0c;使得很多人看到潜在机会&#xff0c;不断从Web&#xff0c;WinForm开发转向了WPF开发&#xff0c;但是WPF开发也有很多…

【Redis缓存:常见问题及解决方案】

目录 ①缓存雪崩 常见的解决方案 加锁排队 随机化过期时间 设置⼆级缓存 ②缓存穿透 常见的解决方案 布隆过滤器 缓存空结果 接口层增加校验 ③缓存击穿 常见的解决方案 加锁排队 设置热点数据永远不过期 分布式缓存系统 ④缓存预热 缓存预热的实现思路 ①缓…

C语言开发手册,辅助工具

方便查函数,头文件,日常语法,c99与c11的差异,等 https://www.php.cn/manual/view/34866.html

微信小程序通过普通二维码扫码进入指定页面带参数

微信小程序通过普通二维码扫码进入指定页面带参数 首先进入公众号开发者后台 https://mp.weixin.qq.com/ 进入开发管理->开发设置 扫普通链接二维码打开小程序 二维码规则填写服务器域名、填写前缀占用规则不占用就是其他开发者都可以使用这个后缀&#xff0c;占用则只能…

MYSQL性能优化——基于成本的优化

MYSQL性能优化 详见 GitBook MYSQL性能优化 什么是成本 我们之前老说MySQL执行一个查询可以有不同的执行方案&#xff0c;它会选择其中成本最低&#xff0c;或者说代价最低的那种方案去真正的执行查询。不过我们之前对成本的描述是非常模糊的&#xff0c;其实在MySQL中一条查…

【计算思维题】少儿编程 蓝桥杯青少组计算思维 数学逻辑思维真题详细解析第10套

少儿编程 蓝桥杯青少组计算思维题真题及解析第10套 1、明明买了一个扫地机器人,可以通过以下指令控制机器人运动: F:向前走 10 个单位长度 L:原地左转 90 度 R:原地右转 90 度 机器人初始方向向右,需要按顺序执行以下那条指令,才能打扫完下图中的道路 A、F-L-F-R-F-F-R-F-…

北邮22级信通院数电:Verilog-FPGA(3)实验“跑通第一个例程”modelsim仿真及遇到的问题汇总(持续更新中)

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 注意&#xff1a;本篇文章所有绝对路径的展示都来自…

apache poi 实现Excel 下拉联动

原文链接&#xff1a;Java poi实现Excel 下拉联动 Java 实现Excel 下拉联动&#xff0c;本示例中实现了省市区乡镇村联动。适用于03版本Excel。 依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --><dependency><groupId>org.apache.poi…

vue 01 创建一个简单vue页面

去vue官网下载vue.js 引用vue.js vue语法 一个vue实例&#xff0c;绑定一个容器&#xff0c;一对一关系 <!DOCTYPE html> <html><head><meta charset"UTF-8"/><title>初始Vue</title><script type"text/javascript&qu…

通过Sealos 180秒部署一套K8S集群

通过Sealos 180秒部署一套K8S集群 一、主机准备 1.1 主机操作系统说明 序号操作系统及版本备注1CentOS7u9 1.2 主机硬件配置说明 k8s集群CPU及内存最低分别为2颗CPU、2G内存&#xff0c;硬盘建议为100G 需求CPU内存硬盘角色主机名值8C8G1024GBmasterk8s-master01值8C8G1024…

面试时总被问,你认为项目管理最难的什么?

导言&#xff1a; 项目管理是一项复杂而又富有挑战性的工作&#xff0c;涉及到资源管理、风险管理、沟通和协调、时间管理以及变更管理等众多方面。然而&#xff0c;在这些方面中&#xff0c;很多人认为其中最具挑战的是时间管理。本文将从不同角度探讨时间管理在项目管理中的重…

default 和 delete 与默认构造函数 的使用

前言 使用default和delete关键字来干预编译器自动生成的函数。让我详细解释一下这些知识点&#xff1a; 正文 编译器生成的默认构造函数&#xff1a; 如果类A没有定义任何构造函数&#xff0c;那么编译器会自动生成一个无参的默认构造函数 A()。这个默认构造函数实际上是一个…

计算机视觉与深度学习-卷积神经网络-卷积图像去噪边缘提取-卷积与边缘提取-[北邮鲁鹏]

目录标题 参考学习链接什么是图像边缘&#xff1f;为什么研究边缘&#xff1f;有哪些边缘种类&#xff1f;表面法向不连续深度不连续表面颜色不连续光照不连续 如何检测边缘&#xff1f;图像求导图像梯度图像梯度的方向图像梯度的模 噪声的影响噪声影响带来的问题解决办法&…

系统架构设计师-数据库系统(2)

目录 一、规范化理论 1、规范化理论的基本概念 2、Armstrong公理 3、候选键 4、范式 5、模式分解 一、规范化理论 1、规范化理论的基本概念 非规范化的关系模式&#xff0c;可能存在的问题包括&#xff1a;数据冗余、更新异常、插入异常、删除异常。 下表可拆分为 学号、姓名…

链表应用(C++,递增合并、递增求交集、逆序、删除区间)

#include<iostream> using namespace std; typedef struct list {int data;list* next; }list,*linklist; void Newlist(linklist& l) {l new list;l->next NULL; } void Createlist(linklist& l) {cout << "输入链表长度&#xff1a;" <…

OpenCV之摩尔纹

摩尔纹 用数码相机拍摄景物中&#xff0c;如果有密纹的纹理&#xff0c;常常会出现莫名其妙的水波样条纹。这就是摩尔纹。简单的说&#xff0c;摩尔纹是差拍原理的一种表现。从数学上讲&#xff0c;两个频率接近的等幅正弦波叠加&#xff0c;合成信号的幅度将按照两个频率之差变…

捕获多种异常练习

捕获多种异常 下面存在三种异常&#xff1a;ValueError、KeyboardInterrupt 和其他异常&#xff0c;分别捕获并输出相应的提示语句 当用户输入123以外的字符时出现ValueError异常&#xff0c;这时提示用户输入数字1-3&#xff1b;当用户在输入数字后CtrlC时出现KeyboardInterru…

利用Pycharm将python程序打包为exe文件(亲测可用)

最近做了一个关于py的小项目&#xff0c;对利用Pycharm将python文件打包为exe文件不是很熟悉&#xff0c;故学习记录之。 目录 一、下载pyinstaller库 二、打开Pycharm进行打包&#xff08;不更改图标&#xff09; 三、打开Pycharm进行打包&#xff08;更改图标&#xff09…