分页查询显示
在之前实现Mybatis-plus的分页查询+模糊查询之后,自动返回的JSON格式如下:
{
"records": [
{
"id": 1,
"username": "admin",
"password": "admin",
"nickname": "管理员",
"email": "admin@qq.com",
"phone": "12345678910",
"address": "Mars",
"avatar": null
},
{
"id": 2,
"username": "admin2",
"password": null,
"nickname": "管理员2",
"email": "admin2@163.com",
"phone": null,
"address": null,
"avatar": null
},
{
"id": 3,
"username": "ddd",
"password": null,
"nickname": null,
"email": null,
"phone": null,
"address": null,
"avatar": null
},
{
"id": 4,
"username": "dmiut",
"password": null,
"nickname": null,
"email": null,
"phone": null,
"address": "mark",
"avatar": null
},
{
"id": 5,
"username": "4",
"password": null,
"nickname": null,
"email": null,
"phone": null,
"address": "",
"avatar": null
}
],
"total": 11,
"size": 5,
"current": 1,
"orders": [],
"optimizeCountSql": true,
"searchCount": true,
"countId": null,
"maxLimit": null,
"pages": 3
}
所以前端就没有res.data这一栏,在前端修改成res.records
load(){
fetch( "http://localhost:9090/user/page?pageNum="+this.pageNum+"&pageSize="+this.pageSize).then(res => res.json())
.then( res => {
this.tableData = res.records
this.total = res.total
console.log(res)
})
},
又一次在前端获取到数据
axios
在前端文件夹目录的terminal下输入:
npm i axios -S
在src下新建utils/request.js
import axios from "axios";
const request = axios.create({
baseURL:'http://localhost',
timeout:5000
})
// request 拦截器
request.interceptors.request.use( config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
// config.headers['token'] = user.token;
return config
}, error => {
return Promise.reject(error)
});
// response拦截器
// 在api响应后统一处理结果
request.interceptors.response.use(
response =>{
let res = response.data;
//如果返回的是文件
if (response.config.responseType === 'blob'){
return res;
}
//兼容服务端返回的字符串数据
if (typeof res === 'string'){
res = res ? JSON.parse(res) : res
}
return res;
},
error => {
console.log('err: ' + error)
return Promise.reject(error)
}
)
export default request
在main.js中将request设为全局变量
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import './assets/globle.css'
import request from "@/utils/request";
Vue.config.productionTip = false
Vue.use(ElementUI,{size:"mini"});
Vue.prototype.axios = request
new Vue({
router,
render: h => h(App)
}).$mount('#app')
在HomeView.vue中重写load()方法
load(){
request.get("http://localhost:9090/user/page?" +
"pageNum="+ this.pageNum+
"&pageSize=" + this.pageSize+
"&username="+this.username+
"&email="+this.email+
"&address="+this.address)
.then(res => {
console.log(res)
})
},
成功在前端log出来res
不过前端默认写了模糊查询的变量为""空字符串,所以在后端加一个空字符串的if判断
@GetMapping("/page")
@ResponseBody
public IPage<User> findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(required = false, defaultValue = "") String username,
@RequestParam(required = false, defaultValue = "") String email,
@RequestParam(required = false, defaultValue = "") String address){
final String cmp = "";
IPage<User> page = new Page<>(pageNum, pageSize);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (!cmp.equals(username)){
queryWrapper.like("username",username);
}
if (!cmp.equals(email)) {
queryWrapper.like("email",email);
}
if (!cmp.equals(address)){
queryWrapper.like("address",address);
}
return userService.page(page,queryWrapper);
}
又调试了一下发现全局变量设置的有问题,还是得import request
模糊查询
在前端中添加placeholder和v-model,之后就会自动读取输入的变量
<div style="margin: 10px 0;">
<el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search" v-model="username"></el-input>
<el-input style="width: 200px;margin-left: 5px" placeholder="请输入邮箱" suffix-icon="el-icon-message" v-model="email"></el-input>
<el-input style="width: 200px;margin-left: 5px" placeholder="请输入地址" suffix-icon="el-icon-position" v-model="address"></el-input>
<el-button style="margin-left: 5px" type="primary" @click="load">搜索</el-button>
</div>
Insert
添加新增用户表单:
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%">
<el-form label-width="80px" size="small">
<el-form-item label="用户名" :label-width="formLabelWidth">
<el-input v-model="form.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="昵称" :label-width="formLabelWidth">
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="formLabelWidth">
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="电话" :label-width="formLabelWidth">
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="地址" :label-width="formLabelWidth">
<el-input v-model="form.address" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
<el-button type="primary" @click="handleAdd">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
在新增按钮上添加handleAdd方法,确定按钮上添加save方法
handleAdd(){
this.dialogFormVisible = true
this.form = {}
},
save(){
request.post("/user",this.form).then(res => {
if (res){
this.$message.success("保存成功!")
this.dialogFormVisible = false
this.load()
}else {
this.$message.success("error!")
}
})
},
Edit
利用Insert中添加的表单实现信息更新
<template slot-scope="scope">
<el-button type="info" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i> </el-button>
<el-button type="danger">删除 <i class="el-icon-delete"></i> </el-button>
</template>
在编辑按钮上添加handleEdit方法
handleEdit(row){
this.form = Object.assign({},row)
this.dialogFormVisible = true
this.load()
}
注意使用Object.assigh({},row)来给from赋值,不然会出现点击取消后依然会保存数据的bug
delete
<el-button type="danger" @click="handleDelete(scope.row.id)">删除 <i class="el-icon-delete"></i> </el-button>
在删除按钮中添加handleDelete方法,直接将row.id传进去
handleDelete(id){
request.delete("/user/"+id)
.then(res => {
if (res){
this.$message.success("删除成功!")
this.load()
}else {
this.$message.error("删除失败!")
}
})
},
考虑到这种情况不太安全,再加一个气泡悬浮
<el-popconfirm
confirm-button-text='确定'
cancel-button-text='取消'
icon="el-icon-info"
icon-color="red"
title="确定删除吗?"
@confirm="handleDelete(scope.row.id)"
>
<el-button type="danger" slot="reference" style="margin-left: 5px">删除 <i class="el-icon-delete"></i> </el-button>
</el-popconfirm>
将删除方法改为放在popconfirm中的@confirm
批量删除
批量删除后端方法:
@PostMapping("/del/batch")
@ResponseBody
public boolean deleteBatch(@RequestBody List<Integer> ids){
return userService.removeBatchByIds(ids);
}
前端在表格中添加selection
<el-table-column type="selection" width="55"></el-table-column>
同时在selection中接收数据:
<el-table :data="tableData" border stripe header-cell-class-name="headerBg" @selection-change="handleSelectionChange">
之后写这两个方法
handleSelectionChange(val){
this.multiSelection = val
},
delBatch(){
let ids = this.multiSelection.map( v => v.id)
request.post("/user/del/batch",ids)
.then( res => {
if (res){
this.$message.success("批量删除成功!")
this.load()
}else{
this.$message.error("批量删除失败!")
}
})
},
Rename
考虑到目前写的页面是Admin的管理界面,所以将HomeView rename为Manage
同时修改路由
path: '/',
name: 'Manage',
component: ()=>import('../views/Manage'),
组件分发
Aside
将侧边栏单独拎出来放到Aside.vue中
<template>
<el-menu :default-openeds="[]" style="height: 100%; overflow-x: hidden"
background-color="rgb(48,65,86)"
text-color="#fff"
active-text-color="#409EFF"
:collapse-transition="false"
:collapse="isCollapse"
>
<div style="height: 60px; line-height: 60px; text-align: center">
<img src="../assets/logo.png" alt="" style="width: 20px; position: relative;top: 5px;margin-right: 5px">
<b style="color: white" v-show="logoTextShow">后台管理系统</b>
</div>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-message"></i>
<span>导航一</span>
</template>
<el-menu-item-group>
<template slot="title">分组一</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-menu"></i>
<span>导航二</span>
</template>
<el-menu-item-group>
<template slot="title">分组一</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="2-3">选项3</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name: "Aside",
props:{
isCollapse:Boolean,
logoTextShow:Boolean,
}
}
</script>
<style scoped>
</style>
如果是在外层中需要使用的变量的话需要在props中提前写好接收的类型
同时在外层调用时指定调用的方法/类型/对象
<el-aside :width="sideWidth + 'px'" style="background-color: rgb(238, 241, 246);height: 100%">
<Aside :isCollapse="isCollapse" :logoTextShow="logoTextShow" />
</el-aside>
Header
将上页中的个人信息单独放到Header.vue中
<template>
<div style="font-size: 12px; line-height: 60px;display: flex">
<div style="flex: 1;font-size: 25px">
<span :class="collapseBtnClass" style="cursor: pointer" @click="collapse"></span>
</div>
<el-dropdown style="width: 70px;cursor: pointer">
<span>王小虎</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: "Header",
props: {
collapseBtnClass: String,
collapse:Function,
}
}
</script>
<style scoped>
</style>
如果调用指定的是一个方法,那么就要在props中指定collapse: Function
而对于按钮组件这样的类型只需要传String类型
User
将查看用户信息栏放入User.vue
实现分发路由
{
path: '/',
name: 'Manage',
component: Manage,
children:[
{path: '/user', name: 'User', component: User}
]
},
将之前的变量和方法放入User.vue中
<template>
<div>
<div style="margin-bottom: 30px">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item><a href="/">用户管理</a></el-breadcrumb-item>
</el-breadcrumb>
</div>
<div style="margin: 10px 0;">
<el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search" v-model="username"></el-input>
<el-input style="width: 200px;margin-left: 5px" placeholder="请输入邮箱" suffix-icon="el-icon-message" v-model="email"></el-input>
<el-input style="width: 200px;margin-left: 5px" placeholder="请输入地址" suffix-icon="el-icon-position" v-model="address"></el-input>
<el-button style="margin-left: 5px" type="primary" @click="load">搜索</el-button>
<el-button style="margin-left: 5px" type="info" @click="reset">重置</el-button>
</div>
<div style="margin: 10px 0">
<el-button type="primary" @click="handleAdd">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
<el-popconfirm
confirm-button-text='确认'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="确定要批量删除吗?"
@confirm="delBatch"
>
<el-button type="danger" slot="reference" style="margin-left: 5px">批量删除 <i class="el-icon-remove-outline"></i> </el-button></el-popconfirm>
<el-button type="primary" style="margin-left: 5px">导入 <i class="el-icon-bottom"></i> </el-button>
<el-button type="primary" style="margin-left: 5px">导出 <i class="el-icon-top"></i> </el-button>
</div>
<el-table :data="tableData" border stripe header-cell-class-name="'headerBg'" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="username" label="用户名" width="120"></el-table-column>
<el-table-column prop="nickname" label="昵称" width="120"></el-table-column>
<el-table-column prop="email" label="邮箱" width="200"></el-table-column>
<el-table-column prop="phone" label="电话" width="130"></el-table-column>
<el-table-column prop="address" label="地址" width="300"></el-table-column>
<el-table-column prop="action" label="操作">
<template slot-scope="scope">
<el-button type="info" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i> </el-button>
<el-popconfirm
confirm-button-text='确定'
cancel-button-text='取消'
icon="el-icon-info"
icon-color="red"
title="确定删除吗?"
@confirm="handleDelete(scope.row.id)"
>
<el-button type="danger" slot="reference" style="margin-left: 5px">删除 <i class="el-icon-delete"></i> </el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[5,10,20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%">
<el-form label-width="80px" size="small">
<el-form-item label="用户名" :label-width="formLabelWidth">
<el-input v-model="form.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="昵称" :label-width="formLabelWidth">
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="formLabelWidth">
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="电话" :label-width="formLabelWidth">
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="地址" :label-width="formLabelWidth">
<el-input v-model="form.address" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: "User",
data(){
return{
tableData: [],
total:0,
pageNum : 1,
pageSize : 10,
username:"",
email:"",
address:"",
dialogFormVisible:false,
form:{},
multiSelection:[],
}
},
created() {
this.load()
},
methods:{
load(){
this.request.get("/user/page",{
params:{
pageNum:this.pageNum,
pageSize:this.pageSize,
username:this.username,
email:this.email,
address:this.address,
}
})
.then(res => {
this.tableData = res.records;
this.total = res.total;
})
},
handleSizeChange(pageSize){
console.log(pageSize)
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum){
console.log(pageNum)
this.pageNum = pageNum
this.load()
},
reset(){
this.username=""
this.email=""
this.address=""
this.load()
},
handleAdd(){
this.dialogFormVisible = true
this.form = {}
},
save(){
this.request.post("/user",this.form).then(res => {
if (res){
this.$message.success("保存成功!")
this.dialogFormVisible = false
this.load()
}else {
this.$message.error("error!")
}
})
},
handleEdit(row){
this.form = Object.assign({},row)
this.dialogFormVisible = true
this.load()
},
handleDelete(id){
this.request.delete("/user/"+id)
.then(res => {
if (res){
this.$message.success("删除成功!")
this.load()
}else {
this.$message.error("删除失败!")
}
})
},
handleSelectionChange(val){
this.multiSelection = val
},
delBatch(){
let ids = this.multiSelection.map( v => v.id)
this.request.post("/user/del/batch",ids)
.then( res => {
if (res){
this.$message.success("批量删除成功!")
this.load()
}else{
this.$message.error("批量删除失败!")
}
})
},
}
}
</script>
<style scoped>
.headerBg{
background: #eee!important;
}
</style>
之后进入/user
配置Vuex
方便面包屑的使用
在src目录下新建store文件夹,新建index.js配置vuex
因为vue的版本问题,需要先输入以下命令:
npm install --save vuex@3.6.2
index.js
import Vue from "vue";
import Vuex from 'vuex';
Vue.use(Vuex)
const index = new Vuex.Store({
state:{
currentPathName:''
},
mutations:{
setPath(state){
state.currentPathName = localStorage.getItem("currentPathName")
}
}
})
export default index
在main.js中添加vuex的配置如下
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import './assets/globle.css'
import request from "@/utils/request";
import store from "@/store";
Vue.config.productionTip = false
Vue.use(ElementUI,{size:"mini"});
Vue.prototype.request = request
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在router/index.js下添加路由守卫
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
//路由守卫
router.beforeEach((to,from,next) => {
localStorage.setItem("currentPathName",to.name) //设置当前的路由名称,为了在Header组件中使用
store.commit("setPath") //触发store的数据更新
next() //放行路由
})
export default router
在Header.vue中添加watch和computed方法
<script>
export default {
name: "Header",
props: {
collapseBtnClass: String,
collapse:Function,
},
computed:{
currentPathName(){
return this.$store.state.currentPathName; //需要监听的数据
}
},
watch:{
currentPathName(newVal, oldVal){
console.log(newVal)
}
}
}
</script>
在面包屑中添加路由:
<el-breadcrumb separator="/" style="display: inline-block; margin-left: 10px">
<el-breadcrumb-item :to="'/'">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ currentPathName }}</el-breadcrumb-item>
</el-breadcrumb>
至此就可以在面包屑中通过点击跳转路由
此时的router/index.js配置如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
import User from "@/views/User";
import Manage from "@/views/Manage";
import Home from '@/views/Home'
import store from "@/store";
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: '首页',
component: Manage,
redirect:"/home",
children:[
{path: 'home', name: '首页', component: Home},
{path: 'user', name: '用户管理', component: User},
]
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
//路由守卫
router.beforeEach((to,from,next) => {
localStorage.setItem("currentPathName",to.name) //设置当前的路由名称,为了在Header组件中使用
store.commit("setPath") //触发store的数据更新
next() //放行路由
})
export default router
此时准备上床睡觉摆烂,于是懒得再开启后端了
明天看看把导入导出登陆注册完结了
Hutool
为了实现导入导出功能,需要在依赖中添加hutool包
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
export信息导出
// 导出接口
@GetMapping("/export")
@ResponseBody
public void export (HttpServletResponse response) throws Exception{
// find all users from mysql
List<User> list = userService.list();
// 将信息导出的磁盘路径
// ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath)
//在内存操作写出到浏览器
ExcelWriter writer = ExcelUtil.getWriter(true);
//自定义标题别名
//反正都能看懂就不换别名了
//writer.addHeaderAlias("username","用户名");
// 一次性写出list内对象到excel,使用默认样式,强制输出标题
writer.write(list,true);
// 设置浏览器响应的格式
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
String fileName = URLEncoder.encode("用户信息","UTF-8");
response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");
ServletOutputStream out = response.getOutputStream();
writer.flush(out,true);
out.close();
writer.close();
}
反正英文变量名都能看懂,就不做变量名转换了
设置浏览器相应格式只需要改一个filename就行,其他的都可以CV
在前端添加的导出标志上添加exp方法
<el-button type="primary" style="margin-left: 5px" @click="exp">导出 <i class="el-icon-top"></i> </el-button>
exp(){
window.open("http://localhost:9090/user/export")
},
import信息导入
// 导入接口
@PostMapping("/import")
@ResponseBody
public Boolean imp(MultipartFile file) throws Exception{
InputStream inputStream = file.getInputStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
List<User> list = reader.readAll(User.class);
// 将获取的数据插入数据库
userService.saveBatch(list);
return true;
}
在导入后需要将读取的数据保存到数据库,有一个saveBatch方法批量保存数据
前端需要在导入中添加一个upload组件
<el-upload
action="http://localhost:9090/user/import"
style="display: inline-block"
:show-file-list="false"
accept="xlsx"
:on-success="handleExcelImportSuccess">
<el-button type="primary" style="margin-left: 5px">导入 <i class="el-icon-bottom"></i> </el-button>
</el-upload>
同时写一个handleExcelImportSuccess的方法处理导入之后的数据
handleExcelImportSuccess(){
this.$message.success("导入成功!")
this.load()
}
登陆界面
<template>
<div class="wrapper">
<div style="margin: 200px auto; background-color: #fff; width: 350px; height: 300px; padding: 20px; border-radius: 10px">
<div style="margin: 20px 0; text-align: center; font-size: 24px"><b>登 录</b></div>
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-user" v-model="user.username"></el-input>
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
<div style="margin: 10px 0; text-align: right">
<el-button type="primary" size="small" autocomplete="off">登录</el-button>
<el-button type="warning" size="small" autocomplete="off">注册</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
user : {}
}
}
}
</script>
<style scoped>
.wrapper {
height: 100vh;
background-image: linear-gradient(to bottom right, #FC4668, #3F5EFB);
overflow: hidden;
}
</style>
效果图:
后端Login
在Controller层中新建dto文件夹,新建UserDTO类表示接收的User对象
package com.example.springweb.contoller.dto;
import lombok.Data;
/**
* 接收前端登录请求的参数
*/
@Data
public class UserDTO {
private String username;
private String password;
}
在Controller层中写login接口:
@PostMapping("/login")
@ResponseBody
public boolean login(@RequestBody UserDTO userDTO){
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)){
return false;
}
return userService.login(userDTO);
}
在service层中定义login函数
public boolean login(UserDTO userDTO) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", userDTO.getUsername());
wrapper.eq("password", userDTO.getPassword());
try{
User one = getOne(wrapper);
return one != null;
} catch (Exception e){
LOG.error(e);
return false;
}
}
前端login
使用axios获取后端接口,简单校验后跳转主菜单
methods : {
login(){
this.request.post("/user/login",this.user)
.then(res => {
if (!res){
this.$message.error("用户名或密码错误")
} else{
this.$router.push("/")
}
})
},
}
看之后能不能整一个拦截器+JWT登录认证
之后我想试试能不能权限登录进行跳转,加了一个下拉悬浮页的selection
<el-form :model="user" ref="userForm">
<el-form-item>
<el-select v-model="user.identity" placeholder="请选择登陆权限">
<el-option value="user"></el-option>
<el-option value="admin"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-user" v-model="user.username"></el-input>
</el-form-item>
<el-form-item>
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
</el-form-item>
</el-form>
click校验是否能够log出不同权限从而进行跳转:
login(){
this.$refs['userForm'].validate((valid) => {
if (valid){
// if (this.user.identity === 'user'){
// console.log("user")
// } else if (this.user.identity === 'admin'){
// console.log("admin")
// }
this.request.post("/user/login",this.user)
.then(res => {
if (!res){
this.$message.error("用户名或密码错误")
} else{
this.$router.push("/")
}
})
} else {
console.log("error submit!")
return false
}
})
},
事实证明分权限登录是可行的😋
课听到现在终于在注册那一P讲到封装接口了
Login封装
在开发过程中最好和前端程序员统一Result返回类型
Result中有code, messge, data三个变量
package com.example.springweb.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 接口统一返回包装类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private String code;
private String msg;
private Object data;
public static Result success(){
return new Result(Constants.CODE_200, "",null);
}
public static Result success(Object data){
return new Result(Constants.CODE_200, "",data);
}
public static Result error(String code, String msg){
return new Result(code, msg,null);
}
public static Result error(){
return new Result(Constants.CODE_500, "", null);
}
}
同时为了接收Result中的code变量,定义一个Constants类
package com.example.springweb.common;
public interface Constants {
String CODE_200 = "200"; // success
String CODE_500 = "500"; // 系统错误
String CODE_401 = "401"; // 权限不足
String CODE_400 = "400"; // 参数不足
String CODE_600 = "600"; //其他业务异常
}
重写login方法
@PostMapping("/login")
@ResponseBody
public Result login(@RequestBody UserDTO userDTO){
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)){
return Result.error(Constants.CODE_400, "参数错误");
}
UserDTO dto = userService.login(userDTO);
return Result.success(dto);
}
在Service层中修改login方法如下:
public UserDTO login(UserDTO userDTO) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", userDTO.getUsername());
wrapper.eq("password", userDTO.getPassword());
User one;
try{
one = getOne(wrapper); // 从数据库查询用户信息
} catch (Exception e){
LOG.error(e);
throw new ServiceException(Constants.CODE_500, "系统错误");
}
if (one != null){
BeanUtil.copyProperties(one, userDTO, true);
return userDTO;
} else {
throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
}
}
自定义一个异常:
package com.example.springweb.exception;
import com.example.springweb.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
@ResponseBody
public Result handle(ServiceException se){
return Result.error(se.getCode(),se.getMessage());
}
}
之后定义一个业务异常:
package com.example.springweb.exception;
import lombok.Getter;
/**
* 自定义异常
*/
@Getter
public class ServiceException extends RuntimeException{
private String code;
public ServiceException(String code, String msg){
super(msg);
this.code = code;
}
}
这样登录时就可以在后端抛出异常及时debug
使用Localstorage获取头像和昵称
login(){
this.$refs['userForm'].validate((valid) => {
if (valid){
// if (this.user.identity === 'user'){
// console.log("user")
// } else if (this.user.identity === 'admin'){
// console.log("admin")
// }
this.request.post("/user/login",this.user)
.then(res => {
if (res.code === '200'){
localStorage.setItem("user", JSON.stringify(res.data)) // 存储用户信息到浏览器
this.$router.push("/")
this.$message.success("登录成功!")
} else{
this.$message.error(res.msg)
}
})
} else {
console.log("error submit!")
return false
}
})
},
再一次改写login方法,此时使用localStorage将获取到的对象存储在前端
而在Header.vue中,可以使用getItem方法获取前端的存储的对象
data(){
return {
user : localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
}
},
这样我们就能在前端中获取用户的昵称和头像
<el-dropdown style="width: 100px;cursor: pointer">
<div style="display: inline-block">
<img :src="user.avatarUrl" alt=""
style="width: 30px; border-radius: 50%; position: relative; top: 10px; right: 5px">
</div>
<span>{{ user.nickname }}</span><i class="el-icon-arrow-down" style="margin-left: 5px;"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item style="font-size: 14px; padding: 10px">个人信息</el-dropdown-item>
<el-dropdown-item style="font-size: 14px; padding: 10px">
<router-link to="/login" style="text-decoration: none">退出</router-link>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
但是在退出时我们也需要删除前端存储的变量
所以在退出按钮上绑定一个logout方法
<span to="/login" style="text-decoration: none" @click="logout">退出</span>
同时将标签改为span,路由切换在方法中定义
logout(){
this.$router.push("/login")
localStorage.removeItem("user")
this.$message.success("退出成功!")
},
后端register
@PostMapping("/register")
@ResponseBody
public Result register(@RequestBody UserDTO userDTO){
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
return Result.error(Constants.CODE_400, "参数错误");
}
return Result.success(userService.register(userDTO));
}
之后在userservice中添加register方法
public UserDTO register(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one == null) {
one = new User();
BeanUtil.copyProperties(userDTO, one, true);
save(one);
return userDTO;
} else {
throw new ServiceException(Constants.CODE_600, "用户已存在");
}
}
因为wrapper有代码冗余,所以将try catch和获取数据库对象封装成一个方法
private User getUserInfo(UserDTO userDTO){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", userDTO.getUsername());
wrapper.eq("password", userDTO.getPassword());
User one;
try{
one = getOne(wrapper); // 从数据库查询用户信息
} catch (Exception e){
LOG.error(e);
throw new ServiceException(Constants.CODE_500, "系统错误");
}
return one;
}
重写后的Register和Login方法如下:
public UserDTO login(UserDTO userDTO) {
User one=getUserInfo(userDTO);
if (one != null){
BeanUtil.copyProperties(one, userDTO, true);
return userDTO;
} else {
throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
}
}
public UserDTO register(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one == null) {
one = new User();
BeanUtil.copyProperties(userDTO, one, true);
save(one);
return userDTO;
} else {
throw new ServiceException(Constants.CODE_600, "用户已存在");
}
}
private User getUserInfo(UserDTO userDTO){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", userDTO.getUsername());
wrapper.eq("password", userDTO.getPassword());
User one;
try{
one = getOne(wrapper); // 从数据库查询用户信息
} catch (Exception e){
LOG.error(e);
throw new ServiceException(Constants.CODE_500, "系统错误");
}
return one;
}
前端register
直接CVLogin代码,稍作修改就可以作为注册页面
<template>
<div class="wrapper">
<div style="margin: 200px auto; background-color: #fff; width: 350px; height: 400px; padding: 20px; border-radius: 10px">
<div style="margin: 20px 0; text-align: center; font-size: 24px"><b>注 册</b></div>
<el-form :model="user" ref="userForm">
<el-form-item>
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-user" v-model="user.username"></el-input>
</el-form-item>
<el-form-item>
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
</el-form-item>
</el-form>
<div style="margin: 10px 0; text-align: right">
<el-button type="primary" size="small" autocomplete="off" @click="register">注册</el-button>
<el-button type="warning" size="small" autocomplete="off" @click="($router.push('/login'))">返回登录</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Register",
data() {
return {
user : {}
}
},
methods : {
register(){
this.$refs['userForm'].validate((valid) => {
if (valid){
// if (this.user.identity === 'user'){
// console.log("user")
// } else if (this.user.identity === 'admin'){
// console.log("admin")
// }
this.request.post("/user/register",this.user)
.then(res => {
if (res.code === '200'){
this.$message.success("注册成功!")
} else{
this.$message.error(res.msg)
}
})
} else {
console.log("error submit!")
return false
}
})
},
}
}
</script>
<style scoped>
.wrapper {
height: 100vh;
background-color: #FFDEE9;
background-image: linear-gradient(0deg, #FFDEE9 0%, #B5FFFC 100%);
overflow: hidden;
}
</style>