这是一个基于SpringBoot和Vue.js的企业级管理系统实战项目,主要实现了部门管理、员工管理、班级管理、学生管理、数据统计和登录认证六大功能模块。系统采用前后端分离架构,后端使用SpringBoot+MyBatis技术栈,前端采用Vue.js+ElementUI框架,是一个典型的企业级应用开发案例。
我在实际开发过程中发现,这类管理系统有几个关键点需要特别注意:首先是前后端数据交互的规范性和一致性,其次是权限控制的实现方式,最后是复杂查询的性能优化。这个项目完整实现了CRUD操作、分页查询、文件上传等企业开发中的常见需求,特别适合想要学习前后端分离开发的开发者参考。
后端技术栈:
前端技术栈:
选择这些技术栈主要基于以下考虑:
系统包含5张核心表:
sql复制-- 部门表
CREATE TABLE dept(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
name VARCHAR(10) NOT NULL UNIQUE COMMENT '部门名称',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME NOT NULL COMMENT '修改时间'
) COMMENT '部门表';
-- 员工表
CREATE TABLE emp (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(32) DEFAULT '123456' COMMENT '密码',
name VARCHAR(10) NOT NULL COMMENT '姓名',
gender TINYINT UNSIGNED NOT NULL COMMENT '性别, 说明: 1 男, 2 女',
image VARCHAR(300) COMMENT '图像',
job TINYINT UNSIGNED COMMENT '职位',
entrydate DATE COMMENT '入职时间',
dept_id INT UNSIGNED COMMENT '部门ID',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME NOT NULL COMMENT '修改时间'
) COMMENT '员工表';
-- 班级表
CREATE TABLE clazz (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
name VARCHAR(30) NOT NULL UNIQUE COMMENT '班级名称',
room VARCHAR(20) DEFAULT NULL COMMENT '班级教室',
begin_date DATE NOT NULL COMMENT '开课时间',
end_date DATE NOT NULL COMMENT '结课时间',
master_id INT UNSIGNED NOT NULL COMMENT '班主任ID',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME NOT NULL COMMENT '修改时间'
) COMMENT '班级表';
-- 学生表
CREATE TABLE student (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
name VARCHAR(10) NOT NULL COMMENT '姓名',
no CHAR(10) NOT NULL UNIQUE COMMENT '学号',
gender TINYINT UNSIGNED NOT NULL COMMENT '性别',
phone VARCHAR(11) NOT NULL UNIQUE COMMENT '手机号',
degree TINYINT UNSIGNED DEFAULT NULL COMMENT '最高学历',
violation_count TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '违纪次数',
violation_score TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '违纪扣分',
clazz_id INT UNSIGNED NOT NULL COMMENT '班级ID',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME NOT NULL COMMENT '修改时间'
) COMMENT '学生表';
数据库设计遵循了以下原则:
pom.xml关键依赖:
xml复制<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
application.yml配置:
yaml复制server:
port: 8080
mybatis:
mapper-locations: classpath:mappers/*xml
type-aliases-package: com.tlias.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tlias
username: root
password: 123456
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
pagehelper:
reasonable: true
项目结构采用标准的Maven多模块结构:
code复制src/main/java
├── com.tlias
│ ├── config # 配置类
│ ├── controller # 控制器
│ ├── entity # 实体类
│ ├── mapper # Mapper接口
│ ├── service # 服务层
│ └── WebTliasApplication.java # 启动类
src/main/resources
├── static
├── templates
└── application.yml
使用Vue CLI创建项目:
bash复制vue create web-tlias
添加ElementUI:
bash复制vue add element
项目目录结构:
code复制src/
├── api/ # 接口定义
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── utils/ # 工具类
├── views/ # 页面组件
└── App.vue # 根组件
实体类:
java复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
private Integer id;
private String name;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
Controller层:
java复制@RestController
@RequestMapping("depts")
@Slf4j
public class DeptController {
@Autowired
private DeptService service;
@GetMapping
public Result getDept(){
List<Dept> list = service.getDeptList();
return Result.success(list);
}
@DeleteMapping("/{id}")
public Result deleteDeptById(@PathVariable("id") Integer id){
service.deleteDeptById(id);
return Result.success();
}
@PostMapping
public Result save(@RequestBody Dept dept){
service.save(dept);
return Result.success();
}
@GetMapping("/{id}")
public Result selectDeptById(@PathVariable("id") Integer id){
Dept dept = service.selectDeptById(id);
return Result.success(dept);
}
@PutMapping
public Result update(@RequestBody Dept dept){
service.update(dept);
return Result.success();
}
}
Service层:
java复制@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Override
public List<Dept> getDeptList() {
return deptMapper.getDeptList();
}
@Override
public void deleteDeptById(Integer id) {
// 检查部门下是否有员工
Integer count = empMapper.selectEmpByDeptId(id);
if (count > 0) {
throw new RuntimeException("不能删除部门,部门下面存在员工");
}
deptMapper.deleteDeptById(id);
}
@Override
public void save(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.save(dept);
}
@Override
public Dept selectDeptById(Integer id) {
return deptMapper.selectDeptById(id);
}
@Override
public void update(Dept dept) {
dept.setUpdateTime(LocalDateTime.now());
deptMapper.update(dept);
}
}
Mapper层:
java复制@Mapper
public interface DeptMapper {
@Select("select * from dept")
List<Dept> getDeptList();
@Delete("delete from dept where id = #{id}")
void deleteDeptById(Integer id);
@Insert("insert into dept values (null,#{name},#{createTime},#{updateTime})")
void save(Dept dept);
@Select("select * from dept where id = #{id}")
Dept selectDeptById(Integer id);
@Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
}
部门列表页面:
vue复制<template>
<div style="margin: 50px; margin-right: 100px">
<el-row>
<el-button type="primary" @click="dialogFormVisible = true; dept={}">
+ 新增部门
</el-button>
</el-row>
<el-table :data="tableData" border>
<el-table-column type="index" width="100" label="序号" align="center"/>
<el-table-column prop="name" label="部门名称" align="center"/>
<el-table-column label="最后操作时间" align="center">
<template slot-scope="scope">
{{scope.row.updateTime ? scope.row.updateTime.replace('T',' '):''}}
</template>
</el-table-column>
<el-table-column label="操作" width="420" align="center">
<template slot-scope="scope">
<el-button type="primary" plain @click="selectById(scope.row.id)">编辑</el-button>
<el-button type="danger" plain @click="deleteById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="保存部门" :visible.sync="dialogFormVisible">
<el-form :model="dept" :rules="rules" ref="dept">
<el-form-item label="部门名称" prop="name">
<el-input v-model="dept.name" placeholder="请输入部门名称"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel('dept')">取消</el-button>
<el-button type="primary" @click="save('dept')">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { findAll, add, update, deleteById, selectById } from "@/api/dept.js";
export default {
data() {
return {
dialogFormVisible: false,
dept: { name: "" },
tableData: [],
rules: {
name: [
{ required: true, message: '请输入部门名称', trigger: 'blur' },
{ min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
]
}
};
},
methods: {
deleteById(id) {
this.$confirm("确认删除?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
deleteById(id).then((result) => {
if(result.data.code == 1){
this.$message.success("删除成功");
this.init();
}else{
this.$message.error(result.data.msg);
}
});
});
},
selectById(id) {
this.dialogFormVisible = true;
selectById(id).then((result) => {
this.dept = result.data.data;
});
},
save(formName) {
this.$refs[formName].validate((valid) => {
if(valid){
let operator = this.dept.id ? update(this.dept) : add(this.dept);
operator.then((result) => {
if (result.data.code == 1) {
this.$message.success("保存成功");
this.init();
this.dialogFormVisible = false;
this.dept = {};
} else {
this.$message.error(result.data.msg);
}
});
}
})
},
init() {
findAll().then((result) => {
if (result.data.code == 1) {
this.tableData = result.data.data;
}
});
},
cancel(formName){
this.dialogFormVisible = false;
this.$refs[formName].resetFields();
}
},
mounted() {
this.init();
},
};
</script>
实体类:
java复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer id;
private String username;
private String password;
private String name;
private Integer gender;
private String image;
private Integer job;
private LocalDate entrydate;
private Integer deptId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
Controller层:
java复制@RestController
@RequestMapping("/emps")
@Slf4j
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
String name, Integer gender,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
PageInfo<Emp> pageInfo = empService.page(page, pageSize, name, gender, begin, end);
return Result.success(pageInfo);
}
@DeleteMapping("/{ids}")
public Result delete(@PathVariable List<Integer> ids) {
empService.delete(ids);
return Result.success();
}
@PostMapping
public Result save(@RequestBody Emp emp) {
empService.save(emp);
return Result.success();
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
Emp emp = empService.getById(id);
return Result.success(emp);
}
@PutMapping
public Result update(@RequestBody Emp emp) {
empService.update(emp);
return Result.success();
}
}
Service层:
java复制@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public PageInfo<Emp> page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end) {
PageHelper.startPage(page, pageSize);
List<Emp> list = empMapper.list(name, gender, begin, end);
return new PageInfo<>(list);
}
@Override
public void delete(List<Integer> ids) {
empMapper.deleteByIds(ids);
}
@Override
public void save(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
empMapper.insert(emp);
}
@Override
public Emp getById(Integer id) {
return empMapper.getById(id);
}
@Override
public void update(Emp emp) {
emp.setUpdateTime(LocalDateTime.now());
empMapper.update(emp);
}
}
员工列表页面:
vue复制<template>
<div class="app-container">
<el-form :inline="true" :model="searchEmp" class="demo-form-inline">
<el-form-item label="姓名">
<el-input v-model="searchEmp.name" placeholder="请输入员工姓名"/>
</el-form-item>
<el-form-item label="性别">
<el-select v-model="searchEmp.gender" placeholder="请选择">
<el-option label="男" value="1"/>
<el-option label="女" value="2"/>
</el-select>
</el-form-item>
<el-form-item label="入职时间">
<el-date-picker
v-model="entrydate"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 400px; margin-left: 20px"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button type="info" @click="clear">清空</el-button>
</el-form-item>
</el-form>
<el-row>
<el-button type="danger" @click="deleteByIds">- 批量删除</el-button>
<el-button type="primary" @click="dialogVisible = true; emp = { image: ''}">+ 新增员工</el-button>
</el-row>
<el-dialog title="编辑员工" :visible.sync="dialogVisible" width="30%">
<el-form :model="emp" :rules="rules" ref="emp" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="emp.username"/>
</el-form-item>
<el-form-item label="员工姓名" prop="name">
<el-input v-model="emp.name"/>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="emp.gender" style="width:100%">
<el-option v-for="item in genderList" :key="item.value"
:label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="头像">
<el-upload
class="avatar-uploader"
action="/api/upload"
:headers="token"
name="image"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="emp.image" :src="emp.image" class="avatar"/>
<i v-else class="el-icon-plus avatar-uploader-icon"/>
</el-upload>
</el-form-item>
<el-form-item label="职位">
<el-select v-model="emp.job" style="width:100%">
<el-option v-for="item in jobList" :key="item.value"
:label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="入职日期">
<el-date-picker
v-model="emp.entrydate"
type="date"
value-format="yyyy-MM-dd"
style="width:100%"/>
</el-form-item>
<el-form-item label="归属部门">
<el-select v-model="emp.deptId" style="width:100%">
<el-option v-for="item in deptList" :key="item.value"
:label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save('emp')">提交</el-button>
<el-button @click="cancel('emp')">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
<el-table :data="tableData" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column prop="name" label="姓名" align="center"/>
<el-table-column prop="image" label="头像" align="center">
<template slot-scope="{ row }">
<el-image style="width: auto; height: 40px" :src="row.image"/>
</template>
</el-table-column>
<el-table-column label="性别" align="center">
<template slot-scope="scope">
{{scope.row.gender == "1" ? "男" : "女"}}
</template>
</el-table-column>
<el-table-column label="职位" align="center">
<template slot-scope="scope">
<span v-if="scope.row.job == 1">班主任</span>
<span v-if="scope.row.job == 2">讲师</span>
<span v-if="scope.row.job == 3">学工主管</span>
<span v-if="scope.row.job == 4">教研主管</span>
</template>
</el-table-column>
<el-table-column label="入职日期" align="center">
<template slot-scope="scope">{{ scope.row.entrydate }}</template>
</el-table-column>
<el-table-column label="最后操作时间" align="center">
<template slot-scope="scope">
{{scope.row.updateTime ? scope.row.updateTime.replace('T',' '):''}}
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" @click="update(scope.row.id)">编辑</el-button>
<el-button type="danger" @click="deleteById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15, 20]"
:page-size="5"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount">
</el-pagination>
</div>
</template>
<script>
import { page, add, update, deleteById, selectById } from "@/api/emp.js";
import { findAll } from "@/api/dept.js";
import { getToken } from '@/utils/auth';
export default {
data() {
return {
pageSize: 5,
totalCount: 0,
currentPage: 1,
dialogVisible: false,
searchEmp: { name: "", gender: "" },
emp: { username: "", name: "", gender: "", image: "", job: "", entrydate: "", deptId: "" },
deptList: [],
genderList: [{id: 1,name: "男"},{id: 2,name: "女"}],
jobList: [{id: 1,name: "班主任"},{id: 2,name: "讲师"},{id: 3, name: "学工主管"},{id: 4,name: "教研主管"}],
entrydate: "",
selectedIds: [],
tableData: [],
token: {token: getToken()},
rules: {
username: [
{required: true, message: '请输入用户名', trigger: 'blur' },
{min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
name: [
{required: true, message: '请输入姓名', trigger: 'blur' },
{min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
],
gender: [
{required: true, message: '请选择性别', trigger: 'change' }
]
}
};
},
mounted() {
this.page();
findAll().then((result) => {
this.deptList = result.data.data;
});
},
methods: {
page() {
page(
this.searchEmp.name,
this.searchEmp.gender,
this.beginTime,
this.endTime,
this.currentPage,
this.pageSize
).then((res) => {
this.totalCount = res.data.data.total;
this.tableData = res.data.data.rows;
});
},
handleSelectionChange(val) {
this.selectedIds = val.map(item => item.id);
},
onSubmit() {
this.currentPage = 1;
this.page();
},
clear(){
this.searchEmp = {name: "", gender: ""};
this.entrydate = "";
this.page();
},
deleteByIds() {
if(this.selectedIds.length === 0) {
this.$message.warning("请选择要删除的员工");
return;
}
this.$confirm("确认删除选中的员工?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
deleteById(this.selectedIds).then((res) => {
if(res.data.code === 1) {
this.$message.success("删除成功");
this.page();
}
});
});
},
update(id) {
this.dialogVisible = true;
selectById(id).then((res) => {
this.emp = res.data.data;
});
},
deleteById(id) {
this.$confirm("确认删除该员工?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
deleteById([id]).then((res) => {
if(res.data.code === 1) {
this.$message.success("删除成功");
this.page();
}
});
});
},
save(formName) {
this.$refs[formName].validate((valid) => {
if(valid) {
const operator = this.emp.id ? update(this.emp) : add(this.emp);
operator.then((res) => {
if(res.data.code === 1) {
this.$message.success("保存成功");
this.dialogVisible = false;
this.page();
}
});
}
});
},
cancel(formName) {
this.dialogVisible = false;
this.$refs[formName].resetFields();
},
handleSizeChange(val) {
this.pageSize = val;
this.page();
},
handleCurrentChange(val) {
this.currentPage = val;
this.page();
},
handleAvatarSuccess(res) {
this.emp.image = res.data;
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isPNG = file.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG && !isPNG) {
this.$message.error('上传头像图片只能是 JPG/PNG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return (isJPG || isPNG) && isLt2M;
}
},
computed: {
beginTime() {
return this.entrydate ? this.entrydate[0] : "";
},
endTime() {
return this.entrydate ? this.entrydate[1] : "";
}
}
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
JWT工具类:
java复制public class JwtUtils {
private static String signKey = "itheima"; //签名密钥
private static Long expire = 43200000L; //有效时间(12小时)
/**
* 生成JWT令牌
*/
public static String generateJwt(Map<String, Object> claims){
return Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
}
/**
* 解析JWT令牌
*/
public static Claims parseJWT(String jwt){
return Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
}
}
登录接口:
java复制@RestController
@RequestMapping("/login")
@Slf4j
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping
public Result login(@RequestBody Emp emp) {
log.info("员工登录: {}", emp);
Emp e = empService.login(emp);
//登录成功后生成令牌
Map<String, Object> claims = new HashMap<>();
claims.put("id", e.getId());
claims.put("name", e.getName());
claims.put("username", e.getUsername());
String token = JwtUtils.generateJwt(claims);
return Result.success(token);
}
}
java复制@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取请求url
String url = request.getRequestURL().toString();
//2.判断请求url是否包含login,如果包含说明是登录操作,放行
if(url.contains("login")){
return true;
}
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
//4.判断令牌是否存在,如果不存在返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
return false;
}
//5.解析token,如果解析失败返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
return false;
}
//6.放行
return true;
}
}
登录页面:
vue复制<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">管理系统</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="账号"/>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="密码"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin" style="width:100%">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { login } from '@/api/user'
import { setToken } from '@/utils/auth'
export default {
name: 'Login',
data() {
return {
loginForm: {
username: 'admin',
password: '123456'
},
loginRules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
]
}
}
},
methods: {
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
login(this.loginForm).then(response => {
if(response.data.code === 1) {
setToken(response.data.data)
this.$router.push('/')
} else {
this.$message.error(response.data.msg)
}
})
}
})
}
}
}
</script>
<style scoped>
.login-container {
min-height: 100%;
width: 100%;
background-color: #2d3a4b;
overflow: hidden;
}
.login-form {
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.title {
font-size: 26px;
color: #eee;
margin: 0 auto 40px;
text-align: center;
font-weight: bold;
}
</style>
请求拦截器:
javascript复制import axios from 'axios'
import { getToken } from '@/utils/auth'
const service = axios.create({
baseURL: '/api',
timeout: 5000
})
service.interceptors.request.use(
config => {
if (getToken()) {
config.headers['token'] = getToken()
}
return config
},
error => {
return Promise.reject(error)
}
)
export default service
bash复制