2026/4/18 10:49:42
网站建设
项目流程
青海省建设厅查询网站,晋江哪里可以学建设网站,windows7 wordpress,哈尔滨网页制作人才招聘物流管理系统
1 系统需求分析
1.1 系统描述
物流的概念最早是在美国形成的#xff0c;起源于 20 世纪 30 年代#xff0c;原意为“实物分配”或“货物配送”。1963 年被引入日本#xff0c;日文意思是“物的流通”。20 世纪 70 年代后#xff0c;日本的“物流”一词逐渐…物流管理系统1 系统需求分析1.1 系统描述物流的概念最早是在美国形成的起源于 20 世纪 30 年代原意为“实物分配”或“货物配送”。1963 年被引入日本日文意思是“物的流通”。20 世纪 70 年代后日本的“物流”一词逐渐取代了“物的流通”。中国的“物流”一词是从日文资料引进来的外来词源于日文资料中对Logistics一词的翻译“物流”。中国的物流术语标准将物流定义为物流是物品从供应地向接收地的实体流动过程中根据实际需要将运输、储存、装卸搬运、包装、流通加工、配送、信息处理等功能有机结合起来实现用户要求的过程。随着人们对物流的需求越来越大人工乃至半自动的管理已经远远不能满足人们对物流管理中包裹跟踪、包裹派送以及员工和仓库等基础信息的管理的需求一个功能强大的物流管理系统已经成为一个必备项。物流管理系统应该包括公共查询部分以及后台查询部分1.1.1 公共查询部分任何用户包括员工和顾客都可以在非登录状态下查询自己的包裹信息。具体操作流程应该是用户输入自己的手机号点击查询按钮系统调用验证码发送接口发送短信验证码用户在系统中输入验证码即可查询到所有与自己相关的包裹信息包括包裹的基本信息以及物流情况跟踪。为了简化操作本系统省略了调用短信验证码的部分用户输入手机号、点击查询按钮直接显示查询到的结果。1.1.2 后台查询部分后台查询部分包括员工信息的录入和更新、仓库信息的录入和更新、包裹信息的录入和更新。这部分内容仅供后台用户登录后使用未登录状态会隐藏这部分功能。登录后的用户根据所属权限不同只能使用各自的部分例如人力资源相关的员工只能使用员工相关的功能而不能管理包裹信息。1.2 数据存储需求jxtxzzw 物流管理系统需要存储如下信息。员工信息 有关物流系统员工的基本信息包括工号、姓名、出生年月、权限、薪水等。包裹信息 有关包裹的基本信息包括包裹编号、寄件和收件双方的基本信息姓名、电话、地址等、运费、寄收件日期等。仓库信息 有关物流仓库的基本信息包括仓库位置、仓库管理员等。用户登录信息 有关员工登录账号的信息包括用户名、密码等。物流跟踪信息 有关物流信息的更新包括入库出库操作、操作人、所属仓库、操作日期等。派件信息 某一给定包裹是由哪一位派件员派送的用户对此次快递评分多少。仓库管理员 该表列出了拥有仓库管理权限的员工并为今后添加仓库管理员职称、师徒关系预留接口。派件员 该表列出了拥有派件权限的员工以及用户对他们的平均评分。人力资源管理 该表列出了拥有人力资源部门相关权限的员工。前台接待员 该表列出了拥有前台接待、包裹信息录入相关权限的员工。运输员 该表列出了拥有运输权限的员工并为今后添加运输员所属运输车队、运输车辆、车辆检修和保险情况预留接口。1.3 系统常做的查询与更新经常做的查询或许对创建索引有影响的1.包裹信息查询包括列出所有包裹和根据用户的筛选列出指定包裹2.物流信息查询包括按时间序列列出给定包裹的物流跟踪情况根据经常做的查询需要创建有关视图的无。关于更新1.包裹信息录入的频率非常大但是一般情况下录入后更新较少2.物流跟踪信息录入的频率非常大一旦录入后不允许修改和删除3.员工信息相对稳定本系统只涉及姓名、出生年月、薪水等信息这些信息或者一旦确定几乎永远不会被更改、或者一般用户没有修改权限因此只需对人力资源部门开放展示和修改权限即可1.4 应用程序功能1.4.1 公共查询任何用户包括员工和顾客都可以在非登录状态下查询自己的包裹信息。1.4.2 用户登录员工可以在此登录。登录状态下访问该页面可以选择注销或者修改密码。预留了未来与其他系统通信的接口例如可以访问财务管理系统导出历史 6 个月的工资和奖金。1.4.3 单向信息更新所谓单向信息更新指的是快捷信息添加且该信息只允许增加不允许修改和删除。派件信息 派件员可以在这个界面看到被分配给自己的、且正在派送中的信息包括收件人地址、联系方式等并提供一个按钮允许派件员点击后确认送达。物流信息 运输员可以在这个界面录入包裹的最新动态只需给定仓库名称、包裹编号即可在数据库中录入“包裹目前已经到达某一仓库”这一信息系统将自动添加当前时间。如上文所述这些信息不具有可修改性一旦录入就是终态因此无需提供修改和删除功能而之所以选择将这两个页面独立是出于 2 方面考虑1.单页面应用可以很容易地部署到移动终端、例如小型嵌入式设备等只需要极少量的信息展示和输入甚至只需要一个按钮无需支持复杂的交互逻辑2.录入包裹编号可以很方便地与扫码枪结合只需预先设置仓库名称之后使用扫码枪扫描条形码录入包裹编号即可自动提交并情况当前信息实现快速、批量录入1.4.4 完整信息更新员工 员工信息的录入、修改和删除。录入员工信息时必须给定员工的手机号、邮箱、出生年月、薪水等。其中手机号必须是 11 位的数字邮箱必须满足邮箱命名规则薪水只能包含数字 0 到 9 和小数点且必须表示一个数值。包裹 包裹信息的录入、修改和删除。录入包裹信息时必须给定寄件收件双方的信息联系方式、姓名、地址等。其中手机号必须是 11 位的数字地址不得少于 10 个字城市将提供下拉框以供选择订单金额只能包含数字 0 到 9 和小数点且必须表示一个数值。其中包裹信息查询页面将嵌入物流信息查看页面。该页面包括给定包裹目前所处的状态已揽件、运输中、派件中、已签收并给出按时间序列排序的物流运输信息查看。仓库 仓库信息的录入、修改和删除。录入仓库信息时必须给定仓库所在地、仓库名称、仓库负责人名称。其中城市将提供下拉框以供选择仓库负责人将提供下拉框以供选择。2 数据库概念设计2.1 确定实体和属性分析物流管理系统的管理需求将系统中涉及到的人、包裹、仓库进行抽象得到了系统的实体如下1.派件员实体集。属性包括派件员员工工号、派件员平均得分2.派件信息实体集。属性包括包裹编号、派件员员工工号、顾客对这一次派件的评分3.员工实体集。属性包括员工工号、姓名、手机号、出生日期、薪水4.人力资源实体集。属性包括员工工号5.城市实体集。属性包括城市名称、上级城市名称6.登录信息实体集。属性包括邮箱、密码、员工工号7.包裹接待员信息实体集。属性包括包裹编号、接待员员工工号8.包裹实体集。属性包括包裹编号、寄件人姓名、寄件人联系方式、寄件人地址、寄件人所在城市、寄件日期、收件人姓名、收件人联系方式、收件人地址、收件人城市、收件日期、运费、运费是否已经支付、包裹状态9.前台接待员实体集。属性包括员工工号10.物流信息实体集。属性包括出入库操作、操作日期、操作员员工工号、仓库编号、包裹编号11.运输员实体集。属性包括员工工号12.仓库管理员实体集。属性包括员工工号13.仓库实体集。属性包括仓库编号、仓库名称、仓库所在城市、仓库管理员员工号2.2E-R 图总览 总览如图。图 1:ER 图总览员工角色相关如图 2 表示员工及所属部门的 E-R 图其中 Employees 为员工信息表其余各表分别表示不同的职能当员工属于某一部门时信息将添加到对应表中这些表采用了员工工号作为外键约束。图 2:员工角色相关 E-R 图仓储 如图 3 表示仓库管理员及所管理仓库的 E-R 图。图 3:仓储 E-R 图包裹及物流 如图 4 表示包裹信息、寄件和收件所在城市、物流信息跟踪按时间序列的各城市仓库周转历史的 E-R 图。图 4:包裹及物流 E-R 图派件 如图 5 表示包裹派件的 E-R 图包括包裹由哪一个派件员派送、这一次派送的用户评分、派件员基础信息及平均得分等。图 5:派件 E-R 图包裹与前台接待 如图 6 表示包裹与前台接待的 E-R 图。图 6:包裹与前台接待 E-R 图3 数据库逻辑结构设计3.1 关系模式设计根据概念结构设计得到的 E-R 图和转换规则得到如下关系模式 5Dispatchers 表(自增编号、员工工号 6、派件员平均得分、记录添加时间、记录最后一次修改时间)DispatchPairs 表(自增编号、包裹编号 7、员工工号 8、顾客对这一次派件的评分、记录添加时间、记录最后一次修改时间)Employees 表(员工工号、姓名、手机号、出生日期、薪水、记录添加时间、记录最后一次修改时间)HumanResources 表(自增编号、员工工号 9、记录添加时间、记录最后一次修改时间)Locations 表(城市名称、上级城市名称 10、记录添加时间、记录最后一次修改时间)Members 表(自增编号、邮箱、密码、员工工号 11、记录添加时间、记录最后一次修改时间)PackageReceptionists 表(自增编号、员工工号 12、包裹编号 13、记录添加时间、记录最后一次修改时间)Packages 表(包裹编号、寄件人姓名、寄件人联系方式、寄件人地址、寄件人所在城市 14、寄件日期、收件人姓名、收件人联系方式、收件人地址、收件人城市 15、收件日期、运费、运费是否已经支付、包裹状态、记录添加时间、记录最后一次修改时间)Receptionists 表(自增编号、员工工号 16、记录添加时间、记录最后一次修改时间)Trackings 表(自增编号、出入库操作、员工工号 17、包裹编号 18、仓库编号 19、记录添加时间、记录最后一次修改时间)Transports 表(自增编号、员工工号 20、记录添加时间、记录最后一次修改时间)WareHouseManagers 表(自增编号、员工工号 21、记录添加时间、记录最后一次修改时间)WareHouses 表(仓库编号、仓库名称、仓库所在城市 22、仓库管理员 23、记录添加时间、记录最后一次修改时间)3.2 基本表设计基本表设计如下所示。表 1:Dispatchers属性名数据类型是否可空列约束默认值键解释idrateuuidint(11)int(11)varchar(255)否否是自增、唯一3主键外键ID 派件员平均得分派件员员工工号表 2:DispatchPairs属性名数据类型是否可空列约束默认值键解释idint(11)否自增、唯一3主键IDrateint(11)是用户对当前订单的评分uuidvarchar(255)是外键派件员员工工号package_idint(11)是外键包裹编号表 3:Employees属性名数据类型是否可空列约束默认值键解释uuidvarchar(255)否唯一主键员工工号namevarchar(255)否姓名phonevarchar(255)否手机birthdaydatetime否生日salaryint(11)否薪水表 4:HumanResources属性名数据类型是否可空列约束默认值键解释idhr_idint(11)varchar(255)否是自增、唯一主键外键IDHR 工号表 5:Locations属性名数据类型是否可空列约束默认值键解释Locationsfathervarchar(255)varchar(255)否是唯一主键外键城市上级城市表 6:Members属性名数据类型是否可空列约束默认值键解释idint(11)否自增、唯一主键IDemailvarchar(255)否唯一邮箱passwordvarchar(255)是密码uuidvarchar(255)是外键员工工号表 7:PackageReceptionists属性名数据类型是否可空列约束默认值键解释idreceptionist_idpackage_idint(11)varchar(255)int(11)否是是自增、唯一主键外键外键ID 接待员员工工号包裹编号表 8:Packages属性名数据类型是否可空列约束默认值键解释package_idint(11)否自增、唯一FALSE主键包裹编号sender_namevarchar(255)否寄件人姓名sender_phonevarchar(255)否寄件人手机sender_addressvarchar(255)否寄件人地址send_datedatetime否寄件日期sender_cityvarchar(255)是外键寄件人城市receiver_namevarchar(255)否收件人姓名receiver_phonevarchar(255)否收件人手机receiver_addressvarchar(255)否收件人地址receive_datedatetime是收件日期receiver_cityvarchar(255)是外键收件人城市priceoat否运费paidtinyint(1)否是否已经支付statusENUM否状态表 9:Receptionists属性名数据类型是否可空列约束默认值键解释idreceptionist_idint(11)varchar(255)否是自增、唯一主键外键ID 接待员工号表 10:Trackings属性名数据类型是否可空列约束默认值键解释idint(11)否自增、唯一当前日期主键IDactionENUM否[’出库’,’入库’]操作datedatetime否日期warehouse_idint(11)是外键仓库编号package_idint(11)是外键包裹编号transport_idvarchar(255)是外键运输员工号表 11:Transports属性名数据类型是否可空列约束默认值键解释idtransport_idint(11)varchar(255)否是自增、唯一主键外键ID 运输员工号表 12:WareHouseManagers属性名数据类型是否可空列约束默认值键解释idmanager_idint(11)varchar(255)否是自增、唯一主键外键ID 仓库管理员工号表 13:WareHouses属性名数据类型是否可空列约束默认值键解释warehouse_idint(11)否自增、唯一主键仓库编号warehouse_namevarchar(255)否仓库名称manager_idvarchar(255)是外键仓库管理员工号locationvarchar(255)是外键仓库所在城市以上各表另有 2 个字段表示每条记录新建的时间和最后修改的时间如表 14 所示。表 14:额外信息记录属性名数据类型是否可空列约束默认值键解释createdAtupdateAtdatetimedatetime否否当前时间记录创建时间最后一次修改时间4 数据库物理设计和实施4.1 数据库的创建使用 MariaDB10.3.14数据库基本信息如下服务器类型MariaDB服务器版本10.3.14-MariaDB-log-Sourcedistribution协议版本10数据库客户端版本libmysql-mysqlnd5.0.12-dev-20150407服务器字符集UTF-8Unicode(utf8)4.2 创建基本表基本表的创建使用 ORM 模型 实现。但是这里仍将给出创建每个基本表的 SQLDDL 语句。4.3 触发器设计触发器设计采用 ORM 模型这里将给出 ORM 模型定义中与触发器相关的部分。Employees.afterCreate(Async (employee) {await Members.create({ email: employee.uuid zzw.mock.com, uuid: employee.uuid, password: MD5_SUFFIX.OUTER md5(MD5_SUFFIX.INNER employee.phone.toString().substr(7)) }) }) Employees.beforeDestroy(async (employee) { await Members.destroy({ where: { uuid: employee.uuid } }) })例如上面这段代码在添加新员工的时候自动触发将员工信息添加到登录信息表。登录信息表中的密码默认采用员工手机号后 4 位、邮箱采用员工工号加企业域名组合、员工工号采用员工信息表中的工号。4.4 存储过程设计本系统涉及到较多的存储过程逻辑这里只展示部分完整内容可参看源码。存储过程设计采用 ORM 模型这里将给出 ORM 模型定义中与存储过程相关的部分。当前录入的物流信息中仓库所在城市与收件人地址所在城市相同时自动将包裹状态更新为派件中并随机从派件员列表中找出一位派件员指派派送该包裹const params request.body await WareHouses.findOne({ where: { warehouse_name: params.warehouse_name }, attributes: [warehouse_id] }).then(async warehouse { const warehouse_id warehouse.get(warehouse_id) await Trackings.create({ action: params.action, date: Date.now(), package_id: params.package_id, warehouse_id: warehouse_id, transport_id: request.user.uuid }).then(async() { await Packages.update({ status: 运输中 }, { where: { package_id: params.package_id } }) // 只要有物流信息了就自动进入运输中这一状态 .then(async() { await Packages.findOne({ where: { package_id: params.package_id }, attributes: [receiver_city] }).then(async city1Project { const city1 city1Project.get(receiver_city) await WareHouses.findOne({ where: { warehouse_id: warehouse_id }, attributes: [location] }).then(async city2Project { const city2 city2Project.get(location) if (city1 city2) { await Packages.update({ status: 派件中 }, { where: { package_id: params.package_id } }) // 如果当前城市和收件人城市相同则进入派件状态 const Dispatchers orm.import(../database/models/Dispatchers) await Dispatchers.count().then(async count { const rand Math.floor(Math.random() * count) await Dispatchers.findOne({ offset: rand }).then(async randomProject { // 随机一个派件员 const randomUUID randomProject.get(uuid) const DispatchPairs orm.import(../database/models/DispatchPairs) await DispatchPairs.create({ package_id: params.package_id, uuid: randomUUID }) // 加入派件信息表 .then(() { response.sendStatus(200) }) }) }) } else { response.sendStatus(200) } }) }) }) }, () { response.sendStatus(403) }) })5 应用程序设计5.1 开发及运行环境介绍5.1.1 技术栈前端1.Node.js2.Vue.js3.Electron.js4.iView后端1.Node.js2.Express.js3.Sequelize5.1.2 数据库初始方式有 3 种方式进行数据库初始化1.直接运行程序使用我的服务器上的数据库进行演示2.修改/server/database/util.js 的配置信息为你自己的数据库地址、用户名、密码然后导入我提供的 SQL 数据3.修改/server/database/util.js 的配置信息为你自己的数据库地址、用户名、密码运行 npmruninit-database 命令系统将自动初始化数据库并新建初始超级管理员5.1.3 运行方式首先需要安装 Node.js 和 npm。然后下载源码在源码根目录下运行 npminstall将自动下载并安装依赖。最后运行 npmrunrun 启动程序。也可以通过分别运行 npmrunserver 和 npmrundev 启动后端和前端。5.2 主要功能设计这里展现各个功能的演示画面和部分代码完整代码过于冗长不便展示可以通过阅读源码的方式了解全部逻辑。5.2.1 公共查询该页面提供一个输入框允许用户输入手机号进行查询。点击查询按钮之后系统将从数据库找出寄件人联系方式或收件人联系方式为该手机号的所有包裹并罗列包裹的基本信息、包裹所处状态、物流跟踪信息等。图 7:公共查询对于已签收的包裹还会给出一个评价按钮用户点击该按钮后可对这一次配送进行评分评分提交后会保存进数据库并自动计算和更新该派件员的平均得分。图 8:用户评分部分代码如下5.2.2 登录该页面根据用户是否登录有不同的展示。当用户未登录时页面提供用户名输入框和密码输入框用户输入后点击登录按钮系统将请求发送到后端后端返回登录成功或失败。图 9:用户登录当登录失败时弹出消息框说明用户名或密码错误用户可关闭消息框后再次登录若登录成功将展示个人信息页面包括修改密码等功能。图 10:用户登录失败图 11:用户登录成功5.2.3 派件信息该页面仅在员工登录后可见。系统会从数据库搜索出被分配给本人派送的且还在派送状态的所有包裹并以表格的形式展示展示的数据隐藏了无关信息只保留寄件收件双方的必要联系信息。在表格的每一行还提供一个确认签收的按钮派件员点击该按钮后会将该包裹标记为已送达表格中不再显示这条记录。图 12:派件信息查看部分代码5.2.4 物流跟踪该页面仅在员工登录后可见。该页面提供 2 个下拉框和 1 个输入框分别需要选择出入库操作和所在仓库名称选择完成后输入包裹编号并提交系统会将对包裹的这一次操作保存到数据库中并绑定当前操作时间。图 13:运输状态录入5.2.5 员工信息维护该页面仅登录后可见。用户登录后将首先看到以表格的形式展示的所有员工的信息。表格项目过多时提供分页功能表格每一行提供修改和删除按钮分别进行修改和删除操作表格顶部提供新增按钮点击可添加新员工的信息点击新增或修改按钮后将进入员工信息的维护页面。图 14:员工信息维护页面包括一个表单填写表单中各个项目后点击提交。该页面在新增员工时表单各个项目为空。在修改员工信息时表单各个项目的初始值为数据库中该员工的已有信息。用户输入时和点击提交按钮后都会触发校验器校验输入的信息是否正确。图 15:员工信息维护特别的在修改员工信息的页面同时提供了一个重置密码的选项点击该按钮后后端会随机生成一个密码并重置该员工的密码。重置成功后新密码会以邮件的形式发送到员工的邮箱。除本人外不再有其他人知道密码是出于安全考虑。图 16:重置密码这些页面同时支持搜索。图 17:搜索5.2.6 包裹该页面仅登录后可见。该页面与员工页面类似。图 18:包裹页面同时给出物流跟踪信息查看给定一个包裹编号将会展示包裹的物流变化情况。展示内容包括 2 部分第 1 部分是包裹所属状态状态以进度条的方式展示第 2 部分展示包裹于何时到达何地以时间轴的方式展示。图 19:物流跟踪5.2.7 仓库该页面仅登录后可见。该页面与员工页面类似。5.3 主要界面图 20:主要界面5.4 前后端通信我的系统采用了前后端分离的方法因此需要有一个方法能够在前端和后端各自判断用户身份以及做好权限跳转。5.4.1 前端路由前端路由采用 vue-router 和 axios前端数据存放采用 vuex。当用户访问的页面需要登录后可见那么会发送一个请求给后端请求包括“跳转自”页面和“跳转到”页面即 from 和 to2 个信息后端根据用户判断是否有权限访问对应页面并返回结果。当用户登录成功时会自动将 Token 信息保存当用户点击注销按钮后或前端路由收到 401 响应时会清空 Token 信息。5.4.2 拦截器请求拦截器 对于每一个发往后端的请求都拦截下来强制添加一个 Authorization 头部。包括用户登录成功后保存的 BearerToken如果用户没有登录则该字段为空。响应拦截器对于后端发往前端的所有响应全部拦截下来拦截器会检查状态是否成功。对于成功的状态200拦截器放行对于未授权的状态401拦截器跳转到登录页面对于已授权但权限不够的状态403拦截器弹出错误信息对于其他状态406 或者 50X拦截器抛出异常。5.4.3 后端路由使用 ExpressRouter 进行路由中间件处理。后端路由的工作流程是收到请求时请求会逐层、依次通过每一层中间件当前层按照当前的逻辑处理完成后将请求和当前层处理的结果一传递给下一层。5.4.4 使用 JWT 进行身份验证JWT 定义了加盐字符串以及无需经过校验的公共 API。所有请求过来都会进行身份验证只有通过了 JWT 校验的请求才会继续发往下一层否则JWT 中间件会返回 401。通过 JWT 校验之后依次通过其他中间件最后进行错误处理。5.5 后端其他5.5.1 后端初始化后端初始化过程首先定义了与跨域相关的头部随后注册了后端路由。5.5.2 数据库初始化数据库的初始化提供了一个初始化角本按照数据表的依赖关系强制删除和重建数据表并录入初始的城市信息和超级管理员帐号初始化时会对超级管理员授予所有权限。5.5.3 异步请求所有异步相关的代码都加了 async 和 await。5.5.4 检查 Token对于给定的 API如果需要用户授权则使用 JWT 解码然后取出 UUID并判断 Token 是否过期。5.5.5 安全性密码用了 MD5 加密加密前和加密后都添加了前缀用以混淆。5.5.6 权限API 接口与数据库交互前都做了权限判断。5.6 用户体验优化我做了大量的优化用户体验的工作以下仅列出部分1.成功就不要弹出 Modal 让用户再点一次只要 Message 就好失败必须弹出 Modal 手动确认2.表单必须在用户输入的同时就 oninputvalidate不要发到后台校验失败了再弹错误3.有校验没有通过的表单就不给提交4.表单提交失败不能清空表单5.当页面上某一元素变化不刷新整个页面只要局部刷新6.401 错误直接重定向到登录页面7.新增一条记录成功之后返回前一页8.点击遮罩层可以取消点击 ESC 可以取消不必一定点取消按钮或者右上角叉叉9.下拉框可以搜索例如城市页面或者仓库选择页面用户可以直接输入关键字进行全文搜索10.查询订单不用新开一个页面直接当前页面批量 Render且支持收起和展开 11.最后一个输入框中按下回车等效于触发 PrimaryButton,除此而外还做了很多优化用户体验的工作细说之下可能又是一篇报告这里仅列出 injectreload 代替全局刷新的部分代码。Vue 中刷新页面的方法有很多但是用户体验都不太好如下1.this.$router.go(0)页面会一瞬间的白屏体验不是很好局部刷新可以用 provide/inject 组合。原理是允许一个祖先组件向其所有子孙后代注入一个依赖不论组件层次有多深并在起上下游关系成立的时间里始终生效。在 App.vue 声明 reload 方法控制 router-view 的显示或隐藏从而控制页面的再次加载。在需要用到刷新的页面。在页面注入 App.vue 组件提供的 reload 依赖在逻辑完成之后直接 this.reload()调用即可刷新当前页面。♻️ 资源大小2.25MB➡️资源下载https://download.csdn.net/download/s1t16/87404248注更多内容可关注微信公众号【神仙别闹】如当前文章或代码侵犯了您的权益请私信作者删除