简介:面向微信小程序毕业设计的高速公路服务区充电桩在线预订系统,包含完整前后端源码、开发说明与演示视频,适合计算机相关专业学生用于课程设计、毕业答辩或项目二次开发。系统基于微信开发者工具与Java、MySQL实现,涵盖游客、会员、管理员三类角色,支持服务区信息浏览、公告与地图查看、会员注册审核、在线预订充电桩及订单管理等核心流程,能完整体现小程序端与服务端协同开发思路。资源包共1131个文件,约25.72MB,以png/svg界面素材、vue/js前端页面与逻辑、java/class后端代码、wxml/wxss小程序页面、sql数据库脚本为主,并附带mp4演示视频和项目配置说明,打开即可对照学习。已有357人学习下载,尤其适合需要快速理解小程序毕业设计完整架构、梳理前后端数据交互与权限管理逻辑的开发者。
1. 高速公路服务区充电桩在线预订小程序:从注册审核到订单闭环的三端实现
拿到这份毕业设计源码的第一眼,我被一堆.bak文件劝退过——IndexMain.vue.bak、BreadCrumbs.vue.bak、update-password.vue.bak,乍看以为是没整理的半成品。但顺着1-install.bat、2-run.bat、3-build.bat三个脚本跑下来,发现这套「微信开发者工具 + Java + MySQL」的组合其实非常完整:游客看服务区、会员预订充电桩、管理员在 PC 后台审核用户和订单,业务闭环是通的。适合拿来做毕业设计底子,也适合想快速搞清楚小程序预约类项目怎么落地的人。
2. 项目功能与角色权限:游客、会员、管理员三端各管什么
2.1 非登录用户:服务区浏览、公告查看与地图信息
游客是指没有注册的用户。打开小程序首页后,能看到后台维护的所有高速公路服务区,每个服务区展示名称、位置、简介和配套服务。这里的「配套服务」在源码里其实预留了扩展空间:加油站、充电桩、便利店、卫生间都可以作为服务项展示,充电桩预订是主业务,加油站在线预订是副业务——摘要里明确写了会员可以「在线预定加油」,说明项目把「加油」和「充电」都纳入了预订体系,服务区作为一个容器,下面挂多种服务类型。
游客能做的操作很有限:刷服务区列表、看公告资讯、看地图。公告资讯是后台管理员发的,内容一般包括服务区临时关闭通知、充电桩维护通知、节假日流量提醒等。地图信息这里不是实时定位,而是管理员在后台维护的静态地图或位置描述,游客打开详情页能看到服务区所在路段、出入口方向。这样设计是为了降低实现难度——不做地图 SDK 集成,数据全部走后台录入,演示效果稳定,不容易翻车。
提示:如果你想让地图变成可交互的,可以在服务区详情页接入地图 SDK 的 marker 展示,源码里保留了这个入口,改动量不大。
2.2 会员端:注册审核、充电桩预订与订单管理
会员是这套系统的核心使用者。游客提交注册申请后,个人信息会进入管理员的「用户审核」列表,审核通过后账号才被激活,才能用账号密码登录小程序。这里有个容易忽略的设计点:注册不是即时的,是人工审核制——这正是课设项目里的加分项,因为它让「管理员审核用户」这个功能有了存在的意义,而很多同类型小程序把注册做成自动通过,后台的审核页面就成了空摆设,答辩时很容易被问住。
登录后的会员拥有游客的全部权限,还能做三件事:在线预订充电桩、提交订单、修改个人信息。
第一,预订充电桩。会员进入某个服务区详情页,能看到该服务区下面的充电桩列表,每个桩有编号、类型(快充/慢充)、状态(空闲/占用)。选桩后填写预计到达时间、车牌号、充电时长,提交订单。订单金额怎么算的?源码里大概率是前端根据时长和单价计算后传给后端,或者后端根据配置的费率自动算——两种做法都合理,后者的健壮性更好,因为金额计算逻辑放在后端,前端改不了价格。
第二,提交订单。订单提交后进入「待使用」状态,会员可以查看自己的订单列表,也能在到达前取消订单。这里需要你在二次开发时补一个规则:临近预约时间多久之内不允许取消,否则桩位被占用却没人来,会影响其他车主的使用体验,也避免恶意锁桩。
第三,修改个人信息。会员能改手机号、车牌号、默认充电偏好等,改密码走的是update-password.vue.bak对应的那个弹窗页面。个人中心的入口在小程序user页面,登录状态用缓存 token 维持,每次请求后端时带上userId或 token 做身份识别。
2.3 管理员端:服务区、公告、用户、订单四条管理线
管理员通过后台登录页面进入管理系统,验证权限后才能操作。后台是一个 PC 端的 Web 管理界面,从.bak文件名能看出是 Vue 风格的后台框架:IndexMain.vue是主内容区、IndexAsideStatic.vue是左侧静态菜单栏、IndexHeader.vue是顶部导航、BreadCrumbs.vue是面包屑。这套结构在课设项目里非常典型,左边菜单、顶部标题、中间内容区的布局,管理线条清晰,二次开发加页面时只需要照着现有模块复制改。
管理员的职责分四条线:
- 服务区管理:增删改查高速公路服务区,包括服务区名称、所在高速路段、位置描述、配套服务开关、营业状态。小程序端的首页列表直接读这张表。
- 公告管理:发布和下线公告,公告标题、内容、发布时间、置顶状态。小程序端的公告列表直接读这张表,游客和会员都能看到。
- 用户管理:核心操作是审核注册用户,通过或拒绝会员申请;也可以禁用违规账号。前端展示列表带分页,按状态筛选待审核、正常、禁用三种。
- 订单管理:查看全部订单,按状态筛选(待使用/已完成/已取消),订单详情里能看到会员信息、服务区、桩号、预约时间、金额。管理员可以手动关闭异常订单,比如用户到了现场找不到桩的情况。
3. 技术架构与目录结构:微信开发者工具、Java、MySQL 的三角组合
3.1 三个入口脚本:install、run、build 各管什么
拿到源码后别急着打开小程序,先看根目录下三个.bat文件,它们是这个项目的「快速上手手册」。从文件名能直接读出这套项目的标准化操作流程:第一次运行先安装依赖,之后日常开发跑服务,最后交付前打包构建。
rem 1-install.bat —— 安装后端依赖 mvn clean install -DskipTests pause rem 2-run.bat —— 启动后端服务 mvn spring-boot:run pause rem 3-build.bat —— 打包生产环境 mvn clean package -DskipTests pause三个脚本是按顺序用的:第一次运行先执行1-install.bat安装 Maven 依赖,然后执行2-run.bat启动后端服务,之后每次开发调试只需要跑2-run.bat就够了;要部署到服务器上时,用3-build.bat打出 jar 包。注意这些脚本假设你已经配置了 Java 环境和 Maven 环境变量,如果mvn命令识别不了,先去配JAVA_HOME和MAVEN_HOME,这是最常见的环境问题。
从CommonController.class这个文件名能判断后端是经典的 Spring MVC 结构:CommonController是通用控制器,负责接收前端请求、调用 Service、返回 JSON。.class说明源码包里带编译产物,也说明后端是 Java 技术栈,大概率是 Spring Boot 项目,用 Maven 做依赖管理。MySQL 负责持久化,连接配置在application.properties里,需要你改成自己本地的数据库地址和密码,改完重启服务才生效。
3.2 小程序端与后台管理端目录对照
这个项目是典型的「小程序 + PC 后台」双端结构,微信小程序在微信开发者工具里跑,后台管理界面在浏览器里跑,两者共享同一个后端服务。目录结构大致如下:
├── 1-install.bat # Maven 安装依赖 ├── 2-run.bat # 启动后端服务 ├── 3-build.bat # 打包后端 ├── src/main/java │ ├── controller/ # 接口层:CommonController 等 │ ├── service/ # 业务层:用户、订单、服务区 │ ├── mapper/ # 数据访问层:MyBatis 或 JPA │ └── model/entity/ # 实体类:User、Order、ServiceArea ├── src/main/resources │ ├── application.properties # 数据库及其他配置 │ └── mapper/ # XML 映射文件(如果用 MyBatis) ├── miniprogram/ # 微信小程序端 │ ├── pages/ │ │ ├── index/ # 首页:服务区列表 │ │ ├── detail/ # 服务区详情 + 充电桩列表 │ │ ├── order/ # 订单提交与列表 │ │ ├── user/ # 个人中心 │ │ └── login/ # 登录注册 │ └── app.js / app.json └── admin-web/ # PC 后台管理界面(Vue) ├── src/ │ ├── views/ │ │ ├── IndexMain.vue │ │ ├── IndexAsideStatic.vue │ │ ├── IndexHeader.vue │ │ ├── BreadCrumbs.vue │ │ └── update-password.vue │ └── main.css └── package.json目录里那批.bak文件是开发过程中的备份文件——改了IndexMain.vue后顺手把旧版本复制成.bak留着当后悔药。这在真实项目里是不该提交进仓库的,但作为课设源码包反而是个福利:把.bak和当前版本 diff 一下,能看出作者迭代了哪些界面逻辑,对想改前端的人有参考价值。我自己接手这份源码时,第一件事就是 diffIndexMain.vue和它的 bak 版本,确实找到了列表页分页逻辑的改动痕迹。
3.3 核心表设计:用户、服务区、充电桩、订单怎么关联
数据库设计的核心是四张表,关联关系是:服务区 1 对 N 充电桩,用户 1 对 N 订单,订单关联服务区和充电桩。业务主表加状态字段,是课设项目里最高效的建模方式。
-- 用户表:注册即插入,status 控制是否审核通过 CREATE TABLE `t_user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(50) DEFAULT NULL, `password` varchar(100) DEFAULT NULL, `phone` varchar(20) DEFAULT NULL, `plate_no` varchar(20) DEFAULT NULL, -- 车牌号 `status` int DEFAULT '0', -- 0待审核 1正常 2禁用 PRIMARY KEY (`id`) ); -- 服务区表:一个服务区下挂多个充电桩 CREATE TABLE `t_service_area` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `highway` varchar(100) DEFAULT NULL, -- 所在高速路段 `location` varchar(255) DEFAULT NULL, -- 位置描述 `status` int DEFAULT '1', PRIMARY KEY (`id`) ); -- 充电桩表:service_area_id 关联服务区 CREATE TABLE `t_charger` ( `id` int NOT NULL AUTO_INCREMENT, `service_area_id` int DEFAULT NULL, `code` varchar(20) DEFAULT NULL, -- 桩编号,如 A-01 `type` int DEFAULT '1', -- 1快充 2慢充 `status` int DEFAULT '0', -- 0空闲 1占用 PRIMARY KEY (`id`) ); -- 订单表:记录谁在哪个服务区预定了哪个桩 CREATE TABLE `t_order` ( `id` int NOT NULL AUTO_INCREMENT, `order_no` varchar(50) DEFAULT NULL, `user_id` int DEFAULT NULL, `service_area_id` int DEFAULT NULL, `charger_id` int DEFAULT NULL, `plate_no` varchar(20) DEFAULT NULL, `appointment_time` datetime DEFAULT NULL, `duration` int DEFAULT NULL, -- 充电时长(小时) `amount` decimal(10,2) DEFAULT NULL, -- 订单金额 `status` int DEFAULT '0', -- 0待使用 1已完成 2已取消 PRIMARY KEY (`id`) );四张表的设计思路是「业务主表 + 状态字段」:用户表用status区分审核状态,充电桩表用status区分空闲占用,订单表用status区分订单生命周期。这种「一个状态字段走天下」的模式,对课设项目来说非常够用,排查问题时也直观——先看数据再看代码,能定位是逻辑问题还是数据问题。你可以在 MySQL 里直接改t_charger.status来模拟桩被占用,测试预订接口的拦截逻辑。
4. 核心代码实现:从会员提交预订到管理员处理订单的完整链路
4.1 小程序端:充电桩预订页面的提交逻辑
小程序端预订充电桩是这套系统最核心的交互。页面加载时先拿serviceAreaId请求后端,拉回该服务区下的充电桩列表,渲染成可点击的桩位;用户点某个空闲桩、填预约时间和车牌号、点提交,页面把数据 POST 给后端。
// miniprogram/pages/detail/detail.js Page({ data: { areaId: '', chargerList: [], selectedCharger: null, appointmentTime: '', plateNo: '' }, onLoad(options) { this.setData({ areaId: options.id }); this.loadChargers(); }, loadChargers() { wx.request({ url: 'http://localhost:8080/api/charger/list?areaId=' + this.data.areaId, success: (res) => { this.setData({ chargerList: res.data }); } }); }, selectCharger(e) { this.setData({ selectedCharger: e.currentTarget.dataset.item }); }, submitOrder() { const order = { serviceAreaId: this.data.areaId, chargerId: this.data.selectedCharger.id, appointmentTime: this.data.appointmentTime, plateNo: this.data.plateNo, userId: wx.getStorageSync('userId') }; wx.request({ url: 'http://localhost:8080/api/order/submit', method: 'POST', data: order, success: (res) => { wx.showToast({ title: '预订成功', icon: 'success' }); wx.navigateTo({ url: '/pages/order/order' }); } }); } });这里要注意loadChargers和submitOrder的请求地址是写死的http://localhost:8080,在微信开发者工具里的模拟器能通,但真机预览时必须改成局域网 IP 或已备案的 HTTPS 域名,否则请求直接被拦截,这是小程序开发最经典的坑。
selectCharger把用户点中的桩存到selectedCharger,submitOrder拼装订单数据提交。当前端校验只做了「是否选桩」这层,真正的校验在后端——比如预约时间不能早于当前时间、同一个桩在同一时段不能重复预订,这些后端必须实现,前端只是体验层,不能依赖前端做数据正确性兜底。
参数说明:appointmentTime是预约到达时间,格式一般传yyyy-MM-dd HH:mm:ss;plateNo是车牌号,用于到达后的核验;chargerId决定订单绑定哪台桩。这三个参数是订单表里的核心字段,后端接口会逐一带校验,缺一个直接返回「参数不完整」。
4.2 后端:订单提交接口的 Controller 与 Service
后端CommonController接收小程序请求,转发到OrderService处理。核心逻辑分三步:校验参数、检查充电桩时段是否冲突、落库并返回订单号。收单接口是整套系统的业务枢纽,下面这段是这类项目最常见的 Controller 加 Service 组织方式。
// 示意:OrderController.java 核心方法 @RestController @RequestMapping("/api/order") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/submit") public Result submit(@RequestBody OrderDTO dto) { // 1. 参数校验:预约时间、车牌号、桩号缺一不可 if (dto.getChargerId() == null || dto.getAppointmentTime() == null || dto.getPlateNo() == null) { return Result.error("参数不完整"); } // 2. 业务处理:查桩、算金额、落库 Order order = orderService.createOrder(dto); return Result.success(order.getOrderNo()); } @GetMapping("/list") public Result list(Long userId) { return Result.success(orderService.listByUser(userId)); } } // 示意:OrderServiceImpl.java 核心方法 @Service public class OrderServiceImpl implements OrderService { @Autowired private ChargerMapper chargerMapper; @Autowired private OrderMapper orderMapper; @Override @Transactional public Order createOrder(OrderDTO dto) { // 1. 充电桩必须存在且空闲 Charger charger = chargerMapper.selectById(dto.getChargerId()); if (charger == null || charger.getStatus() != 0) { throw new BusinessException("该充电桩不可预订"); } // 2. 检查同一时间段该桩是否已有订单 int count = orderMapper.countByChargerAndTime( dto.getChargerId(), dto.getAppointmentTime()); if (count > 0) { throw new BusinessException("该时段已被预订"); } // 3. 计算金额:按充电桩类型和时长折算 BigDecimal amount = calcAmount(charger.getType(), dto.getDuration()); // 4. 生成订单号并落库 Order order = new Order(); order.setOrderNo("ORD" + System.currentTimeMillis()); order.setUserId(dto.getUserId()); order.setChargerId(dto.getChargerId()); order.setAppointmentTime(dto.getAppointmentTime()); order.setDuration(dto.getDuration()); order.setAmount(amount); order.setStatus(0); orderMapper.insert(order); // 5. 顺手把桩标记为占用,防止重复预订 chargerMapper.updateStatus(dto.getChargerId(), 1); return order; } }这段代码最关键的设计是「先查再写」的双重保护:createOrder里先查充电桩状态,再查时间冲突,然后@Transactional事务里落订单并同步修改充电桩状态。这样两个用户同时提交订单时,数据库的行锁会挡住第二次写入,不会出现「一个桩被两个人订」的脏数据。课设项目能考虑到这一层,答辩时是实打实的加分项。
费用计算这里用了简化方案:快充和慢充按不同费率算。calcAmount是私有方法,内部逻辑大致是duration * rate,rate 按充电桩类型取。真实场景里充电费用应该按实际电量算,但课设里按时间折算更可控,演示效果好,也方便答辩时解释业务规则。
4.3 后台管理端:订单列表与用户审核的实现样板
后台管理界面是 Vue 写的,IndexMain.vue.bak是主内容区的备份,IndexAsideStatic.vue.bak是左侧菜单。订单管理页面的核心是「表格 + 状态筛选 + 操作按钮」三板斧,所有管理页面基本都是这个套路,学会了这一个,其他页面照葫芦画瓢。
<!-- 示意:admin-web 订单管理页面模板结构 --> <template> <div class="order-list"> <el-select v-model="statusFilter" placeholder="订单状态" @change="loadOrders"> <el-option label="全部" :value="null"></el-option> <el-option label="待使用" :value="0"></el-option> <el-option label="已完成" :value="1"></el-option> <el-option label="已取消" :value="2"></el-option> </el-select> <el-table :data="orders"> <el-table-column prop="orderNo" label="订单号"></el-table-column> <el-table-column prop="areaName" label="服务区"></el-table-column> <el-table-column prop="chargerCode" label="桩号"></el-table-column> <el-table-column prop="plateNo" label="车牌号"></el-table-column> <el-table-column prop="amount" label="金额"></el-table-column> <el-table-column prop="status" label="状态"> <template slot-scope="scope"> {{ statusText(scope.row.status) }} </template> </el-table-column> <el-table-column label="操作"> <template slot-scope="scope"> <el-button v-if="scope.row.status === 0" @click="finishOrder(scope.row.id)">完成</el-button> <el-button v-if="scope.row.status === 0" @click="cancelOrder(scope.row.id)">取消</el-button> </template> </el-table-column> </el-table> </div> </template>后台的核心交互是「状态推进」:待使用订单可以被管理员标记为完成或取消。finishOrder和cancelOrder分别调用后端的/api/order/finish和/api/order/cancel,后端把订单状态改掉,顺带把充电桩状态复位成空闲——这一步至关重要,如果管理员完成订单后桩状态没复位,这个桩就永远订不出去了,属于典型的「逻辑漏一环」。
提示:源码包里
main.css.bak和update-password.vue.bak是作者改版留下的旧文件,你完全可以把当前版本和.bak版本的差异当作「设计变更记录」来看,对理解页面迭代思路很有帮助。
用户审核页面同理:loadUsers拉取status = 0的待审核用户,管理员点「通过」调/api/user/approve接口,后端把用户表status从 0 改成 1。这两条管理线(订单状态推进、用户审核)是后台管理端的核心,代码量不大,但把整个系统的闭环串起来了——没有审核,会员体系就塌了;没有订单状态推进,充电桩预订就只是「假预订」。
5. 常见问题与排查:.bak 备份文件、端口冲突与登录失败
5.1 现象:小程序里样式错乱、页面布局和文档截图不一致
原因:多半是打开项目时选错了目录,微信开发者工具把miniprogram目录当成普通文件夹导入,app.json里的页面路径没被正确加载;也可能是基础库版本过低,不兼容项目里的新组件语法。
解决:在微信开发者工具里重新「导入项目」,AppID 选择测试号,目录必须指到miniprogram那层(能看到app.js和app.json的那一层),基础库选 2.20.0 以上。如果导入后仍错乱,清掉项目重新导入,不要直接覆盖式打开,有时候缓存比代码更坑。
5.2 现象:后端启动失败,报端口被占用或数据库连不上
原因:spring-boot:run默认跑 8080 端口,如果本地已经有服务占用了 8080,Spring Boot 直接启动失败;另一个常见原因是application.properties里 MySQL 的用户名密码没有改成自己本地的,或者没提前建好数据库。
解决:改端口在application.properties里加server.port=8081;数据库连接串要改成jdbc:mysql://localhost:3306/charging?useSSL=false&serverTimezone=Asia/Shanghai,并且先执行项目里的sql文件把数据库建好。注意 MySQL 8.0 和 5.7 的驱动配置写法有差异,8.0 要在连接串里带serverTimezone,不然启动直接报时区错误。
5.3 现象:注册会员后登录提示「用户不存在」或「账号未审核」
原因:这个项目的注册流程是「先注册、后审核」,管理员没在后台通过注册申请前,账号即使写进数据库了,status还是 0,登录逻辑会拒绝这类账号登录。很多同学在测试时注册完就直接登录,发现登不进去,以为出了 bug,其实这是设计好的审核流程。
解决:先用管理员账号登录后台,在「用户管理」里找到刚注册的用户,点击审核通过,再回小程序登录。如果你是被测试数据困扰,可以直接在 MySQL 里执行UPDATE t_user SET status=1 WHERE username='你的账号';,一次到位,省得来回切页面。
5.4 现象:真机预览时请求一直失败,模拟器上却正常
原因:代码里写死的localhost:8080在模拟器里指向本机,真机上指向手机自己,自然连不上。微信开发者工具的模拟器做了特殊处理,能访问电脑本地的 localhost,真机没有这个能力。
解决:把代码里的请求域名改成电脑的局域网 IP,比如http://192.168.1.100:8080,同时手机和电脑必须在同一个 Wi-Fi;如果后端要上线,需要部署到服务器并配置 HTTPS 域名,然后在小程序后台把该域名加入 request 合法域名。注意wx.request默认不接受 HTTPS 以外的域名,开发调试阶段可以在开发者工具里勾选「不校验合法域名」,否则本地调试都过不去。
5.5 现象:后台管理页面看到订单,但小程序端列表为空
原因:后台和小程序虽然共用后端,但查询接口的权限逻辑不同——小程序端订单列表只查当前登录用户的订单,后台查全部。如果你用管理员账号的userId去小程序里查订单,结果是空的,因为管理员不是会员,没有自己的订单。
解决:测试时用注册并通过审核的会员账号登录小程序,订单就会正常显示。这个「各端各查各的」是权限设计,不是 bug,排查时别在这上面耗时间,先确认登录账号的角色再判断。
6. 二次开发指南:把预订系统做成带桩控状态的版本
这套源码的骨架很完整,但充电桩的status只有「空闲/占用」两个值,对于真实的高速公路服务区场景来说太粗了。我最建议的改造是把充电桩状态机扩展成「空闲、充电中、故障、离线」四态,并允许管理员在后台手动维护,这样演示时能讲出更多业务细节。
第一步是动表结构:t_charger的status字段加两个枚举值,把原来的 0 空闲、1 占用改成 0 空闲、1 充电中、2 故障、3 离线;订单状态的status同步细化:待使用、已到达、充电中、已完成、已取消。第二步是改后端:createOrder时校验桩状态为 0 才能下单;管理员在后台一键把桩标记为故障后,前端所有页面立即隐藏该桩,用户无法再预订。第三步是小程序端:服务区详情页的桩位图改成不同颜色——绿色空闲、橙色充电中、红色故障、灰色离线,用户一眼看懂哪个桩能订,交互体验直接上一个档次。
另一个值得做的改造是「通知能力」。现在用户提交订单后没有短信或消息提醒,到达服务区前如果桩状态变了(比如管理员把预订的桩标记为故障),用户完全不知道,到现场才发现订不了了。可以在后台接入微信订阅消息:用户提交订单时引导订阅「订单状态变更通知」,后台更改订单状态时调微信接口给用户推一条订阅消息。这块实现不难,微信开发者文档有完整的订阅消息 API,前后端半小时能跑通,但给项目的完整度加分不少。
改造完记得回归测试三件事:一、用两个账号同时订同一个桩,确认只有一个能成功,验证并发锁的逻辑没被改坏;二、管理员完成订单后确认桩状态复位为空闲,防止桩被永久锁死;三、故障桩在用户端不可见、在后台可恢复,跑通状态机的双向流转。这三条是这套系统的核心链路验证,跑通了再放心打包部署。
拿到这份源码后,我把.bak文件挨个 diff 了一遍,才真正看懂了作者迭代后台页面的思路,也意识到备份文件在课设源码里反而是理解演进过程的钥匙。从那以后我每次接手源码包,都会先找备份文件和管理脚本,再决定从哪里动手。希望你这份也能顺利跑起来,改出自己想要的样子。
本文还有配套的精品资源,点击获取