第一部分:
elementplus官网:一个 Vue 3 UI 框架 | Element Plus (element-plus.org)
1、安装elementplus
npm install element-plus --save
查看package.json中存在依赖表示成功安装
2、引入elementplus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(ElementPlus)
注意:vue3不支持elementUI,所以选择以下搭配其中之一(这里我选的是2)
(1)vue2+elementUI
(2)vue3+elementPlus
3、使用组件设置布局
点击官网上方菜单栏的组件,点击
点击布局容器可以看到示例,我们选择使用最下面的侧边栏的示例代码
Container 布局容器 | Element Plus (element-plus.org)
查看示例的代码,这里选择手动引入(elementplus还支持自动按需引入)
代码主要分成四部分,第一部分我们需要放在template里面,第二部分是选择按需部分引入的方式,我们可以不管,第三部分是放在script里的data部分里面也就是提供页面的数据,具体填入形式如下
<script >
export default {
name: "HomeView",
data(){
const item = {
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
};
return {
tableData : (Array.from({ length: 20 }).fill(item)),
}
}
}
</script>
第四部分是页面的样式,不重要,我们可以不使用
引入完成功编译运行后的结果:
4、在asserts文件夹里面创建一个global.css用来书写全局样式
//global.css
html,body,div{
margin: 0;
padding: 0;
}
html,body{
height: 100%;
}
然后再main.js里引入 (不要忘了加 ./ )
将所有菜单的高度改成100% 这个时候滚动页面整个页面不会跟随滚动,菜单栏可以单独滚动
自行调整样式后:
第二部分
1、改变e-menu的背景颜色等一些属性
<el-menu :default-openeds="['1', '3']" style="height: 100%;overflow-x:hidden"
background-color="rgb(48,65,86)" //菜单背景设为藏蓝
text-color="#fff" //字体设为白色
active-text-color="#ffd04b" //选中后字体变成黄色
:collapse-transition="false"
:collapse="isCollapse" //设置初始被展开的属性为false(初始为折叠)
>
效果:
2、添加一些图标
先安装elementPlus图标:
npm install --save @element-plus/icons
到elementPlus官网图标下赋值图标标签代码
Icon 图标 | Element Plus (element-plus.org)
<span>Tom
<el-icon style="margin-left: 5px">
<ArrowDownBold></ArrowDownBold>
</el-icon>
</span>
效果:
编写收缩的方法
methods: {
collapse(){ //点击收缩按钮触发
this.isCollapse=!this.isCollapse
}
}
此时收缩会出现问题,文字没有隐藏,宽度不对
所以需要将宽度设为动态的
然后再将导航的名称都放在span标签里,就会自动收缩进去
然后更改如果收缩了,图标就改变,这里我用v-if和v-else的组合
再结合前面设置的isCollapse状态
使得图标可以切换
效果:
------------------》
3、设置菜单栏标题:
<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 v-show="!isCollapse" style="color: white">后台管理系统</b>
</div>
效果:
----------------------》
linehight如果设置和垂直高度一致,就会居中,否则就会在上方或者下面
style="line-height: 60px"
效果:
----------》
4、 阴影特效:
首先去掉e-menu的边框的颜色
border-right-color: transparent
再在e-side里面添加阴影属性
box-shadow: 2px 0 6px rgb(0 21 41 / 35%)
效果:
第三部分
1、设置表格页码数
进到官网页面,pagination
Pagination 分页 | Element Plus (element-plus.org)
copy“完整功能”代码
黏贴在e-mian标签下的e-table下
<div style="padding: 10px 0">
<el-pagination
v-model:current-page="currentPage4"
v-model:page-size="pageSize4"
:page-sizes="[100, 200, 300, 400]"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
/>
</div>
此时可以看到底部的效果,并且还没有生效
2、设置搜索栏(一定要看script里面是否自动导入了输入框图标)
<el-input style="width: 300px" type="text" placeholder="请输入名称">
<template #prefix>
<el-icon class="el-input__icon">
<search></search>
</el-icon>
</template>
</el-input>
<el-button class="ml-5" type="" style="color: orangered">搜索</el-button>
效果:
扩展多个输入框:
3、设置表头颜色和边框
<el-table :data="tableData" border stripe >
<style >
.el-table th{
background-color: beige !important
}
</style>
效果:
4、添加一些按钮:
增删导入导出
<div style="margin: 10px 0;text-align: left">
<el-button type="primary">新增
<el-icon><CirclePlus></CirclePlus></el-icon>
</el-button>
<el-button type="danger">批量删除
<el-icon><remove></remove></el-icon>
</el-button>
<el-button type="primary">导入
<el-icon><bottom></bottom></el-icon>
</el-button>
<el-button type="primary">导出
<el-icon><top></top></el-icon>
</el-button>
</div>
表格里的编辑、删除
<el-table-column label="操作" >
<el-table-column>
<template>slot-scope="scope"</template>
<el-button type="success">编辑
<el-icon><edit></edit></el-icon>
</el-button>
<el-button type="danger">删除
<el-icon><remove></remove></el-icon>
</el-button>
</el-table-column>
</el-table-column>
最后的效果:
5、固定表头优化:
(108条消息) vue项目中table表格固定表头和首尾列_梨城毒妃的博客-CSDN博客_vue固定表头
<el-table :data="tableData" border stripe height="500px" max-height="400px" >
至此管理系统的前端雏形已经产生
第四部分
1、加一个页签
在官网的面包屑模块下:
Breadcrumb 面包屑 | Element Plus (element-plus.org)
<div style="margin-bottom: 30px">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
<el-breadcrumb-item :to="{path: '/new'}">用户管理</el-breadcrumb-item>
<el-breadcrumb-item>promotion list</el-breadcrumb-item>
<el-breadcrumb-item>promotion detail</el-breadcrumb-item>
</el-breadcrumb>
</div>
效果:
2、SpringBoot框架搭建
1、创建项目,引入lombok,mybatisplus,mysql等等依赖
后两个依赖在项目勾选时候可以自动导入,所以只用关注前面的
<dependencies>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>3.4.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、编写yml文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: 123456
main:
banner-mode: off
mybatis-plus:
global-config:
banner: false
server:
port: 8081
效果:
项目结构:
测试springboot
3、数据库设计
建立表:sys_user
写入数据:
建立包和实体接口
使接口继承BaseMapper,并注解为Mapper
在写任何controller时候前面都要加一个注解:RestController
测试连接数据库
用lambdaquery来写查询
@RequestMapping("/select") //访问路径
public List<User> select(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// lqw.like(User::getUsername,'a');
List<User> userList = userDao.selectList(lqw);
return userList;
}
结果:
4、springBoot使用MybastisPlus实现分页查询
先设置拦截器:(一定要把pom里面的所有关于mybatisplus的依赖的版本号改成一致的)
编写接口:
后来发现要加total,所以改成返回Map
@GetMapping("/page")
public List<User> fingPage(@RequestParam Integer pageNum,@RequestParam Integer pageSize){
pageNum = (pageNum-1)*pageSize;
IPage page = new Page(pageNum,pageSize);
userDao.selectPage(page, null);
return page.getRecords();
}
用postman测试:(查出两条数据)
在vue页面查看要传输的数据:
编写传数据的方法:
created() {
//请求分页查询数据
fetch("http://localhost:9090/user/page?pageNum=1&pageSize=2").then(res => res.json()).then(
res =>{
console.log(res)
})
},
查看,出现了跨域问题 (前端端口与后端端口不一致)
选择在后端处理跨域
最简单方法:在controller类名前加@CrossOrigin注解,这是最简单的处理方式
比较完善的方法:加入如下代码
(94条消息) SpringBoot解决跨域问题_程序员青戈的博客-CSDN博客_springboot解决跨域问题 青戈
这个时候我们再次启动后台:此时控制台显示我们的数据获取成功
返回改成map后:(就分成了两组)
封装渲染时加载数据的代码为load
load(){
fetch("http://localhost:8081/page?pageNum="+this.pageNum+"&pageSize="+this.pageSize).then(res => res.json()).then(res => {
console.log(res)
this.tableData=res.data
this.total=res.total
})
}
在created函数内调用load
created() {
//请求分页查询数据
this.load()
}
created函数:在页面刷新时,重新加载数据,在vue3中有一个setup()注意里面不能用method里的方法也不能使用this
编写跟随页数更改跳转和重新渲染页面的两个方法:用于动态更改页码和显示数据数目
handleSizeChange(pageSize) handleCurrentChange
Pagination 分页 | Element Plus (element-plus.org)
handleSizeChange(pageSize){
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum){
this.pageNum=pageNum
this.load()
}
效果:
至此页码的所有功能都已激活
第五部分
绑定搜索框,返回搜索数据
增加查询的名称username关键字
@GetMapping("/page")
public Map<String,Object> fingPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam String username){
pageNum = (pageNum-1)*pageSize;
Page page = new Page(pageNum,pageSize);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.like(User::getUsername,username);
userDao.selectPage(page, lqw);
Map<String,Object> hm = new HashMap<>();
hm.put("data",page.getRecords());
hm.put("total",page.getTotal());
return hm;
// return userService.findAll();
}
我们可以看到我们写完QuerWrapper后可以直接填写到selectPage函数第二个参数里(原来为null)非常巧妙
我们用postman输入参数尝试查询结果成功,注意此时我们并没有更改任何前端
我们成功后即可用v-model绑定前端的搜索框
然后写入data()(默认为空),然后在fetch函数里添加username字段
绑定前端搜索按钮事件为重新渲染数据
第一个搜索框的功能已经激活成功
扩展多条件查询(将其它两个搜索框也激活)
总结上面一个框激活的流程:
1、在接口里增加查询的参数,并且增加querymapper对象的方法
2、用v-model绑定搜索框数据,并且绑定按钮渲染函数
3、在后端data增加参数,并且增加fectch渲染函数的参数
清楚了流程,我们做起来就事半功倍了
至此多条件查询已激活所有搜索框
第六部分
激活新增,批量删除,导入,导出四个按钮
驼峰映射是自动的,它会把下划线去掉并把下划线原来后面的字母大写
如:user_name->userName
用postman测试如果觉得复杂,可以用swaggerUI(一个接口测试文档)
(94条消息) SpringBoot集成swagger-ui以及swagger分组显示_程序员青戈的博客-CSDN博客_swagger ui 分组 第一步:
导入swagger依赖:
创建swaggerConfig类:
更改包的地址:
我觉得还是postman好用
使用axios代替fetch (fetch太繁琐)
(94条消息) Vue项目搭建常用的配置文件,request.js和vue.config.js_程序员青戈的博客-CSDN博客_vue config.headers配置
安装axios
npm i axios -s
新建utils文件夹,并新建js文件
黏贴封装的request.js代码:
import axios from 'axios'
const request = axios.create({
baseURL: '/api', // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
timeout: 5000
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
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 拦截器
// 可以在接口响应后统一处理结果
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) // for debug
return Promise.reject(error)
}
)
export default request
设置request为全局变量:
在vue2中用prototype设置,vue3中可以用app.config.globalProperties
来代替prototype
(94条消息) vue 3.0 prototype 替代用法_寻ing的博客-CSDN博客_vue3 prototype
我们使用axios后可以看到前后代码对比
使用前:
fetch("http://localhost:8081/page?pageNum="+this.pageNum+"&pageSize="+this.pageSize+"&username="+this.username
+"&email="+this.email+"&address="+this.address).then(res => res.json()).then(res => {
console.log(res)
this.tableData=res.data
this.total=res.total
})
使用后:
request.get("http://localhost:8081/page",{
params:{
pageNum:this.pageNum,
pageSize:this.pageSize,
username:this.username,
email:this.email,
address:this.address
}
}).then(res => {
console.log(res)
this.tableData= res.data
this.total = res.total
})
变得更加简洁,而且达到相同的功能效果
在查询后为了回复原来的页面,增加一个重置按钮
编写reset方法,思路就是把表单清空,并重新渲染页面,超级简单
reset(){
this.username=""
this.email=""
this.address=""
this.load()
}
dialog对话框编写:
在elementplus官网黏贴代码
Dialog 对话框 | Element Plus (element-plus.org)
<el-dialog
v-model="dialogVisible"
title="Tips"
width="30%"
:before-close="handleClose"
>
<span>This is a message</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</span>
</template>
</el-dialog>
选择下面的嵌套表单的代码
<el-dialog v-model="dialogTableVisible" title="Shipping address">
<el-table :data="gridData">
<el-table-column property="date" label="Date" width="150" />
<el-table-column property="name" label="Name" width="200" />
<el-table-column property="address" label="Address" />
</el-table>
</el-dialog>
<!-- Form -->
<el-button text @click="dialogFormVisible = true">
open a Form nested Dialog
</el-button>
<el-dialog v-model="dialogFormVisible" title="Shipping address">
<el-form :model="form">
<el-form-item label="Promotion name" :label-width="formLabelWidth">
<el-input v-model="form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="Zones" :label-width="formLabelWidth">
<el-select v-model="form.region" placeholder="Please select a zone">
<el-option label="Zone No.1" value="shanghai" />
<el-option label="Zone No.2" value="beijing" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogFormVisible = false">
Confirm
</el-button>
</span>
</template>
</el-dialog>
绑定新增按钮的事件handleAdd
编写确认按钮的事件save
在这之前要先把接口写好
这里写一个业务层的save接口,然后放在controller里调用即可
然后我们设置id为自增规则,并且表数据为倒叙排序,此时我们新增数据功能就完成了:
注意:若想让数据按id倒叙排序,不能并列式的写LambdaQuery,要单独起一行
我们输入数据即可在数据库中插入数据(id不是随机且自增)
接下来我们来实现编辑按钮:
在vue3中不能使用 slot-scope 在vue3中用#相当于vue2中的slot=
(94条消息) vue3的slot插槽用法,`slot-scope` are deprecated_浩星的博客-CSDN博客_`slot-scope` are deprecated
这个时候我们就可以打开这个数据的el-dialog 点击保存即可完成编辑
删除:
编写业务层接口:
这里写错了,应该用RemoveById方法
编写控制层的接口:
绑定删除按钮 注意要加id,血的教训
增加一个气泡弹出窗口用来确定是都真正删除
<!-- 确认组件-->
<el-popover :visible="visible" placement="top" :width="160" class="ml-5">
<p>您确定删除吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="small" text @click="visible = false">我再想想</el-button>
<el-button size="small" type="primary" @click="handleDelete(scope.row.id)"
>确认</el-button
>
</div>
<template #reference>
<el-button type="danger" >删除
<el-icon><remove></remove></el-icon>
</el-button>
</template>
</el-popover>
注意不要忘了在templete标签里写#reference 不然按钮会不显示
多选:
在e-table标签下加入一行代码,并且在e-table标签里写入
@selection-change="handleSelectionChange
之后编写批量删除的接口
//批量删除的接口
@DeleteMapping("/del/batch")
public boolean deleteBatch(@RequestBody List<Integer> ids){
return userService.removeByIds(ids);
}
在vue里编写delBatch方法
delBatch(){
let ids = this.multipleSelection.map(v => v.id)
console.log(ids)//[{}.{}.{}] => [1,2,3]
request.delete("http://localhost:8081/del/batch",{data:ids}).then(res => {
if(res){
this.$message.success("批量删除成功")
}else{
this.$message.error(("批量删除失败"))
}
this.load();
})
}
设置批量删除气泡弹窗,并绑定事件
<!-- 确认组件-->
<el-popover :visible="visible" placement="top" :width="160" class="ml-5">
<p>您确定批量删除数据吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="small" text @click="visible = false">我再想想</el-button>
<el-button size="small" type="primary" @click="delBatch"
>确认</el-button
>
</div>
<template #reference>
<el-button type="danger" >批量删除
<el-icon><remove></remove></el-icon>
</el-button>
</template>
</el-popover>
效果:
优化:
发现:如果不把load()重新加载数据放在最后一行,在新增操作保存后多选数据气泡窗口可能不显示
新增多选按钮,点击出现多选框,再次点击隐藏
给多选框绑定v-if
<el-table-column v-if="letapear" type="selection" width="55" />
将letapear初始值默认为false
设置多选按钮 用双括号绑定名称
<!-- 多选按钮-->
<el-button type="info" @click="Apearselection">{{duoxuan}}
<el-icon><CirclePlus></CirclePlus></el-icon>
</el-button>
编写方法:
Apearselection(){
if(!this.letapear){
this.letapear=true;
this.duoxuan="取消多选"
}else{
this.letapear=false;
this.duoxuan="多选"
}
}
效果:
---------------->
第七部分
1、封装侧边栏
新建一个Aside.vue将以下代码(e-menu)从menage.vue移动到里面
<el-menu :default-openeds="['1', '3']" style="height: 100%;overflow-x:hidden;border-right-color: transparent"
background-color="rgb(48,65,86)"
text-color="#fff"
active-text-color="#ffd04b"
: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 v-show="!isCollapse" style="color: white">后台管理系统</b>
</div>
<el-sub-menu index="1">
<template #title>
<el-icon>
<message></message>
</el-icon>
<span>导航一</span>
</template>
<el-menu-item-group>
<template #title>Group 1</template>
<el-menu-item index="1-1">Option 1</el-menu-item>
<el-menu-item index="1-2">Option 2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group 2">
<el-menu-item index="1-3">Option 3</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title>Option4</template>
<el-menu-item index="1-4-1">Option 4-1</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon>
<Menu></Menu>
</el-icon>
<span>导航二</span>
</template>
<el-menu-item-group>
<template #title>Group 1</template>
<el-menu-item index="2-1">Option 1</el-menu-item>
<el-menu-item index="2-2">Option 2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group 2">
<el-menu-item index="2-3">Option 3</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="2-4">
<template #title>Option 4</template>
<el-menu-item index="2-4-1">Option 4-1</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>
<el-icon>
<Setting></Setting>
</el-icon>
<span>导航三</span>
</template>
<el-menu-item-group>
<template #title>Group 1</template>
<el-menu-item index="3-1">Option 1</el-menu-item>
<el-menu-item index="3-2">Option 2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group 2">
<el-menu-item index="3-3">Option 3</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="3-4">
<template #title>Option 4</template>
<el-menu-item index="3-4-1">Option 4-1</el-menu-item>
</el-sub-menu>
</el-sub-menu>
</el-menu>
然后再AsideView里面编写props将用到的原来组件的参数传入进来,并且编写components将原来用到的组件传递过来
然后在原来的界面引入AsideView组件,并且使用该组件
e-side我们就转移完毕了
然后我们转移e-header
将e-header代码转移到HeaderView里面,然后将style样式做部分迁移,因为转移后,都放在外面不好使。
<div style="text-align: left; font-size: 12px; line-height: 60px;
display: flex">
<div style="flex:1;font-size: 18px">
<el-icon style="cursor: pointer" @click="collapse">
<Fold v-if="isCollapse"></Fold>
<Expand v-else></Expand>
</el-icon>
</div>
<div class="toolbar">
<el-dropdown style="width: 100px;cursor: pointer" >
<span style="line-height: 60px">Tom
<el-icon style="margin-left: 5px">
<ArrowDownBold></ArrowDownBold>
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
<el-dropdown-item>Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
然后本来collapse方法在原来的主vue里面,所以我们要定义一个方法,并且通过$emit
将本组件的请求发送过去:
然后再在主vue引入我们的header,并且在header里面连接方法
这样header就拆装完毕了
之后我们再把e-main拿出来单独封装
建立一个TableView.vue
之后将e-main里面的代码放在里面
<template>
<div style="margin-bottom: 30px">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
<el-breadcrumb-item :to="{path: '/new'}">用户管理</el-breadcrumb-item>
<el-breadcrumb-item>promotion list</el-breadcrumb-item>
<el-breadcrumb-item>promotion detail</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div style="margin: 10px 0;text-align: left" >
<!-- 名称搜索-->
<el-input style="width: 200px" type="text" placeholder="请输入名称" v-model="username">
<template #prefix>
<el-icon class="el-input__icon">
<search></search>
</el-icon>
</template>
</el-input>
<!-- 邮箱输入-->
<el-input style="width: 200px" type="text" placeholder="请输入邮箱" class="ml-5" v-model="email">
<template #prefix>
<el-icon class="el-input__icon">
<message></message>
</el-icon>
</template>
</el-input>
<!-- 地址搜索-->
<el-input style="width: 200px" type="text" placeholder="请输入地址" class="ml-5" v-model="address">
<template #prefix>
<el-icon class="el-input__icon">
<location></location>
</el-icon>
</template>
</el-input>
<!-- 搜索按钮-->
<el-button class="ml-5" type="" style="color: orangered" @click="load">搜索</el-button>
<el-button class="mr-5" type="warning" style="color: white" @click="reset">重置</el-button>
</div>
<div style="margin: 10px 0;text-align: left">
<el-button type="primary" @click="handleAdd">新增
<el-icon><CirclePlus></CirclePlus></el-icon>
</el-button>
<!-- 多选按钮-->
<el-button type="info" @click="Apearselection">{{duoxuan}}
<el-icon><CirclePlus></CirclePlus></el-icon>
</el-button>
<!-- 确认组件-->
<el-popover :visible="visible" placement="top" :width="160" class="ml-5">
<p>您确定批量删除数据吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="small" text @click="visible = false">我再想想</el-button>
<el-button size="small" type="primary" @click="delBatch"
>确认</el-button
>
</div>
<template #reference>
<el-button type="danger" >批量删除
<el-icon><remove></remove></el-icon>
</el-button>
</template>
</el-popover>
<el-button type="primary">导入
<el-icon><bottom></bottom></el-icon>
</el-button>
<el-button type="primary">导出
<el-icon><top></top></el-icon>
</el-button>
</div>
<!-- <el-scrollbar>-->
<el-table :data="tableData" border stripe height="500px" max-height="350px"
@selection-change="handleSelectionChange">
<!-- 多选框-->
<el-table-column v-if="letapear" type="selection" width="55" />
<el-table-column prop="id" label="ID" width="50" />
<el-table-column prop="username" label="用户名" width="140" />
<el-table-column prop="nickname" label="昵称" width="120" />
<el-table-column prop="address" label="地址" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="phone" label="电话" />
<el-table-column label="操作" >
<el-table-column>
<template #default="scope">
<el-button type="success" @click="handleEdit(scope.row)" >编辑
<el-icon><edit></edit></el-icon>
</el-button>
<!-- 确认组件-->
<el-popover :visible="visible" placement="top" :width="160" class="ml-5">
<p>您确定删除吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="small" text @click="visible = false">我再想想</el-button>
<el-button size="small" type="primary" @click="handleDelete(scope.row.id)"
>确认</el-button
>
</div>
<template #reference>
<el-button type="danger" >删除
<el-icon><remove></remove></el-icon>
</el-button>
</template>
</el-popover>
</template>
</el-table-column>
</el-table-column>
</el-table>
<!-- 页尾-->
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[2, 5, 10, 20]"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<!-- </el-scrollbar>-->
<el-dialog title="用户信息" v-model="dialogFormVisible" width="30%">
<el-form :model="form" label-width="80px" size="small">
<el-form-item label="用户名" >
<el-input v-model="form.username" autocomplete="off" />
</el-form-item>
<el-form-item label="昵称" >
<el-input v-model="form.nickname" autocomplete="off" />
</el-form-item>
<el-form-item label="邮箱" >
<el-input v-model="form.email" autocomplete="off" />
</el-form-item>
<el-form-item label="电话" >
<el-input v-model="form.phone" autocomplete="off" />
</el-form-item>
<el-form-item label="地址" >
<el-input v-model="form.address" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false,this.load">取消</el-button>
<el-button type="primary" @click="save">
确认
</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import {Bottom, CirclePlus, Edit, Location, Message, Remove, Search, Top} from "@element-plus/icons";
export default {
name: "TableView",
components: {
Edit,
Top,
Bottom,
Remove,
CirclePlus,
Location, Search,
Message,
},
data(){
// const item = {
// date: '2016-05-02',
// name: 'Tom',
// address: 'No. 189, Grove St, Los Angeles',
// };
return {
// tableData : (Array.from({ length: 20 }).fill(item)),
tableData :[],
pageNum:1,
pageSize:2,
total:0,
username:"",
email:"",
address:"",
isCollapse:true,
sidewidth:200,
dialogFormVisible:false,
form:{},
multipleSelection:[],
letapear:false,
duoxuan:"多选"
}
},
created() {
//请求分页查询数据
this.load()
},
methods:{
load(){
this.request.get("http://localhost:8081/page",{
params:{
pageNum:this.pageNum,
pageSize:this.pageSize,
username:this.username,
email:this.email,
address:this.address
}
}).then(res => {
console.log(res)
this.tableData= res.data
this.total = res.total
})
},
save(){
this.request.post("http://localhost:8081/save",this.form).then(res => {
if(res){
this.$message.success("保存成功")
}else{
this.$message.error(("保存失败"))
}
this.load();
this.dialogFormVisible=false;
})
},
handleSelectionChange(val){
// console.log(val)
this.multipleSelection = val;
},
handleAdd(){
this.dialogFormVisible=true;
this.form={}
},
handleEdit(row){
this.dialogFormVisible=true;
this.form = Object.assign({},row);
},
handleDelete(id){
this.request.delete("http://localhost:8081/"+id).then(res => {
if(res){
this.$message.success("删除成功")
}else{
this.$message.error(("删除失败"))
}
this.load();
})
},
delBatch(){
let ids = this.multipleSelection.map(v => v.id)
console.log(ids)//[{}.{}.{}] => [1,2,3]
this.request.delete("http://localhost:8081/del/batch",{data:ids}).then(res => {
if(res){
this.$message.success("批量删除成功")
}else{
this.$message.error(("批量删除失败"))
}
this.load();
})
},
Apearselection(){
if(!this.letapear){
this.letapear=true;
this.duoxuan="取消多选"
}else{
this.letapear=false;
this.duoxuan="多选"
}
},
reset(){
this.username=""
this.email=""
this.address=""
this.load()
},
handleSizeChange(pageSize){
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum){
this.pageNum=pageNum
this.load()
}
}
}
</script>
<style scoped>
</style>
再把meanagor组件的大部分方法和属性和组件(除了折叠的方法,和用到的属性,其它都迁移到TableView里面)
然后把TableView放在路由里面:作为
它的子路由,这样就可以通过主路由加载子路由的方式拼装整个页面,这样侧边栏就会一直出现在页面里供我们操作
我们也定义一个home放在里面当子路由:
设置重定向:
之后我们就可以通过路由入口的方式把路由入口router-view放在父组件需要加载子组件的地方
这样我们就可以通过访问子路由的方式 让整个页面加载
至此我们就把整个menager组件的所有功能拆分开成不同的组件,这样方便管理,方便维护更改
然后我们设置侧边栏点击可以跳转我们之前设置的两个路由界面:home,table
这个很简单我们只需要更改el-menu-item标签里的 index即可
不过在此之前一定要记得把e-menu里面的router属性自己手动设为true ,它默认为false
这样我们就实现了点击侧边栏跳转到我们设置的路由
之后我们实现导入导出功能
这个功能要依赖于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>
hutool官网;Hutool — 🍬A set of tools that keep Java sweet.
在getwriter参数里面可以写输出路径,我们这里不写,为了在浏览器直接点击下载
在控制类里封装一个export接口
//导出的接口
@GetMapping("/export")
public void export(HttpServletResponse response)throws Exception{
//从数据库中查出所有的数据
List<User>list = userService.list();
//通过工具类创建writer 写出到磁盘路径
// ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath+"/用户信息.xlsx");
//在内存操作,写出到浏览器
ExcelWriter writer = new ExcelUtil().getWriter(true);
//自定义标签别名
writer.addHeaderAlias("username","用户名");
writer.addHeaderAlias("password","密码");
writer.addHeaderAlias("nickname","昵称");
writer.addHeaderAlias("email","邮箱");
writer.addHeaderAlias("address","地址");
writer.addHeaderAlias("createTime","创建时间");
writer.addHeaderAlias("avatarUrl","头像");
//一次性写出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();
}
第八部分
设置登陆注册页面
登录页面:
(95条消息) Vue3+SpringBoot实现【登录】【毛玻璃】【渐变色】_白开水为啥没味的博客-CSDN博客
注册页面
复制登陆界面作为注册界面,给注册界面添加路由,编写接口
增加确认密码,并更改按钮,添加事件,具体代码如下
<template>
<div class="wrapper">
<div class="logincss" style="margin: 150px auto; 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 :rules="rules" label-width="auto">
<!--应该让前端的校验都通过之后,才可以触发登陆按钮,否则没有太大意义做表单校验提示-->
<el-form-item label="用户名" prop="username" >
<el-input placeholder="请输入账号" size="default" v-model="user.username" >
<template #prefix>
<el-icon class="el-input__icon">
<user></user>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input placeholder="请输入密码" size="default" show-password v-model="user.password" >
<template #prefix>
<el-icon class="el-input__icon">
<lock></lock>
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- 确认密码-->
<el-form-item label="确认密码" prop="confirmPassword" >
<el-input placeholder="请确认密码" size="default" show-password v-model="user.confirmPassword" >
<template #prefix><el-icon class="el-input__icon"><lock></lock></el-icon></template>
</el-input>
</el-form-item>
<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>
</el-form>
</div>
</div>
</template>
<script>
import {Lock, User} from "@element-plus/icons";
export default {
name: "LoginView",
components: {Lock, User},
data(){
return {
user: {},
rules: {
username: [
{required: true, message: '请输入用户名', trigger: 'blur'},
{min: 3, max: 5, message: '名称长度应该为3到5', trigger: 'blur'},
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 3, max: 5, message: '密码长度应该为3到5', trigger: 'blur'},
],
confirmPassword: [
{required: true, message: '请再次输入密码', trigger: 'blur'},
{min: 3, max: 5, message: '密码长度应该为3到5', trigger: 'blur'},
],
}
}
},
methods:{
register(){
if (this.user.password !== this.user.confirmPassword){
this.$message.error("两次输入的密码不一致")
return false
}
this.request.post("http://localhost:8081/register",this.user).then(res =>{
// if(!res){
// this.$message.error("用户名或密码错误")
// }else {
// this.$message.success("登陆成功")
// this.$router.push("/new/home")
// }
if(res.code === '200'){
this.$message.success("注册成功")
}else {
this.$message.error(res.msg)
}
})
}
}
}
</script>
<style scoped>
.wrapper{
height: 100vh;
/*红蓝渐变*/
/*background-image: linear-gradient(to bottom right,#FC466B,#3F5EFB);*/
background-image: linear-gradient(to bottom right, #9b1919, #3ffbf5);
overflow:hidden;
}
.logincss{
background-color:rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
}
.e-form-item label {
}
</style>
绑定头像和昵称:
首先在数据库添加字段:
注意要按照驼峰映射来命名,如数据库中用两个小写单词中间用下划线隔开命名
实体类中的属性名称用小写单词加大写单词命名
在实体类添加字段
user
userDto
绑定前端数据(头像图片和用户昵称)
编写获取user对象 信息(账号,邮箱,昵称,头像连接)
注意,在退出的时候清楚缓存
效果:
组件命名最好符合驼峰映射:
驼峰映射: