简介:本资源是一套完整的基于SSM框架的企业员工管理系统毕设项目,面向计算机相关专业本科生及Java初学者,解决毕业设计选题难、实战项目匮乏、SSM整合实践薄弱等核心问题。压缩包共3个文件(1个说明文档、1个含完整源码的ZIP包、1个MySQL建库建表SQL脚本),总大小25.07MB,涵盖可直接部署运行的前后端代码、数据库初始化脚本及详细配置说明,覆盖部门、职位、员工、考勤、薪资、意见、个人管理七大功能模块,支持管理员与员工双角色权限控制。已有1807人学习下载,项目经严格调试,适配JDK+Eclipse+Tomcat开发环境,配套SQL脚本可一键导入,源码结构清晰、注释规范,便于理解SSM分层架构、MyBatis动态SQL应用及SpringMVC请求流转机制,是掌握企业级Java Web开发流程的理想实践素材。
1. 项目概述:一个典型的SSM实战项目
如果你正在为计算机专业的毕业设计发愁,或者想找一个能串联起Java Web开发核心技术的练手项目,那么这个“基于SSM的企业员工管理系统”绝对是一个经典且实用的选择。它不是什么花哨的炫技项目,而是一个能真实反映企业日常管理需求、技术栈成熟、文档和代码相对完整的实战案例。简单来说,这就是一个用Java技术栈(Spring + Spring MVC + MyBatis)搭建的Web应用,核心功能是管理公司内部的人事信息,比如员工的入职、部门调动、薪资记录、考勤情况等。
为什么说它“经典”?因为SSM框架组合(Spring、Spring MVC、MyBatis)在过去很长一段时间里,都是国内Java后端开发的主流选择,无数企业的后台管理系统都基于此构建。通过完成这个项目,你不仅能得到一个可以写在简历里的“项目经验”,更能把学校里分散学习的Java、数据库、Web前端等知识,像拼图一样整合成一个完整的系统。对于毕设而言,它的复杂度适中——既有足够的技术点可以展开论述(如权限控制、数据分页、事务管理),又不至于过于庞大导致无法按期完成。你得到的通常是一个包含前端页面、后端Java代码和数据库SQL脚本的完整工程包,这为你省去了从零搭建框架的繁琐,可以直接聚焦于业务逻辑的理解与实现。
2. 核心需求与功能模块拆解
一个企业员工管理系统,其核心诉求是规范化和效率化人事管理流程。我们需要将线下杂乱的人事档案、审批流程搬到线上,实现信息的集中存储、快速查询和流程可控。基于这个目标,我们可以将系统拆解为以下几个核心功能模块。
2.1 员工信息管理中心
这是系统的基石。你需要设计一张或多张数据库表来存储员工的基础信息,远不止姓名、工号、性别这些。一个完整的设计通常包括:
- 基本信息表:员工ID(主键)、姓名、性别、出生日期、身份证号、联系方式、邮箱、家庭住址、入职日期、劳动合同期限等。
- 组织关系表:这部分体现了员工与公司的结构关系。关键字段包括所属部门ID、岗位/职位、职级、直接上级领导ID等。这里通常需要与“部门表”进行关联。
- 系统账户表:用于登录系统的账号、密码(必须加密存储)、账户状态(启用/禁用)。这张表与员工信息表通过员工ID关联,实现了业务信息与权限信息的分离,是一种更清晰的设计。
在实现上,后端需要提供对员工信息的增删改查全套接口。特别是“查”,要支持多条件组合查询,例如“查询研发部所有职级为P7的员工”,这涉及到动态SQL的编写。前端则需提供清晰的数据表格和表单页面。
实操心得:在设计员工编号时,不要用数据库自增ID直接暴露给用户。最好设计一套有意义的规则,如“DEP202405001”(部门缩写+年份+序号),这样在日常交流和管理中更直观。自增ID仅在数据库内部作为主键使用。
2.2 部门与组织架构管理
公司的组织架构是树形结构。这个模块需要管理部门的创建、调整、合并甚至撤销。
- 核心表设计:部门表至少包含部门ID、部门名称、部门编码、部门负责人ID、上级部门ID、成立时间、部门描述等字段。其中“上级部门ID”是实现树形结构的关键,通过它可以用递归或一次查询(如使用
<collection>标签)来获取完整的部门层级。 - 功能实现:除了基本的增删改查,一个重要的功能是部门树形列表的展示。前端可以使用现成的树形组件(如zTree、Element UI的Tree),后端需要提供一个接口,返回格式化的嵌套JSON数据。当员工调动部门时,这个模块的数据就是源和目标的依据。
2.3 考勤与薪资核算模块
这是直接关系到员工利益的核心模块,对数据的准确性和安全性要求极高。
- 考勤管理:记录员工的每日上下班打卡时间(可与考勤机接口对接,或手动登记)。需要计算迟到、早退、旷工、加班等情况。涉及的表可能有打卡记录表、请假申请表、加班申请表等。业务逻辑复杂在各类考勤规则的核算上,比如如何计算调休、如何关联请假流程。
- 薪资管理:这是最复杂的部分之一。薪资通常由基本工资、绩效奖金、各类补贴(餐补、交通补)、扣款(社保、公积金、个税、考勤扣款)等组成。需要设计薪资项目表、薪资核算记录表。每月核算薪资时,系统需要根据考勤数据、绩效数据、社保缴费基数等,自动计算应发和实发金额。务必注意:所有薪资相关的计算和记录,都必须有严格的权限控制和操作日志,且数据不允许随意修改,通常只允许财务特定角色操作。
2.4 系统权限与角色管理
任何管理系统没有权限控制就等于没有大门。这是一个通用但至关重要的模块。
- RBAC模型:最常用的是基于角色的访问控制模型。设计五张核心表:用户表、角色表、权限表(或菜单表)、用户-角色关联表、角色-权限关联表。
- 实现思路:用户登录后,后端根据其角色查询出所有权限标识(如
employee:add,salary:view)。在访问每一个接口或加载前端菜单时,进行权限校验。前端可以根据权限动态渲染菜单和按钮。Spring Security或Shiro是实现此功能的流行框架,但在SSM项目中,也常通过自定义拦截器(Interceptor)配合注解来实现,这样更轻量、更贴合项目。
3. 技术栈选型与SSM框架整合解析
为什么是SSM?因为它提供了一个分层清晰、耦合度低、易于维护的解决方案。下面我们拆解每个组件的作用和整合要点。
3.1 Spring:项目的“大管家”
Spring的核心是IoC(控制反转)和AOP(面向切面编程)。
- IoC容器:它负责创建和管理所有Java对象(Bean),比如Service、DAO、控制器等。你不再需要用
new关键字来创建它们,而是通过配置文件或注解声明,Spring会在需要时“注入”给你。这降低了组件间的依赖。 - 在项目中的应用:我们通过
applicationContext.xml配置文件(或Java Config配置类),定义数据源DataSource、事务管理器TransactionManager、MyBatis的SqlSessionFactoryBean等核心Bean。业务逻辑层(Service)依赖数据访问层(DAO),这种依赖关系通过@Autowired注解自动注入,非常简洁。 - AOP的应用:这是实现声明式事务管理的关键。我们不需要在每个数据库操作方法里手动写
begin transaction和commit。只需在Service层的方法上添加@Transactional注解,Spring就能在方法执行前开启事务,执行后提交或回滚。这同样是通过配置文件(<tx:annotation-driven/>)启用的。
3.2 Spring MVC:请求的“调度中心”
它负责处理前端发来的HTTP请求,是模型(Model)、视图(View)、控制器(Controller)之间的桥梁。
- 工作流程:
- 用户请求到达
DispatcherServlet(前端控制器,在web.xml中配置)。 DispatcherServlet查询HandlerMapping,找到处理该请求的控制器(Controller)。- 控制器调用相应的Service方法处理业务逻辑。
- 控制器将处理结果(Model)和要渲染的视图名(View Name)返回给
DispatcherServlet。 DispatcherServlet查询ViewResolver,找到具体的视图(如JSP、Thymeleaf模板)。- 视图渲染Model数据,生成最终的HTML响应给浏览器。
- 用户请求到达
- 在项目中的体现:你会编写许多带有
@Controller注解的类,其中的方法使用@RequestMapping来映射URL。方法参数可以灵活绑定请求参数(@RequestParam)、路径变量(@PathVariable)、JSON请求体(@RequestBody)。返回结果可以是视图名,也可以是直接序列化为JSON的@ResponseBody。
3.3 MyBatis:数据的“搬运工”
它是一个优秀的持久层框架,封装了JDBC的繁琐操作,让你能更专注于SQL本身。
- 核心优势:与数据库表结构高度解耦。你通过XML映射文件或注解,将Java对象(POJO)与SQL语句动态地关联起来。它提供了强大的动态SQL功能,能轻松应对多条件查询。
- 在项目中的整合:
- 在Spring配置中定义
SqlSessionFactoryBean,指定数据源和MyBatis全局配置文件mybatis-config.xml的位置。 - 在全局配置文件中,可以设置别名、插件(如分页插件PageHelper)。
- 为每个实体类(如
Employee)编写Mapper接口(如EmployeeMapper),并在对应的XML文件(如EmployeeMapper.xml)中编写SQL。例如:<!-- EmployeeMapper.xml --> <select id="selectByCondition" resultType="Employee"> SELECT * FROM employee <where> <if test="deptId != null"> AND dept_id = #{deptId} </if> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> </where> ORDER BY entry_date DESC </select> - 在Service中,通过
@Autowired注入Mapper接口的实例,即可调用其方法执行SQL。
- 在Spring配置中定义
3.4 前端与其它辅助技术
一个完整的项目离不开前端。
- 视图技术:早期SSM项目多使用JSP。现在更流行前后端分离,后端只提供RESTful API,前端使用独立的Vue.js、React等框架开发。对于毕设,如果时间紧,也可以使用模板引擎如Thymeleaf,它语法自然,能与Spring MVC完美集成。
- 关键工具与依赖:
- Maven/Gradle:项目管理工具,用于声明项目依赖(如Spring、MyBatis、MySQL驱动、JUnit)、统一构建。
- PageHelper:MyBatis分页插件,一句代码就能实现物理分页,极大简化开发。
- Lombok:通过注解自动生成Getter/Setter、构造方法等,让POJO类非常简洁。
- Logback/SLF4J:日志框架,替代
System.out.println,方便记录和排查问题。 - JUnit:单元测试框架,保证核心业务逻辑的正确性。
4. 数据库设计与脚本解析
数据库是系统的“记忆中枢”,设计好坏直接决定系统的性能和扩展性。拿到数据库脚本(.sql文件)后,不要急着运行,先花时间读懂它。
4.1 核心表结构设计思路
我们以最核心的几张表为例,分析设计考量:
| 表名 | 核心字段示例 | 设计要点与说明 |
|---|---|---|
employee(员工表) | id(主键),emp_no(员工号),name,gender,dept_id,position,entry_date | 1.id是自增主键,用于内部关联,效率高。2. emp_no是业务唯一编号,有规则,可暴露给用户。3. dept_id是外键,关联department表。4. 对 name,emp_no等查询频繁的字段可考虑加索引。 |
department(部门表) | id,dept_no,name,parent_id,manager_id,order_num | 1.parent_id实现无限级树形结构,存储上级部门ID,根部门可为0或NULL。2. manager_id关联employee.id,表示部门负责人。3. order_num用于同级部门排序显示。 |
user(系统用户表) | id,username,password,employee_id,status,create_time | 1.password字段必须使用强哈希算法(如BCrypt)加密存储,严禁明文。2. employee_id关联employee.id,实现业务员与系统账户一对一绑定。3. status控制账户启用/禁用。 |
salary_record(薪资记录表) | id,employee_id,year_month,base_salary,bonus,deduction,net_salary,status | 1.year_month格式如‘2024-05’,表示薪资所属月份。2. 各项金额字段使用 DECIMAL类型,确保计算精度。3. status标识“已生成”、“已审核”、“已发放”等状态,驱动薪资流程。 |
4.2 SQL脚本中的关键操作
一个完整的数据库脚本通常包含以下部分:
- 删除与创建数据库:
DROP DATABASE IF EXISTSemployee_ms; CREATE DATABASEemployee_msDEFAULT CHARSET utf8mb4;使用utf8mb4字符集以支持所有Emoji表情。 - 建表语句:如上所示,包含字段定义、主键、外键约束。外键约束能保证数据完整性,但在高并发写入场景或分库分表时,有时会在应用层保证一致性,而不使用数据库外键。
- 初始数据插入:
INSERT INTO ...语句,用于插入超级管理员账号、基础部门信息等系统运行必需的数据。 - 索引创建:在表创建后,通过
CREATE INDEX语句为高频查询条件(如employee.dept_id,salary_record.employee_id, year_month)创建索引,大幅提升查询速度。
注意事项:运行脚本前,务必在本地或测试环境操作。检查脚本中是否有删除现有数据的危险操作。对于薪资等敏感数据,脚本中通常不会包含真实数据,你需要自己编写或生成测试数据。
5. 项目搭建与核心功能实现步骤
假设你已拿到源码,我们以IDEA为开发环境,梳理从零到一运行起这个系统的关键步骤。
5.1 开发环境准备与项目导入
- 基础环境:确保本地已安装JDK 8或11、Maven 3.6+、MySQL 5.7+(或8.0)。建议使用Tomcat 9作为Servlet容器。
- 导入项目:在IDEA中,选择“Open”或“Import Project”,找到项目根目录(包含
pom.xml的文件夹)。IDEA会自动识别为Maven项目并开始下载依赖。 - 数据库初始化:打开MySQL客户端(如命令行、Navicat),执行提供的
database.sql脚本,创建数据库和表结构。 - 配置修改:这是最关键的一步。找到项目中的配置文件,通常是
src/main/resources目录下的:jdbc.properties: 修改里面的数据库连接URL、用户名和密码,确保与你的本地MySQL环境一致。applicationContext.xml: 检查数据源配置是否正确引用了上面的jdbc属性。logback.xml: 可以调整日志输出级别和路径。
5.2 员工列表查询与分页功能实现
这是最常见的功能,我们以此为例,看后端到前端的完整链路。
- DAO层(Mapper):在
EmployeeMapper.java接口中定义方法:List<Employee> selectByPage(@Param("emp") Employee employee, @Param("pageNum") int pageNum, @Param("pageSize") int pageSize);。对应的XML文件中编写动态SQL,使用<if>标签判断查询条件。 - Service层:在
EmployeeServiceImpl中注入EmployeeMapper。这里引入PageHelper插件,它通过拦截器实现分页。代码非常简单:@Override public PageInfo<Employee> getEmployeePage(int pageNum, int pageSize, Employee queryCondition) { // 关键:这行代码后面的第一个MyBatis查询方法会被分页 PageHelper.startPage(pageNum, pageSize); List<Employee> list = employeeMapper.selectByCondition(queryCondition); // PageInfo包含了分页的所有信息(数据列表、总页数、当前页等) return new PageInfo<>(list); } - Controller层:接收前端传递的页码、每页条数及查询条件表单数据。
@RequestMapping("/list") @ResponseBody // 返回JSON public Result getList(@RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="10") int limit, Employee employee) { // 条件对象 PageInfo<Employee> pageInfo = employeeService.getEmployeePage(page, limit, employee); // 将PageInfo封装成前端表格插件(如Layui, Bootstrap Table)需要的格式 return Result.success().data("data", pageInfo.getList()).count(pageInfo.getTotal()); } - 前端页面:使用jQuery或Vue发起Ajax请求,将用户输入的查询条件和分页参数传到后端,接收JSON数据后渲染到表格(如使用DataTables或Layui Table)。表格底部通常有分页组件,点击页码会触发新的请求。
5.3 员工信息新增与编辑功能
涉及表单提交和数据持久化,重点在于数据校验和事务。
- 前端表单:构建一个包含输入框、下拉框(用于选择部门)的表单。部门下拉框的数据需要先通过另一个接口从后端获取。使用JavaScript进行非空、格式(如邮箱、身份证号)的前端校验,提升体验。
- 后端Controller:定义两个方法处理
/save(POST)和/update(PUT)请求。使用@RequestBody接收JSON格式的员工对象。 - Service层与事务:在Service方法上添加
@Transactional注解。新增员工时,业务逻辑可能不止插入一张表(如同时初始化系统账号),事务确保这些操作要么全成功,要么全失败回滚。@Transactional(rollbackFor = Exception.class) // 发生任何异常都回滚 public boolean addEmployee(Employee emp) { // 1. 基础信息校验(如工号是否重复) // 2. 插入员工表 int count = employeeMapper.insert(emp); // 3. 同步创建系统账号(可能需要用到刚生成的员工ID) User user = new User(); user.setUsername(emp.getEmpNo()); // 初始账号可能用工号 user.setPassword(passwordEncoder.encode("初始密码")); // 密码加密 user.setEmployeeId(emp.getId()); userMapper.insert(user); return count > 0; } - 数据校验:除了前端校验,后端必须进行重复校验(如身份证号、手机号是否已存在)和业务规则校验(如入职日期不能晚于今天)。可以使用Spring Validation框架配合注解(
@NotNull,@Pattern)进行声明式校验。
6. 常见问题排查与调试技巧实录
在实际开发和部署中,你一定会遇到各种问题。这里记录几个典型场景和解决思路。
6.1 数据库连接失败
- 错误现象:启动Tomcat时,控制台报错
Communications link failure或Access denied for user。 - 排查步骤:
- 检查配置文件:逐字核对
jdbc.properties中的url、username、password。注意url中的端口号(默认3306)和数据库名是否正确。 - 检查MySQL服务:确保MySQL服务已启动。可以在命令行尝试连接:
mysql -u用户名 -p密码。 - 检查权限:确认你使用的数据库用户是否有权从本地(
localhost)或指定IP访问目标数据库。有时需要执行GRANT ALL PRIVILEGES ON employee_ms.* TO 'username'@'localhost'; FLUSH PRIVILEGES;。 - 检查驱动版本:MySQL 8.0需要
mysql-connector-java版本在8.0以上,且url中需要指定时区参数,如?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8。
- 检查配置文件:逐字核对
6.2 页面显示乱码
- 问题描述:前端提交中文数据到后台,数据库存储变成问号“??”,或者从数据库查询出的中文显示为乱码。
- 解决方案(三处统一):
- 数据库层面:建库建表时使用
utf8mb4字符集和utf8mb4_general_ci排序规则。 - 连接层面:在JDBC连接URL中明确指定字符集:
jdbc:mysql://localhost:3306/employee_ms?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai。 - Web容器层面:在
web.xml中配置字符编码过滤器CharacterEncodingFilter,并设置为UTF-8。这是解决POST请求乱码的关键。 - 前端页面:确保JSP或HTML页面的
<meta charset="UTF-8">已设置。
- 数据库层面:建库建表时使用
6.3 MyBatis查询结果映射失败
- 错误现象:查询返回的
List不为空,但里面对象的属性全是null;或者直接报错Could not set property 'xxx'。 - 排查思路:
- 检查“蛇形命名”与“驼峰命名”转换:数据库字段名是
dept_id,Java实体类属性名是deptId。确保在MyBatis全局配置中开启了自动映射:<setting name="mapUnderscoreToCamelCase" value="true"/>。或者在SQL中使用AS显式指定别名:SELECT dept_id AS deptId FROM ...。 - 核对XML中的
resultMap或resultType:resultType指定的类路径必须正确。如果使用resultMap,检查<result>标签的property和column是否一一对应。 - 使用日志查看实际执行的SQL:在
logback.xml中将mapper接口所在包的日志级别设为DEBUG。这样可以在控制台看到MyBatis打印出的完整SQL语句和参数,直接复制到数据库客户端执行,看结果是否正确。
- 检查“蛇形命名”与“驼峰命名”转换:数据库字段名是
6.4 事务未生效
- 问题描述:在Service方法中抛出了异常,但之前执行的数据库插入操作并没有回滚。
- 常见原因:
- 异常类型未被捕获:Spring默认只在抛出运行时异常(
RuntimeException)和Error时回滚。如果你抛出的Exception,需要在@Transactional注解中指定rollbackFor = Exception.class。 - 方法访问权限问题:
@Transactional是基于AOP代理实现的。如果事务方法被定义在类内部,并且被同一个类的其他非事务方法调用,那么事务可能不会生效。因为内部调用绕过了代理对象。最佳实践是将事务方法放在单独的Service类中。 - 数据库引擎不支持事务:确保MySQL表使用的存储引擎是InnoDB,而不是MyISAM。可以通过
SHOW CREATE TABLE employee;命令查看。
- 异常类型未被捕获:Spring默认只在抛出运行时异常(
6.5 前端页面资源404
- 问题描述:能访问JSP页面,但页面引用的CSS、JS、图片等静态资源加载失败。
- 解决方案:Spring MVC的
DispatcherServlet通常会拦截所有请求(/)。需要在Spring配置文件中(springmvc.xml)添加静态资源映射,告诉Spring哪些路径的请求应该直接交给容器处理,而不是找控制器。
同时,确保你的静态资源文件放在了Web应用的根目录(如<mvc:resources mapping="/static/**" location="/static/" /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/js/**" location="/js/" />/webapp/static/)下,并且前端页面引用路径正确,例如<script src="${pageContext.request.contextPath}/js/main.js"></script>。
7. 从项目源码到毕业设计论文的转化
拥有一个可运行的项目只是第一步,如何将其转化为一份合格的毕业设计文档,才是体现你综合能力的关键。
7.1 论文结构组织建议
你的论文不应是代码的堆砌,而应是以项目为载体,展示你分析、设计、实现和总结的能力。一个经典的结构如下:
- 绪论:阐述企业人事管理数字化的背景和意义,分析现有手工或旧系统的痛点,引出开发本系统的必要性和研究目标。
- 相关技术介绍:系统性地介绍SSM框架(Spring、Spring MVC、MyBatis)、MySQL、前端技术(如jQuery、Bootstrap)等。重点不是罗列概念,而是说明为什么选择它们,比如Spring的IoC如何解耦、MyBatis相比Hibernate在SQL灵活性上的优势。
- 系统分析:包括可行性分析(技术、经济、操作可行性)和需求分析。需求分析部分,可以画出系统的用例图,清晰地展示不同角色(管理员、普通员工、部门经理)能做什么。
- 系统设计:这是论文的核心章节。
- 总体设计:绘制系统架构图(展示前端、Web服务器、应用服务器、数据库的层次关系)、功能模块图。
- 数据库设计:给出详细的E-R图,并逐一说明核心表的设计思路和字段含义(可以参考第4.1节的表格形式)。这是老师重点看的部分。
- 详细设计:选择2-3个核心功能模块(如员工信息管理、薪资核算),绘制其业务流程图或时序图,并配合文字说明关键的业务逻辑。
- 系统实现与测试:展示关键功能的实现界面(截图),并附上核心代码片段及解释。例如,展示带分页和条件查询的员工列表后端接口实现,并解释PageHelper的原理。测试部分要设计测试用例(包括正常和异常情况),并展示测试结果。
- 总结与展望:回顾整个开发过程,总结收获、遇到的难点及解决方案。客观分析系统的不足(如未实现移动端适配、性能优化不足等),并提出未来可改进的方向。
7.2 如何让项目与论文脱颖而出
- 深入一个技术点:不要只停留在“用了SSM”。可以深入探讨某一个点,比如:
- 安全性:你是如何实现密码加密(BCrypt)的?如何防止SQL注入(MyBatis的
#{}已解决)和XSS攻击? - 性能:是否引入了Redis缓存高频查询的部门信息?数据库查询慢时,你是如何通过
EXPLAIN分析SQL并优化索引的? - 扩展性:系统权限设计是否足够灵活,能轻松应对新增角色和权限?
- 安全性:你是如何实现密码加密(BCrypt)的?如何防止SQL注入(MyBatis的
- 体现工程化思想:在论文中提及你如何使用Git进行版本控制,如何编写清晰的代码注释和API文档,如何进行单元测试(JUnit),这能体现你的工程素养。
- 关注用户体验:在前端实现一些细节优化,比如表单的异步验证、操作成功/失败的友好提示、数据的导出功能(导出Excel)等,并在论文中加以说明,这能展示你的产品思维。
最后,确保你的项目在你的电脑上能一键启动,数据库脚本干净,代码结构清晰。答辩时,熟练地演示系统,并对老师可能提出的技术问题(如“Spring MVC的工作流程是怎样的?”“你的数据库事务是如何管理的?”)做好充分准备。这个项目本身是一个很好的学习载体和展示平台,吃透它,你的毕业设计就成功了一大半。
本文还有配套的精品资源,点击获取