java
springboot
mybatisplus
mysql
element-ui
vue
axios
html
css
maven
瑞吉项目流程图
登录流程
登录流程实现细节
###登录页面html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>瑞吉外卖管理端</title>
<link rel="shortcut icon" href="../../favicon1.ico">
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css">
<link rel="stylesheet" href="../../styles/login.css">
<link rel="stylesheet" href="../../styles/icon/iconfont.css" />
<style>
.body{
min-width: 1366px;
}
</style>
</head>
<body>
<div class="login" id="login-app">
<div class="login-box">
<img src="../../images/login/login-l.png" alt="">
<div class="login-form">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" >
<div class="login-form-title">
<img src="../../images/login/logo.png" style="width:139px;height:42px;" alt="" />
</div>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号" maxlength="20"
prefix-icon="iconfont icon-user" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="iconfont icon-lock" maxlength="20"
@keyup.enter.native="handleLogin" />
</el-form-item>
<el-form-item style="width:100%;">
<el-button :loading="loading" class="login-btn" size="medium" type="primary" style="width:100%;"
@click.native.prevent="handleLogin">
<span v-if="!loading">登录</span>
<span v-else>登录中...</span>
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/request.js"></script>
<script src="../../js/validate.js"></script>
<script src="../../api/login.js"></script>
<script>
new Vue({
el: '#login-app',
data() {
return {
loginForm:{
username: 'admin',
password: '123456'
},
loading: false
}
},
computed: {
loginRules() {
const validateUsername = (rule, value, callback) => {
if (value.length < 1 ) {
callback(new Error('请输入用户名'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('密码必须在6位以上'))
} else {
callback()
}
}
return {
'username': [{ 'validator': validateUsername, 'trigger': 'blur' }],
'password': [{ 'validator': validatePassword, 'trigger': 'blur' }]
}
}
},
created() {
},
methods: {
async handleLogin() {
this.$refs.loginForm.validate(async (valid) => {
if (valid) {
this.loading = true
let res = await loginApi(this.loginForm)
if (String(res.code) === '1') {
localStorage.setItem('userInfo',JSON.stringify(res.data))
window.location.href= '/backend/index.html'
} else {
this.$message.error(res.msg)
this.loading = false
}
}
})
}
}
})
</script>
</body>
</html>
当点击登录按钮时调用handleLogin方法
@click.native.prevent="handleLogin">
<span v-if="!loading">登录</span>
<span v-else>登录中...</span>
methods: {
async handleLogin() {
this.$refs.loginForm.validate(async (valid) => {
if (valid) {
this.loading = true
let res = await loginApi(this.loginForm)
if (String(res.code) === '1') {
localStorage.setItem('userInfo',JSON.stringify(res.data))
window.location.href= '/backend/index.html'
} else {
this.$message.error(res.msg)
this.loading = false
}
}
})
}
}
登录成功后跳转到/backend/index.html
管理页面html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>瑞吉外卖管理端</title>
<link rel="shortcut icon" href="favicon1.ico">
<!-- 引入样式 -->
<link rel="stylesheet" href="plugins/element-ui/index.css" />
<link rel="stylesheet" href="styles/common.css" />
<link rel="stylesheet" href="styles/index.css" />
<link rel="stylesheet" href="styles/icon/iconfont.css" />
<style>
.body{
min-width: 1366px;
}
.app-main{
height: calc(100% - 64px);
}
.app-main .divTmp{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="app" id="app">
<div class="app-wrapper openSidebar clearfix">
<!-- sidebar -->
<div class="sidebar-container">
<div class="logo">
<!-- <img src="images/logo.png" width="122.5" alt="" /> -->
<img src="images/login/login-logo.png" alt="" style="width: 117px; height: 32px" />
</div>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="defAct"
:unique-opened="false"
:collapse-transition="false"
background-color="#343744"
text-color="#bfcbd9"
active-text-color="#f4f4f5"
>
<div v-for="item in menuList" :key="item.id">
<el-submenu :index="item.id" v-if="item.children && item.children.length>0">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span>{{item.name}}</span>
</template>
<el-menu-item
v-for="sub in item.children"
:index="sub.id"
:key="sub.id"
@click="menuHandle(sub,false)"
>
<i :class="iconfont" :class="sub.icon"></i>
<span slot="title">{{sub.name}}</span>
</el-menu-item
>
</el-submenu>
<el-menu-item v-else :index="item.id" @click="menuHandle(item,false)">
<i class="iconfont" :class="item.icon"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
</div>
</el-menu>
</el-scrollbar>
</div>
<div class="main-container">
<!-- <navbar /> -->
<div class="navbar">
<div class="head-lable">
<span v-if="goBackFlag" class="goBack" @click="goBack()"
><img src="images/icons/btn_back@2x.png" alt="" /> 返回</span
>
<span>{{headTitle}}</span>
</div>
<div class="right-menu">
<div class="avatar-wrapper">{{ userInfo.name }}</div>
<!-- <div class="logout" @click="logout">退出</div> -->
<img src="images/icons/btn_close@2x.png" class="outLogin" alt="退出" @click="logout" />
</div>
</div>
<div class="app-main" v-loading="loading">
<div class="divTmp" v-show="loading"></div>
<iframe
id="cIframe"
class="c_iframe"
name="cIframe"
:src="iframeUrl"
width="100%"
height="auto"
frameborder="0"
v-show="!loading"
></iframe>
</div>
</div>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="plugins/axios/axios.min.js"></script>
<script src="js/request.js"></script>
<script src="./api/login.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
defAct: '2',
menuActived: '2',
userInfo: {},
menuList: [
// {
// id: '1',
// name: '门店管理',
// children: [
{
id: '2',
name: '员工管理',
url: 'page/member/list.html',
icon: 'icon-member'
},
{
id: '3',
name: '分类管理',
url: 'page/category/list.html',
icon: 'icon-category'
},
{
id: '4',
name: '菜品管理',
url: 'page/food/list.html',
icon: 'icon-food'
},
{
id: '5',
name: '套餐管理',
url: 'page/combo/list.html',
icon: 'icon-combo'
},
{
id: '6',
name: '订单明细',
url: 'page/order/list.html',
icon: 'icon-order'
}
// ],
// },
],
iframeUrl: 'page/member/list.html',
headTitle: '员工管理',
goBackFlag: false,
loading: true,
timer: null
}
},
computed: {},
created() {
const userInfo = window.localStorage.getItem('userInfo')
if (userInfo) {
this.userInfo = JSON.parse(userInfo)
}
this.closeLoading()
},
beforeDestroy() {
this.timer = null
clearTimeout(this.timer)
},
mounted() {
window.menuHandle = this.menuHandle
},
methods: {
logout() {
logoutApi().then((res)=>{
if(res.code === 1){
localStorage.removeItem('userInfo')
window.location.href = '/backend/page/login/login.html'
}
})
},
goBack() {
// window.location.href = 'javascript:history.go(-1)'
const menu = this.menuList.find(item=>item.id===this.menuActived)
// this.goBackFlag = false
// this.headTitle = menu.name
this.menuHandle(menu,false)
},
menuHandle(item, goBackFlag) {
this.loading = true
this.menuActived = item.id
this.iframeUrl = item.url
this.headTitle = item.name
this.goBackFlag = goBackFlag
this.closeLoading()
},
closeLoading(){
this.timer = null
this.timer = setTimeout(()=>{
this.loading = false
},1000)
}
}
})
</script>
</body>
</html>
登录相关 java后台代码实现
前端发送请求
function loginApi(data) {
return $axios({
'url': '/employee/login',
'method': 'post',
data
})
}
后台通过全局过滤器判断登录
package com.jjk.ruiji.filters;
import com.alibaba.fastjson.JSON;
import com.jjk.ruiji.common.BaseContext;
import com.jjk.ruiji.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//全局过滤器
@Slf4j
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//A. 获取本次请求的URI
String requestURI = request.getRequestURI();
String urls[] = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**"
};
//B. 判断本次请求, 是否需要登录, 才可以访问
//C. 如果不需要,则直接放行
if (check(urls, requestURI)) {
filterChain.doFilter(request, response);
return;
}
//D. 判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request, response);
return;
}
/* Long id = Thread.currentThread().getId();
log.info("id {}", id);*/
//E. 如果未登录, 则返回未登录结果
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 路径匹配,检查本次请求是否需要放行
*
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls, String requestURI) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
return true;
}
}
return false;
}
}
登录成功放行来到 EmployeeController 处理登录请求
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
//①. 将页面提交的密码password进行md5加密处理, 得到加密后的字符串
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
//②. 根据页面提交的用户名username查询数据库中员工数据信息
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getUsername, employee.getUsername());
Employee emp = employeeService.getOne(wrapper);
//③. 如果没有查询到, 则返回登录失败结果
if (emp == null) {
return R.error("登录失败");
}
//④. 密码比对,如果不一致, 则返回登录失败结果
if (!emp.getPassword().equals(password)) {
return R.error("登录失败");
}
//⑤. 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if (emp.getStatus() == 0) {
return R.error("员工已禁用");
}
//⑥. 登录成功,将员工id存入Session, 并返回登录成功结果
request.getSession().setAttribute("employee", emp.getId());
System.out.println(emp.toString());
return R.success(emp);
}
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request) {
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
登录成功然后进入管理页面
失败返回登录页面并提示信息
管理页面默认初始化第一个为员工管理页
员工管理
员工管理代码实现
添加员工
前端发送添加员工请求
1 点击按钮调用方法 addMemberHandle
<el-button
type="primary"
@click="addMemberHandle('add')"
>
+ 添加员工
2 执行方法 跳转到 url: ‘/backend/page/member/add.html’,
// 添加
addMemberHandle (st) {
if (st === 'add'){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html',
name: '添加员工'
},true)
} else {
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html?id='+st,
name: '修改员工'
},true)
}
}
3 编辑号信息后 点击保存按钮 调用 方法 @click=“submitForm(‘ruleForm’, true)”
保存
</el-button>
<el-button
v-if="actionType == 'add'"
type="primary"
class="continue"
@click="submitForm('ruleForm', true)"
>
4 执行方法 submitForm (formName, st)
submitForm (formName, st) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.actionType === 'add') {
const params = {
...this.ruleForm,
sex: this.ruleForm.sex === '女' ? '0' : '1'
}
addEmployee(params).then(res => {
if (res.code === 1) {
this.$message.success('员工添加成功!')
if (!st) {
this.goBack()
} else {
this.ruleForm = {
username: '',
'name': '',
'phone': '',
// 'password': '',
// 'rePassword': '',/
'sex': '男',
'idNumber': ''
}
}
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
} else {
const params = {
...this.ruleForm,
sex: this.ruleForm.sex === '女' ? '0' : '1'
}
editEmployee(params).then(res => {
if (res.code === 1) {
this.$message.success('员工信息修改成功!')
this.goBack()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
}
} else {
console.log('error submit!!')
return false
}
})
},
5 发送添加请求 到后台
// 新增---添加员工
function addEmployee (params) {
return $axios({
url: '/employee',
method: 'post',
data: { ...params }
})
}
6 请求数据通过全局过滤器后 来到EmployeeController
然后处理数据 添加成功后 响应数据并刷新页面
@PostMapping
public R<String> save(@RequestBody Employee employee) {
log.info("添加员工 {}", employee.toString());
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employeeService.save(employee);
return R.success("新增员工成功");
}
按姓名搜索员工代码实现
1 点击按钮触发 @keyup.enter.native=“handleQuery”
<el-input
v-model="input"
placeholder="请输入员工姓名"
style="width: 250px"
clearable
@keyup.enter.native="handleQuery"
>
2 执行方法 handleQuery()
handleQuery() {
this.page = 1;
this.init();
}
3 执行方法 init()
async init () {
const params = {
page: this.page,
pageSize: this.pageSize,
name: this.input ? this.input : undefined
}
await getMemberList(params).then(res => {
if (String(res.code) === '1') {
this.tableData = res.data.records || []
this.counts = res.data.total
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
}
4 执行方法 getMemberList () 发送请求到后台
function getMemberList (params) {
return $axios({
url: '/employee/page',
method: 'get',
params
})
}
5 请求通过全局过滤器来到 EmployeeController
处理数据 并响应数据
/**
* 员工信息分页查询
*
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page<Employee>> page(int page, int pageSize, String name) {
/* 分页查询
底层基于mybatisplus 提供的一个分页插件
编写格式固定死的
*/
log.info("page = {} pageSize = {} name = {}", page, pageSize, name);
//分页构造器
Page<Employee> pageInfo = new Page<>(page, pageSize);
//条件构造器
LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper();
//添加一个过滤条件
lambdaQueryWrapper.like(StringUtils.isNotEmpty(name), Employee::getName, name);
//添加排序条件
lambdaQueryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo, lambdaQueryWrapper);
return R.success(pageInfo);
}
修改员工代码实现
1
<el-button
type="text"
size="small"
class="blueBug"
@click="addMemberHandle(scope.row.id)"
:class="{notAdmin:user !== 'admin'}"
>
编辑
2
// 添加
addMemberHandle (st) {
if (st === 'add'){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html',
name: '添加员工'
},true)
} else {
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html?id='+st,
name: '修改员工'
},true)
}
}
3
created() {
this.id = requestUrlParam('id')
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {
this.init()
}
},
mounted() {
},
methods: {
async init () {
queryEmployeeById(this.id).then(res => {
console.log(res)
if (String(res.code) === '1') {
console.log(res.data)
this.ruleForm = res.data
this.ruleForm.sex = res.data.sex === '0' ? '女' : '男'
// this.ruleForm.password = ''
} else {
this.$message.error(res.msg || '操作失败')
}
})
}
4
// 修改页面反查详情接口
function queryEmployeeById (id) {
return $axios({
url: `/employee/${id}`,
method: 'get'
})
}
5
/**
* 根据id查询
*
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id) {
log.info("根据id查询员工信息...");
Employee employee = employeeService.getById(id);
if (employee != null) {
return R.success(employee);
}
return R.error("查询id对应员工失败");
}
启用或禁用员工代码实现
1