Vue.js
概述
- Vue是一个专注于构建web用户界面的 JavaScript 库
- 作者:尤雨溪,中国人,早前就职于 Google
- 思想:MVVM(Model-View-ViewModel)
- 一个渐进式的框架
使用
1.基础案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
{msg}
</div>
<script>
var app = new Vue({
el: "#app",
data:{
msg:'hello'
}
});
</script>
</body>
</html>
el:是一个必不可少的属性,用来指定一个页面中已存在的Dom元素来挂载vue实例,其值可以是一个dom元素,也可以是css选择器
data:存放数据的对象
{}*2:文本插值,类似于:innerText,会把数据原封不动的显示出来,其中的值可以是JavaScript的表达式
Vue3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VueDemo</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
{msg}
</div>
<script>
const config = {
data(){
return {
msg:"hello"
}
}
}
//Vue.createApp(config):基于config配置对象,创建一个Vue应用实例
//mount("#app"):把Vue实例挂载到id为app的div上
const root = Vue.createApp(config).mount("#app");
</script>
</body>
</html>
2.v-html
v-html:会对数据中的html内容进行解析,显示解析后的结果,类似于Dom的 innerHTML
v-text: 不会对数据中的html内容进行解析,类似于dom中的 innerText
<div id="app">
{msg}<br>
<p v-text="msg"></p>
<p v-html="msg"></p>
</div>
<script>
var app = new Vue({
el: "#app",
data:{
msg:'<span style="color:red;">hello</span>'
}
});
</script>
3.v-bind
动态绑定一个或多个属性(简写形式: 冒号)
<div id="app">
<p :align="align" v-html="msg"></p>
</div>
<script>
var app = new Vue({
el: "#app",
data:{
msg:'<span style="color:red;">hello</span>',
align:'right'
}
});
</script>
4.v-model
绑定表单数据,双向绑定
<div id="app">
<input type="text" v-model="msg">
</div>
<script>
var app = new Vue({
el: "#app",
data:{
msg:'<span style="color:red;">hello</span>',
}
});
</script>
5.v-on
绑定事件处理程序(简写:@)
<div id="app">
<input type="text" :value="msg">
<input type="button" value="按钮" @click="del">
</div>
<script>
var app = new Vue({
el: "#app",
data:{
msg:'<span style="color:red;">hello</span>',
},
methods:{
del(){
console.log(100);
}
}
});
</script>
6.v-show
是否显示元素
当 v-show的值为:0、空字符串、undefined、null、false、NaN时候隐藏,其他显示
<p v-show="isShow">可能隐藏的内容</p>
data:{
isShow:null
}
7.v-if、v-else-if、v-else
<p v-if="isShow">可能隐藏的内容</p>
<p v-else-if="elseIf">else if 部分</p>
<p v-else>else 部分</p>
data:{
isShow:0,
elseIf:0
}
v-if和v-show区别
v-if:如果为false,删除dom元素,为true:创建dom元素
v-show:用来显示和隐藏元素
8.v-for
循环指令
<p v-for="(name,index) in arr" >{index}-{name}</p>
data:{
arr:["tom","marry","scott"]
}
html模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
}
});
</script>
</body>
</html>
省市级联案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
省份:
<select @change="getCity" v-model="province">
<option>请选择</option>
<option v-for="(item,index) in map.keys()">{item}</option>
</select>
城市:
<select>
<option>请选择</option>
<option v-for="(item,index) in cities">{item}</option>
</select>
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
map: new Map([
["辽宁",["沈阳","大连"]],
["吉林",["长春","四平"]],
["黑龙江",["哈尔滨","齐齐哈尔"]]
]),
province:'请选择',
cities:[]
},
methods:{
getCity(){
this.cities = this.map.get(this.province);
}
}
});
</script>
</body>
</html>
移动列表选中项案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<select size="10" multiple="multiple" v-model="leftSelected">
<option v-for="n in leftList" >{n}</option>
</select>
<input type="button" value="=>" @click="toRight">
<input type="button" value="<=" @click="toLeft">
<select size="10" multiple="multiple">
<option v-for="n in rightList" :value="n">{n}</option>
</select>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
leftList:[1,2,3,4,5,6,7,8],
rightList:[10,20],
leftSelected:[2,3]
},
methods:{
toRight(){
for(n of this.leftSelected){
this.rightList.push(n);
}
var temp = this.leftList.filter(n=>!this.leftSelected.includes(n));
this.leftList = temp;
},
toLeft(){
}
}
});
</script>
</body>
</html>
vue3实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuedemo</title>
<script src="js/vue.js"></script>
<style>
select{
width:40px;
height: 200px;
}
</style>
</head>
<body>
<div id="app">
<select multiple size="15" v-model="leftSel">
<option v-for="n in left">{n}</option>
</select>
<input type="button" value="=>" @click="toRight">
<input type="button" value="<=" @click="toLeft">
<select multiple size="15" v-model="rightSel">
<option v-for="n in right">{n}</option>
</select>
</div>
<script>
//vue的配置对象
let config = {
data(){
return {
left:['1','2','3','4','5','6','7','8','9'],
right:[],
leftSel:[],
rightSel:[]
}
},
methods:{
toRight(){
for(let i = 0;i < this.leftSel.length;i++){
this.right.push(this.leftSel[i])
let index = this.left.indexOf(this.leftSel[i]);
this.left.splice(index,1)
}
this.leftSel.length = 0
},
toLeft(){
for(let i = 0;i < this.rightSel.length;i++){
this.left.push(this.rightSel[i])
let index = this.right.indexOf(this.rightSel[i]);
this.right.splice(index,1)
}
this.rightSel.length = 0
}
}
}
//createApp方法返回一个应用程序对象
const app = Vue.createApp(config);
const p = app.mount("#app")
</script>
</body>
</html>
<script>
</script>
vue绑定复选框
<input type="checkbox" v-model="all" value="1">
如果all为数组,选中复选框,value值在数组中,没有选中,value值不在数组中
data:{
all:[]
}
如果all不是数组,选中复选框,all的值为true,不选中,为false
data:{
all:true
}
vue绑定下拉列表框
<select v-model="s" multiple>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
如果有multiple属性,需要绑定一个数组,数组中的元素被选中
data:{
s:[]
}
如果没有multiple属性,需要绑定单个值,value等于该值的被选中,没有value属性的,value属性等于innerText
data:{
s:'2'
}
vue绑定单选按钮
<input type="radio" value="1" v-model="r">
<input type="radio" value="2" v-model="r">
单选按钮需要绑定到单个值,不能绑定到数组,当v-model的值与value值相等的时候,单选按钮被选中
data:{
r:'1'
}
Vue动态添加属性
默认情况下,vue不跟踪动态添加的属性
<div id="app">
{stu.name}
<input type="button" value="动态添加属性" @click="addAttr">
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
stu:{}
},
methods:{
addAttr(){
// this.stu.name ="tom";//不行
// this.$set(this.stu,"name","tom")//可以
Vue.set(this.stu,"name","tom");
}
}
});
</script>
综合案例:员工管理
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
编号:<input type="text" v-model="dept.deptno"><br>
名称:<input type="text" v-model="dept.dname"><br>
地址:<input type="text" v-model="dept.loc"><br>
<input type="button" value="添加" @click="add"><br>
<table border="1" width="500">
<tbody >
<tr v-for="dept,index in depts">
<td>{dept.deptno}</td>
<td v-if="dept.edit">
<input type="text" v-model="tempDept.dname" size="4">
</td>
<td v-else>{dept.dname}</td>
<td v-if="dept.edit">
<input type="text" v-model="tempDept.loc" size="4">
</td>
<td v-else>{dept.loc}</td>
<td>
<input type="button" value="删除" @click="del(index)">
<input type="button" :value="dept.editBtnValue" @click="handleClick(dept,index)">
<input type="button" value="取消" @click="cancel(dept,index)">
</td>
</tr>
</tbody>
</table>
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
depts:[],
dept:{},
tempDept:{},
editBtnValue:"编辑"
},
created(){
//模仿ajax操作,得到部门集合
this.depts = [
{"deptno":10,"dname":"市场部","loc":"沈阳","editBtnValue":"编辑"},
{"deptno":20,"dname":"生产部","loc":"大连","editBtnValue":"编辑"},
{"deptno":30,"dname":"供应部","loc":"北京","editBtnValue":"编辑"}
]
},
methods:{
del(index){
if(confirm("是否删除?")){
this.depts.splice(index,1);
}
},
add(){
this.depts.push(this.dept);
this.dept = {};
},
handleClick(dept,index){
if(dept.editBtnValue == "编辑"){
this.$set(dept,"edit",true)
dept.editBtnValue = "保存";
this.tempDept.dname = dept.dname;
this.tempDept.loc = dept.loc;
}else{
dept.edit = false;
dept.editBtnValue = "编辑";
dept.dname = this.tempDept.dname;
dept.loc = this.tempDept.loc;
}
},
cancel(dept,index){
dept.edit = false;
dept.editBtnValue = "编辑";
}
}
});
</script>
</body>
</html>
vue3实现
<template>
员工管理
<table border="1" width="500">
<tr v-for="emp,index in emps" :key="emp.empno">
<template v-if="emp.edit">
<td>{emp.empno}</td>
<td><input type="text" v-model="copyEmps[index].ename"></td>
<td><input type="text" v-model="copyEmps[index].sal"></td>
<td>
<input type="button" value="保存" @click="update(index)">
<input type="button" value="取消" @click="cancel(index)">
</td>
</template>
<template v-else>
<td>{emp.empno}</td>
<td>{emp.ename}</td>
<td>{emp.sal}</td>
<td>
<input type="button" value="编辑" @click="edit(index)">
<input type="button" value="删除" @click="del(index)">
</td>
</template>
</tr>
</table>
</template>
<script>
export default {
name: "Emp",
data(){
return {
emps:[
{empno:1,ename:"tom",sal:3000},
{empno:2,ename:"scott",sal:2000},
{empno:3,ename:"marry",sal:3300}
],
copyEmps:[]
}
},
created() {
//初始化的时候,复制员工数组
let s = JSON.stringify(this.emps);
this.copyEmps = JSON.parse(s);
},
methods:{
edit(index){
this.emps[index].edit = true
},
update(index){
this.emps[index].edit = false
this.emps[index] = {...this.copyEmps[index]}//克隆元素
},
cancel(index){
this.emps[index].edit = false
this.copyEmps[index] = {...this.emps[index]}
},
del(index){
if(confirm("是否删除")){
this.emps.splice(index,1)
this.copyEmps.splice(index,1)
}
}
}
}
</script>
<style scoped>
</style>
计算属性
作用:处理一些复杂逻辑
计算属性将基于它们的响应依赖关系缓存。
计算属性只会在相关响应式依赖发生改变时重新求值。
<div id="app">
商品单价:<input type="text" v-model="price"><br>
商品数量:<input type="text" v-model="num"><br>
总额:{total}
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
username:"marry",
price:'',
num:''
},
computed:{
un(){
return this.username.toUpperCase();
},
total(){
return this.price * this.num;
}
}
});
</script>
vue3
<div id="app">
商品单价:<input type="text" v-model="price"><br>
商品数量:<input type="text" v-model="num"><br>
总额:{total}
</div>
computed:{
total(){
return this.total = this.price * this.num;
}
}
上面的方式会产生一个警告:
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
商品单价:<input type="text" v-model="price"><br>
商品数量:<input type="text" v-model="num"><br>
总额:{total}
</div>
<script>
const config = {
name:"root",
data(){
return {
price:'',
num:''
}
},
computed:{
total:{
get(){
return this.total = this.price * this.num;
},
set(newValue){
}
}
}
}
//Vue.createApp(config):基于config配置对象,创建一个Vue应用实例
//mount("#app"):把Vue实例挂载到id为app的div上
const app = Vue.createApp(config)
const vm = app.mount("#app");
</script>
</body>
监听属性
可以通过watch来响应数据的变化
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
<div id="app">
<input type="text" v-model="km">千米<br>
<input type="text" v-model="m">米<br>
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
km:'',
m:''
},
watch:{
km(newValue,oldValue){
this.m = newValue * 1000;
},
m(newValue,oldValue){
this.km = newValue/1000;
}
}
});
</script>
监听对象
<body>
<div id="app">
商品单价:<input type="text" v-model="good.price"><br>
商品数量:<input type="text" v-model="good.num"><br>
总额:<span v-if="!isNaN(good.total)">{good.total}</span>
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
good:{
}
}
watch:{
good:{
//handle方法名固定
handler(newValue,oldValue){
newValue.total = newValue.price * newValue.num;
},
deep:true
}
}
});
</script>
</body>
vue3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
商品单价:<input type="text" v-model="good.price"><br>
商品数量:<input type="text" v-model="good.num"><br>
总额:<span v-if="!isNaN(good.total)">{good.total}</span>
</div>
<script>
const config = {
name:"root",
data(){
return {
good:{}
}
},
watch:{
good:{
handler(newValue,oldValue){
this.good.total = this.good.price * this.good.num;
},
deep:true
}
}
}
const app = Vue.createApp(config)
const vm = app.mount("#app");
</script>
</body>
</html>
v-model参数特性
lazy:在默认情况下,v-mode在每次 input 事件触发后,将输入框的值与数据同步,可以使用 lazy 修饰符,从而转变为使用 change 事件进行同步
<body> <div id="app"> <input type="text" v-model.lazy="km">千米<br> <input type="text" v-model="m">米<br> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ km:'', m:'' }, watch:{ km(newValue,oldValue){ this.m = newValue * 1000; }, m(newValue,oldValue){ this.km = newValue/1000; } } }); </script> </body>
number:如果想自动将用户输入值类型转换为数值类型,可以使用number修饰符
可通过vue开发者工具查看对象数据类型
<input type="text" v-model.number="km">千米<br>
trim:自动过滤用户输入的首尾空白符,使用trim去掉首尾空白符
可通过vue开发者工具查看对象数据类型
<div id="app"> <input type="text" v-model.trim="msg"> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ msg:'tom' } }); </script>
样式绑定
绑定 class 类样式(值是一个对象)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/vue.js" type="text/javascript" charset="utf-8"></script> <style> .class1{ color:red; } .class2{ font-size: 40px; } </style> </head> <body> <div id="app"> <div :class="{class1:class1Style,class2:class2Style}"> 动态样式 </div> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ class1Style:true, class2Style:true } }); </script> </body> </html>
可以把一个数组传递给class属性
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/vue.js" type="text/javascript" charset="utf-8"></script> <style> .class1{ color:red; } .class2{ font-size: 40px; } </style> </head> <body> <div id="app"> <div :class="[class1Style,class2Style]"> 动态样式 </div> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ class1Style:'class1', class2Style:"class2" } }); </script> </body> </html>
style对象语法
<div id="app"> <div :style="{'color':color,'font-size':size}"> 动态样式 </div> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ color:'blue', size:'50px' } }); </script>
style数组语法
<div id="app"> <div :style="[style1,style2]"> 动态样式 </div> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ style1:{'color':'red'}, style2:{'font-size':'20px'} } }); </script>
定时器使用案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<div>{name}</div>
<input type="button" value="开始" @click="startGame">
<input type="button" value="停止" @click="stopGame">
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
name:'请选择',
arr:["唐僧","悟空","八戒","沙僧","小白龙"],
t:'',
index:0
},
methods:{
startGame(){
this.index = parseInt(Math.random()*this.arr.length);
this.name = this.arr[this.index];
this.t = setTimeout(this.startGame,100);
},
stopGame(){
clearTimeout(this.t);
this.arr.splice(this.index,1);
}
}
});
</script>
</body>
</html>
vue3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>摇奖</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
#style1{
color:blue;
font-size: 180px;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div id="style1">{name}</div>
<input type="button" value="开始" @click="startGame" ref="startGame">
<input type="button" value="结束" @click="endGame" ref="endGame">
</div>
<script>
const config = {
data(){
return {
stus:["张三","李四","王五","赵六","马七"],
name:'请选择',
t:null,
index:'',
b:false
}
},
methods:{
startGame(){
this.$refs.endGame.focus();
if(this.stus.length > 0){
this.index = parseInt(Math.random()*this.stus.length)
this.name = this.stus[this.index];
this.t = setTimeout(this.startGame,100)
}else{
alert("摇奖结束")
}
},
endGame(){
clearTimeout(this.t)
this.stus.splice(this.index,1)
this.$refs.startGame.focus();
}
},
mounted() {
this.$refs.startGame.focus();
}
}
Vue.createApp(config).mount("#app")
</script>
</body>
</html>
定时器练习
点击开始按钮,随机生成4个各不相同的数,并滚动显示
点击停止按钮的时候,求和
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vuedemo</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<div>
<input type="button" v-for="num in arr" :value="num">
和:{sum}
</div>
<input type="button" value="开始" @click="startGame">
<input type="button" value="结束" @click="stopGame">
</div>
<script>
//vue的配置对象
let config = {
data(){
return {
arr:[0,0,0,0],
t:-1,//定时器
sum:0
}
},
methods:{
startGame(){
this.arr.length = 0;
this.sum = ''
/*for(let i = 0;i < 4;i++){
let n = parseInt(Math.random()*10);
this.arr.push(n);
}*/
//生成各不相同的4个数
while(true){
let n = parseInt(Math.random()*10);
if(this.arr.indexOf(n) == -1){
this.arr.push(n);
}
if(this.arr.length == 4) break;
}
this.t = setTimeout(this.startGame,100)
},
stopGame(){
clearTimeout(this.t)
this.sum = 0;
for(let i = 0;i < 4;i++){
this.sum += this.arr[i]
}
}
}
}
//createApp方法返回一个应用程序对象
const app = Vue.createApp(config);
const p = app.mount("#app")
</script>
</body>
</html>
vue3键盘事件
Vue 提供了绝大多数常用的按键码的别名:
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
使用
@keyup.enter=showInfo
@keydown.tab=showInfo
登录案例
回车触发登录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
#style1{
color:blue;
font-size: 180px;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div @keyup.enter="login">
<input type="text" placeholder="手机号码">
<input type="text" placeholder="密码">
<button @click="login" >登录</button>
</div>
</div>
<script>
const config = {
data(){
return {
}
},
methods:{
login(){
console.log("login")
}
}
}
Vue.createApp(config).mount("#app")
</script>
</body>
</html>
游戏案例
在document对象上触发onkeydown事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>游戏</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
#style1{
color:blue;
font-size: 180px;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div id="style1">
请选择
</div>
</div>
<script>
const config = {
data(){
return {
}
},
methods:{
keyDown(e){
if(e.code == "Enter"){
this.startGame()
}else if(e.code == "Space"){
this.endGame()
}
},
startGame(){
alert("开始游戏")
},
endGame(){
alert("结束游戏")
}
},
created(){
document.addEventListener("keydown",this.keyDown);
}
}
Vue.createApp(config).mount("#app")
</script>
VueCli
vue.js开发的标准工具
Node.js:是一个基于 Chrome V8引擎的 JavaScript运行时环境(需要安装)
npm:是随着node.js一起安装的包管理工具,允许用户从npm服务器下载别人编写好的第三方包到本地
测试:npm –version
配置npm源:
npm config set registry http://192.168.1.190:5001/repository/dhee-group-npm/ 外网 npm config set registry http://registry.npm.taobao.org/
安装vuecli
npm i -g @vue/cli@4.5.13 //安装最新版 npm i -g @vue/cli
创建项目
vue create 项目名
单页面应用
在Idea中打开项目
选择上面创建好的Vue项目
运行项目
npm run serve
组件
定义:可以复用的Vue实例,且带有一个名字
使用
定义组件
const mybtn = { template: ` <button type="button" @click="add">点击 {num}</button> `, //data必须是一个方法 data(){ return{ num:0 } }, methods:{ add(){ this.num++; } } }; const mybtn = Vue.extend({ });
注册
全局注册
Vue.component("mybtn",mybtn);
局部注册
var app = new Vue({ el:"#app", data:{ }, components:{ "mybtn":mybtn//简写:mybtn } });
使用
<div id="app"> <mybtn></mybtn> </div>
vue3,在Vue-cli中全局注册
import { createApp } from 'vue' import App from './App.vue' import Demo1 from "./components/Demo1"; const app = createApp(App) //全局注册组件 app.component("Demo1",Demo1) app.mount('#app')
任意组件中使用
<template> <Demo1></Demo1> </template>
vue3局部注册同vue2相同
vue组件命名(非单文件组件中)
在模板中(挂载点)的标签名(组件名),使用短横线命名法(单词之间用短横线分隔),所有字符小写
template编写方法
直接写到vue实例对象中
使用标签
<template id="btn"> <button type="button" @click="add">点击 {num}</button> </template> <script type="text/javascript"> const mybtn = { template:"#btn", data(){ return{ num:0 } }, methods:{ add(){ this.num++; } } }; //全局注册 // Vue.component("myBtn",mybtn); var app = new Vue({ el:"#app", data:{ }, components:{ "myBtn":mybtn } }); </script>
父组件向子组件传值
使用 prop 属性向组件传值,在组件中定义属性接收值
//父组件 <my-btn num="10"></my-btn> //子组件 <template id="btn"> <button type="button" @click="add">点击 {num}</button> </template> const mybtn = { template:"#btn", data(){ return{ n:'' } }, created(){ this.n = this.num;//初始同步 }, methods:{ add(){ this.n++; } }, props:[ "num" ] };
在子组件中,可以向使用data的属性一样,来使用props中的属性
注意:在子组件中不要去改变属性的值
通过 自定义事件 子组件向父组件传值
在 子组件中 触发自定义事件(什么时候想向父组件传值,什么时候触发)
this.$emit("retval",this.n);
父组件中,订阅子组件触发的自定义事件
<my-btn :num="num" @retval="getValue($event)"></my-btn>
注意:自定义的事件名与组件命名规则相同
vue3
子组件
<template>
Demo1
<button @click="send">传递值</button>
</template>
<script>
export default {
name: "Demo1",
props:[
"num"
],
//声明可以触发的事件列表
emits:[
'retval'
],
data(){
return {
age:22
}
},
methods:{
send(){
this.$emit("retval",this.age);
}
}
}
</script>
<style scoped>
</style>
父组件
<template>
<Demo1 num="10" @retval="getAge($event)"></Demo1>
</template>
<script>
export default {
name: 'App',
components: {
},
methods:{
getAge(age){
console.log(age)
}
}
}
</script>
组件传值综合练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<div>
<child v-for="q in n" :endval="endval" :isstart="isstart" @myevent="getVal($event)" style="width:30px"></child>
<div>
<input type="button" value="开始" @click="isstart=true" >
<input type="button" value="停止" @click="isstart=false" >
和:{sum}
</div>
</div>
</div>
<template id="child">
<input type="button" v-model="num">
</template>
<script>
let child = {
template: "#child",
props: [
"endval",
"isstart"
],
created() {
this.start();
},
data() {
return {
num: '0',//按钮上默认数字
b:false//是否已返回数
}
},
methods: {
start() {
setTimeout(this.start, 100);//循环执行,始终监听是否重新开始运行
if (this.isstart) {
this.num = parseInt(Math.random() * this.endval) + 1;//产生随机数
this.b = false;//重新开始
}else{
if(!this.b){
this.$emit("myevent",this.num);//触发自定义事件,并返回数
this.b = true//已返回数
}
}
}
}
}
let app = new Vue({
el: "#app",
components: {
child
},
data: {
isstart: true,//是否开始
endval: 35,//数字范围
nums:[],//存储组件返回的数
sum:0,//和
n:10,//按钮个数
i:0//已返回数字的按钮数
},
methods:{
getVal(num){
this.nums.push(num);//把新产生的数添加到数组中
this.i++;
//全部组件都返回数后计算
if(this.i == this.n){
this.sum = 0;//重新开始计算
for(let p of this.nums){
this.sum += p;
}
this.nums = [];//重新开始计算
this.i = 0;//重新开始计算
}
}
}
});
</script>
</body>
</html>
vue3的cli实现
ChildCom
接收父组件是否开始的消息和随机数范围
负责把随机生成的数显示在按钮上
接收父组件停止命令,返回此刻产生的随机数给父组件
<template>
<input type="button" v-model="num">
</template>
<script>
export default {
name: "ChildCom",
props: [
"endval",
"isstart"
],
created() {
this.start();
},
data() {
return {
num: '0',//按钮上默认数字
b:false//是否已返回数
}
},
emits:[
"myevent"
]
,
methods: {
start() {
setTimeout(this.start, 100);//循环执行,始终监听是否重新开始运行
if (this.isstart) {
this.num = parseInt(Math.random() * this.endval) + 1;//产生随机数
this.b = false;//重新开始
}else{
if(!this.b){
this.$emit("myevent",this.num);//触发自定义事件,并返回数
this.b = true//已返回数
}
}
}
}
}
</script>
<style scoped>
</style>
父组件:ParentCom
- 控制子组件的开始和结束
- 接收子组件返回的数
- 控制子组件数量
- 求子组件返回数的和
<template>
<div>
<ChildCom v-for="q in n" :key="q" :endval="endval" :isstart="isstart" @myevent="getVal($event)" style="width:30px"></ChildCom>
<div>
<input type="button" value="开始" @click="isstart=true" >
<input type="button" value="停止" @click="isstart=false" >
和:{sum}
</div>
</div>
</template>
<script>
import ChildCom from './ChildCom'
export default {
name: "ParentCom",
data(){
return {
isstart: true,//是否开始
endval: 35,//数字范围
nums:[],//存储组件返回的数
sum:0,//和
n:20,//按钮个数
i:0//已返回数字的按钮数
}
},
components:{
ChildCom
},
methods:{
getVal(num){
this.nums.push(num);//把新产生的数添加到数组中
this.i++;
//全部组件都返回数后计算
if(this.i == this.n){
this.sum = 0;//重新开始计算
for(let p of this.nums){
this.sum += p;
}
this.nums = [];//重新开始计算
this.i = 0;//重新开始计算
}
}
}
}
</script>
<style scoped>
</style>
App.vue
<template>
<ParentCom></ParentCom>
</template>
<script>
import ParentCom from "./components/ParentCom";
export default {
name: 'App',
components: {
ParentCom
},
methods:{
}
}
</script>
<style>
</style>
单向数据流讲解
单向数据流(堆可以修改,栈不可修改)
我们都知道, 父传子的数据, 是单向数据流,即子组件不能直接修改, 父组件传递过来的值
但实际上, 对于修改值, 真正是:基本数据类型不可修改,复杂数据类型不要修改引用地址(栈),它的值可以随便修改
向子组件传值,修改后再返回
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<child :com-num="num" @myevent="num=$event" ></child>
<!--<child :com-num.sync="num"></child>-->
<div>
{num}
</div>
</div>
<template id="child">
<div>
<input type="text" :value="comNum">
<input type="text" v-model="num">
<input type="button" value="传出" @click="toVal">
</div>
</template>
<script>
let child = {
template:"#child",
props:[
"comNum"
],
data(){
return {
num:''
}
},
methods: {
toVal(){
// this.$emit("update:comNum",this.num)
this.$emit("myevent",this.num)
}
}
}
var app = new Vue({
el:"#app",
data:{
num:10
},
components:{
child
}
});
</script>
</body>
</html>
简化写法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<!--<child :com-num="num" @myevent="num=$event" ></child>-->
<!--.sync表示异步修改,需要组件内支持,即自定义事件名为:update:组件属性名-->
<child :com-num.sync="num"></child>
<div>
{num}
</div>
</div>
<template id="child">
<div>
<input type="text" :value="comNum">
<input type="text" v-model="num">
<input type="button" value="传出" @click="toVal">
</div>
</template>
<script>
let child = {
template: "#child",
props: [
"comNum"
],
data() {
return {
num: ''
}
},
methods: {
toVal() {
//update:comNum中的comNum与属性名相同,前缀固定为update
this.$emit("update:comNum", this.num)
// this.$emit("myevent",this.num)
}
}
}
var app = new Vue({
el: "#app",
data: {
num: 10
},
components: {
child
}
});
</script>
</body>
</html>
vue3中cli实现
如果直接修改父组件传递过来的属性,vue3中会报错:Unexpected mutation of “sonProp” prop vue/no-mutating-props(不能修改的属性)
change(){
this.sonProp = this.sonProp + 1;//报错
}
写法一
子组件:Child1
<template>
<div>{sonProp}</div>
<button @click="change">返回</button>
</template>
<script>
export default {
name: "Child1",
props:[
"sonProp"
],
emits:[
"retVal"
],
methods:{
change(){
this.$emit("retVal",this.sonProp+1);
}
},
data(){
return{
}
}
}
</script>
父组件:Parent1
<template>
<Child1 :sonProp="num" @retVal="num=$event"></Child1>
</template>
<script>
import Child1 from './Child1'
export default {
name: "Parent1",
components:{
Child1
},
data(){
return{
num:0
}
}
}
</script>
简化写法
子组件:Child1
<template>
<div>{sonProp}</div>
<button @click="change">返回</button>
</template>
<script>
export default {
name: "Child1",
props:[
"sonProp"
],
emits:[
"update:sonProp"
],
methods:{
change(){
this.$emit("update:sonProp",this.sonProp+1);
}
},
data(){
return{
}
}
}
</script>
父组件:Parent1
<template>
<Child1 v-model:sonProp="num"></Child1>
</template>
<script>
import Child1 from './Child1'
export default {
name: "Parent1",
components:{
Child1
},
data(){
return{
num:0
}
}
}
</script>
App.vue
<template>
<Parent1></Parent1>
</template>
<script>
import Parent1 from "./components/Parent1";
export default {
name: 'App',
components: {
Parent1
}
}
</script>
多属性
子组件:Child1
<template>
<div>{sonProp}</div>
<div>{prop2}</div>
<button @click="change">返回</button>
</template>
<script>
export default {
name: "Child1",
props:[
"sonProp",
"prop2"
],
emits:[
"update:sonProp",
"update:prop2"
],
methods:{
change(){
this.$emit("update:sonProp",this.sonProp+1);
this.$emit("update:prop2",this.prop2+2);
}
},
data(){
return{
}
}
}
</script>
父组件:Parent1
<template>
<Child1 v-model:sonProp="num" v-model:prop2="num2"></Child1>
</template>
<script>
import Child1 from './Child1'
export default {
name: "Parent1",
components:{
Child1
},
data(){
return{
num:0,
num2:100
}
}
}
</script>
插槽
需求:组件间传递值比较麻烦,有的时候,仅仅需要把父组件的值显示到子组件的可视范围内
匿名插槽:<slot></slot>
,只能有一个
<body>
<div id="app">
<child>{msg}</child>
</div>
<template id="child">
<div>
<hr>
<!-- 引用父组件的值 -->
<slot></slot>
<hr>
</div>
</template>
<script>
let child = {
template: "#child"
}
let app = new Vue({
el: "#app",
data: {
msg: 'hello'
},
components: {
child
}
});
</script>
具名插槽:<slot name="插槽名"></slot>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<child >
<!--为插槽s1赋值-->
<template slot="s1">marry</template>
<template slot="s2">scott</template>
<!--为匿名插槽赋值-->
<template>{msg}</template>
<!--或 {msg}-->
</child>
</div>
<template id="child">
<div>
子组件
<hr>
<slot name="s1"></slot>
<hr>
<slot name="s2"></slot>
<hr>
<slot></slot>
<hr>
</div>
</template>
<script type="text/javascript">
var child = {
template:"#child"
};
var app = new Vue({
el:"#app",
data:{
msg:"tom"
},
components:{
child
}
});
</script>
</body>
</html>
注意:插槽属于父组件,在子组件中,只能决定放置在哪个地方,不能修改
vue3中cli
匿名插槽
子组件:
<template>
<hr>
<slot></slot>
<hr>
</template>
<script>
export default {
name: "Child2"
}
</script>
父组件:
<template>
<Child2>
<template #default>100</template>
</Child2>
或
<Child2>100</Child2>
</template>
<script>
import Child2 from './Child2'
export default {
name: "Parent2",
components:{
Child2
}
}
</script>
<style scoped>
</style>
具名插槽
子组件
<template>
<hr>
<slot></slot>
<hr>
<slot name="name1"></slot>
<hr>
<slot name="name2"></slot>
</template>
<script>
export default {
name: "Child2"
}
</script>
父组件
<template>
<Child2>
<template #default>100</template>
<template #name1>200</template>
<template #name2>300</template>
</Child2>
</template>
或
<template>
<MyDemo3>
<template v-slot:default>1002</template>
<template v-slot:name1>200</template>
<template v-slot:name2>300</template>
</MyDemo3>
</template>
<script>
import Child2 from './Child2'
export default {
name: "Parent2",
components:{
Child2
}
}
</script>
作用域插槽
作用:把子组件的值通过插槽属性传递出来,由父组件决定如何显示,并且只在当前插槽中有效
slot-scope:就像一个临时变量,包含了从组件传回的所有属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<child>
<template slot-scope="scope" slot="s1">
<span>姓名:{scope.name1}</span>
<span>年龄:{scope.age}</span>
</template>
</child>
</div>
<template id="child">
<div>
<!-- 注意插槽的属性不能为name,否则不显示 -->
<slot name="s1" :name1="name" :age="age"></slot>
</div>
</template>
<script>
let child = {
template: "#child",
data(){
return{
name:'tom',
age:20
}
}
}
let app = new Vue({
el: "#app",
data: {
msg: 'hello'
},
components: {
child
}
});
</script>
</body>
</html>
也可以传递对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<child>
<template slot-scope="scope" slot="s1">
<span>姓名:{scope.stu.name}</span>
<span>年龄:{scope.stu.age}</span>
</template>
</child>
</div>
<template id="child">
<div>
<slot name="s1" :stu="stu"></slot>
</div>
</template>
<script>
let child = {
template: "#child",
data(){
return{
stu:{name:"tom",age:20}//传递对象
}
}
}
let app = new Vue({
el: "#app",
data: {
msg: 'hello'
},
components: {
child
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<child :data="arr">
<template slot-scope="scope">
<li><h3>{scope.n}</h3><h4>{scope.i}</h4></li>
</template>
</child>
</div>
<template id="t1">
<div>
<ul>
<slot v-for="(name,index) in data" :n="name" :i="index"></slot>
</ul>
</div>
</template>
<script type="text/javascript">
var child = {
template:"#t1",
props:[
"data"
]
}
var app = new Vue({
el:"#app",
data:{
arr:["tom","marry","scott"]
},
components:{
child
}
});
</script>
</body>
</html>
vue3中cli实现
子组件
Child3.vue
<template>
<slot age="20" name2="tom"></slot>
</template>
<script>
export default {
name: "Child3"
}
</script>
父组件
Parent3.vue
<template>
<Child3 v-slot="scope">
{scope.age}-{scope.name2}
</Child3>
</template>
<script>
import Child3 from "./Child3";
export default {
name: "Parent3",
components:{
Child3
}
}
</script>
具名作用域插槽
子组件
Child3.vue
<template>
<slot age="30"></slot>
<slot name2="tom" name="name1" age="20"></slot>
</template>
<script>
export default {
name: "Child3"
}
</script>
父组件
Parent3.vue
<template>
<Child3 >
<!-- 可简写为:#default="scope" -->
<template v-slot:default="scope">
{scope.age}
</template>
<!-- 可简写为:#name1="scope" -->
<template v-slot:name1="scope">
{scope.name2}-{scope.age}
</template>
</Child3>
</template>
<script>
import Child3 from "./Child3";
export default {
name: "Parent3",
components:{
Child3
}
}
</script>
动态组件
<component :is="com"></component>
:com属性的值是一个组件对象
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<component :is="com"></component>
<button type="button" @click="getCom">切换组件</button>
</div>
<script type="text/javascript">
var com1 = {
template:`<div>组件1</div>`
}
var com2 = {
template:`<div>组件2</div>`
}
var app = new Vue({
el:"#app",
data:{
com:com2
},
components:{
com1,com2
},
methods:{
getCom(){
this.com = com1;
}
}
});
</script>
</body>
</html>
vue3中cli实现
子组件
Child4,child5
<template>
<div>子组件4</div>
</template>
<template>
<div>子组件5</div>
</template>
父组件
Parent4
<template>
<component :is="com"></component>
<button @click="change">更换组件</button>
</template>
<script>
import Child4 from './Child4'
import Child5 from './Child5'
import {markRaw} from 'vue'
export default {
name: "Parent4",
components:{
Child4,Child5
},
data(){
return {
//markRaw:把该组件变为非响应式的,否则会有警告
com:markRaw(Child4)
}
},
methods:{
change(){
if(this.com.name =="Child4"){
this.com = markRaw(Child5)
}else{
this.com = markRaw(Child4)
}
}
}
}
</script>
路由
作用:在应用中切换组件
使用
创建组件
导入路由文件
<script src="js/vue-router.js" type="text/javascript" charset="utf-8"></script>
创建路由器实例,配置路由器
var router = new VueRouter({ //路由表 routes:[ //路由 { path:'/login', component:login }, { path:'/main', component:main } ] });
注册路由器
var app = new Vue({ el:"#app", data:{ }, router });
<router-link to="/login">登录页</router-link>
:功能类似于超链接,用来链接组件<router-view></router-view>
:用来显示组件使用代码切换组件
this.$router.push("/main");
显示第一个组件
//配置路由 { path:'/', component:login } //通过代码 var app = new Vue({ el:"#app", data:{ }, router, created(){ this.$router.push("/login"); } });
命名路由
{ path:'/login', name:"login", component:login }, { path:'/main', name:"main", component:main }
使用路由名子切换组件
this.$router.push({name:'login'}).catch(()=>{});
嵌套路由
var router = new VueRouter({ //路由表 routes:[ //路由 /* { path:'/', component:login }, */ { path:'/login', name:"login", component:login }, { path:'/main', name:"main", component:main, children:[ { path:'/dept', name:'dept', component:dept }, { path:'/emp', name:'emp', component:emp } ] } ] });
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/vue.js" type="text/javascript" charset="utf-8"></script> <script src="js/vue-router.js" type="text/javascript" charset="utf-8"></script> <style> #parent{ width: 800px; height:500px; display: flex; } #nav{ width:100px; height:500px; background-color: bisque; } #main{ width:600px; height:500px; background-color: aliceblue; } </style> </head> <body> <div id="app"> <!-- <router-link to="/login">登录页</router-link> <router-link to="/main">主页</router-link> --> <router-view></router-view> </div> <script type="text/javascript"> var login = { template:` <div> <h1>登录页</h1> <input type="button" value="登录" @click="login"> </div> `, methods:{ login(){ this.$router.push("/main"); } } } var main = { template:` <div> <h1>主页</h1> <div id="parent"> <div id="nav"> <h4>导航</h4> <router-link to="/dept">部门管理</router-link><br> <router-link to="/emp">员工管理</router-link> </div> <div id="main"> <h4>主区域</h4> <router-view></router-view> </div> </div> </div> ` } var dept = { template:`<div><h2>部门管理</h2></div>` } var emp = { template:`<div><h2>员工管理</h2></div>` } var router = new VueRouter({ //路由表 routes:[ //路由 /* { path:'/', component:login }, */ { path:'/login', name:"login", component:login }, { path:'/main', name:"main", component:main, children:[ { path:'/dept', name:'dept', component:dept }, { path:'/emp', name:'emp', component:emp } ] } ] }); var app = new Vue({ el:"#app", data:{ }, router, created(){ // this.$router.push("/login"); this.$router.push({name:'login'}).catch(()=>{}); } }); </script> </body> </html>
路由传参
向切换后的组件传递参数
动态路由
//路由表 { path:'/dept/:deptno', name:'dept', component:dept } //router-link <router-link to="/dept/10">部门管理</router-link> //代码 this.$router.push({path:'/editdept/20/hr5'}); //在组件中得到路由参数 {$route.params.deptno} this.deptno = this.$route.params.deptno;
//路由表 { path:'/dept', component:dept } //router-link <router-link :to="{path:'/update_dept',query:{'deptno':dept.deptno}">更新</router-link> //在组件中得到路由参数 {$route.query.deptno} this.deptno = this.$route.query.deptno;
查询参数(查询参数会显示在地址栏中)
//路由表 { path:'/dept', name:'dept', component:dept } 发送数据: this.$router.push({name:'main',query:{username:'admin',age:20}); 接收数据: {$route.query.username} this.username = this.$route.query.username;
通过 name 和 params 结合传递参数(地址栏中不显示参数数据)
//路由表 { path:'/dept', name:'dept', component:dept } 发送数据: this.$router.push({name:'main',params:{username:'admin',age:20}); 接收数据: {$route.params.username} this.username = this.$route.params.username;
注意:path不能与params同时使用,params会被忽略掉
Vue-Cli
vue.js开发的标准工具
Node.js:是一个基于 Chrome V8引擎的 JavaScript运行时环境
npm:是随着node.js一起安装的包管理工具,允许用户从npm服务器下载别人编写好的第三方包到本地
测试:npm –version
安装 cnpm
npm install -g cnpm --registry=http://registry.npm.taobao.org
安装 Vue-Cli
cnpm install -g @vue/cli
启动项目
npm run serve
安装路由(脚手架插件)
vue add router
或
npm(cnpm) install vue-router
Axios
安装:
vue add axios
定义:易用、简洁且高效的http库
作用:用户向后台服务器发送异步请求,并处理响应的结果
配置:
//基础路径配置 axios.defaults.baseURL="http://localhost:8089/" //允许传递证书 axios.defaults.withCredentials=true
get请求
var url = `emp/getPaged?pageNum=${pageNum}&pageSize=${pageSize}`; axios.get(url).then((resp)=>{ console.log(resp); });
var url = `emp/getPaged`; var params = { pageNum:pageNum, pageSize:pageSize } axios.get(url,{params:params}).then((resp)=>{ console.log(resp); });
post请求
var url = `emp/getPaged`; axios.post(url,`pageNum=${pageNum}&pageSize=${pageSize}`).then((resp)=>{ console.log(resp); });
@RequestMapping("update") public int update(@RequestBody Emp emp) { return empService.update(emp); } update(){ var url = "emp/update"; //this.empVO.emp:为JSON对象 axios.post(url,this.empVO.emp).then((resp)=>{ if(resp.data == 1){ alert("更新成功!"); this.$router.push("/emp") } }); }
Axios-vue3
定义:易用、简洁且高效的http库
安装
npm install axios
配置
/src/main.js import axios from 'axios' axios.defaults.baseURL = "http://localhost:8090/" //允许传递证书 axios.defaults.withCredentials = true const app = createApp(App); app.config.globalProperties.$http = axios
springboot添加允许跨域请求
package com.dhee.springbootdemo1; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; //表示当前类是一个spring配置类 @Configuration public class SpringMVCConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowCredentials(true) .allowedHeaders("*") .allowedMethods("*") .allowedOriginPatterns("*"); } }
get请求
let url = `user/login?username=${this.user.username}&password=${this.user.password}`; this.$http.get(url).then((resp)=>{ if(resp.data != null && resp.data != ""){ sessionStorage.setItem("username","admin") // alert("登录成功"); //向路由器中路径的历史记录中添加一条记录 this.$router.push("/main"); }else{ alert("登录失败!") } })
let url = `user/login`; this.$http.get(url,{params:this.user}).then((resp)=>{ if(resp.data != null && resp.data != ""){ sessionStorage.setItem("username","admin") // alert("登录成功"); //向路由器中路径的历史记录中添加一条记录 this.$router.push("/main"); }else{ alert("登录失败!") } })
post请求
this.$http.post("dept/update2",`deptno=${this.dept.deptno}&dname=${this.dept.dname}&loc=${this.dept.loc}`) .then((resp)=>{ if(resp.data == 1){ this.$router.push("/main/dept") }else{ alert("修改失败") } })
this.$http.post("dept/update2",this.dept) .then((resp)=>{ if(resp.data == 1){ this.$router.push("/main/dept") }else{ alert("修改失败") } }) //服务器端 @RequestMapping("update2") @ResponseBody public int update2(@RequestBody Dept dept){ return deptService.update(dept); }
封装axios
request/index.js
import axios from 'axios' // 创建一个 axios 实例 const service = axios.create({ baseURL: 'http://localhost:8089/', // 所有的请求地址前缀部分 timeout: 60000, // 请求超时时间毫秒 withCredentials: true, // 异步请求携带cookie headers: { // 设置后端需要的传参类型 'Content-Type': 'application/json', // 'token': 'your token', 'X-Requested-With': 'XMLHttpRequest', }, }) // 添加请求拦截器 service.interceptors.request.use( function (config) { // 在发送请求之前做些什么 return config }, function (error) { // 对请求错误做些什么 console.log(error) return Promise.reject(error) } ) // 添加响应拦截器 service.interceptors.response.use( function (response) { // console.log(response) // 2xx 范围内的状态码都会触发该函数。 // 对响应数据做点什么 // dataAxios 是 axios 返回数据中的 data const dataAxios = response.data // 这个状态码是和后端约定的 // const code = dataAxios.reset return dataAxios }, function (error) { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 console.log(error) return Promise.reject(error) } ) export default service
main.js
import { createApp } from 'vue' import App from './App.vue' import Router from "./router/index"; import store from './store/index' import axios from './request/index' const app = createApp(App) app.config.globalProperties.$http = axios app.use(Router) app.use(store) app.mount('#app')
Dept.vue
created() { let url = 'dept/getAll'; this.$http(url).then(data=>{ console.log(data) this.depts = data; }); }
axios拦截器
拦截器介绍
一般在使用axios时,会用到拦截器的功能,一般分为两种:请求拦截器、响应拦截器。
请求拦截器 在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装;
响应拦截器 同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等。
比如一些网站过了一定的时间不进行操作,就会退出登录让你重新登陆页面,当然这不用拦截器你或许也可以完成这功能,但是会很麻烦而且代码会产生大量重复,所以我们需要用到拦截器
应用场景
1:每个请求都带上的参数,比如token,时间戳等。
2:对返回的状态进行判断,比如token是否过期
axios的拦截器作用非常大。axios的拦截器分为请求拦截器跟响应拦截器,都是可以设置多个请求或者响应拦截。每个拦截器都可以设置两个拦截函数,一个为成功拦截,一个为失败拦截。在调用axios.request()之后,请求的配置会先进入请求拦截器中,正常可以一直执行成功拦截函数,如果有异常会进入失败拦截函数,并不会发起请求;调起请求响应返回后,会根据响应信息进入响应成功拦截函数或者响应失败拦截函数。
因此,我们可以在拦截器中处理一些请求的统一处理。比如在请求拦截器中设置请求头,处理统一的请求数据,在响应拦截去中根据响应状态码做统一的提示信息,整理响应数据等。
在请求或响应被 then 或 catch 处理前拦截它们。
// 官方用例
// 1.添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 2.添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
1.axios-请求拦截器
目标
什么是请求
什么是axios的请求拦截器
场景
在发起请求之前, 最后对要发送的请求配置对象进行修改
例如: 如果本地有token, 携带在请求头给后台
// 添加请求拦截器--代码实现案例:仅供参考
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么, 如果vuex里有token携带在请求头中
if (store.state.token.length > 0 && config.headers.Authorization === undefined) {
// 发起请求之前, 把token携带在请求头上(表明自己身份)
config.headers.Authorization = 'Bearer ' + store.state.token
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
所有api接口里以后暂时不用自己携带Headers+Token了,简略代码,统一管理
小结
请求拦截器时候时候执行?
在发起请求最后一刻执行
2.axios-响应拦截器
目标
什么是响应
什么是axios的响应拦截器
场景
在响应回来后, 马上执行响应拦截器函数
例如: 判断是否错误401, 统一进行权限判断
// 添加响应拦截器--代码实现案例:仅供参考
axios.interceptors.response.use(function (response) { // 当状态码为2xx/3xx开头的进这里
// 对响应数据做点什么
return response
}, async function (error) { // 响应状态码4xx/5xx进这里
// 对响应错误做点什么
if (error.response.status === 401) { // 身份过期/token无效
// 1.清空vuex的token
store.commit('setToken', '')
store.commit('setRefreshToken', '')
// 2. 清空本地token
localStorage.removeItem('token')
localStorage.removeItem('refresh_token')
// 跳转到登录页面登录
router.push({
path: '/login'
})
}
return Promise.reject(error)
})
小结
响应拦截器什么时候执行?
在响应回来以后
什么时候进响应拦截器成功, 什么时候进失败?
2xx/3xx开头的响应状态码进入成功
4xx/5xx开头的响应状态码进入失败
Vuex
安装
npm install --save vuex
Vuex是Vue的状态管理库,用来管理状态(应用程序中的信息或数据)。它将状态存储在一个中心位置,使得任何组件都很容易与之交互
使用
在src目录下,添加一个文件:store.js,创建Vuex的Store对象
import Vue from 'vue' import Vuex from 'vuex' //安装Vuex插件 Vue.use(Vuex) export default new Vuex.Store({ state:{ count:0 } })
在main.js,导入 store.js
import { createApp } from 'vue' import App from './App.vue' import Router from "./router/index"; import vuex from './store/index' const app = createApp(App) app.use(Router) app.use(vuex) app.mount('#app')
在组件中访问
{$store.state.user.username}
在组件内,来自store的数据是只读的,不能手工修改,改变 store中数据的唯一途径就是显示的提交mutations
export default new Vuex.Store({ state:{ count:10 }, mutations:{ add(state,payload){ //payload可以是一个对象 //state.count += payload.num; state.count += payload; } } }) //this.$store.commit("add",{num:6}); this.$store.commit("add",5);
使用store中的getters,来处理state数据,以便以统一的形式来显示数据
export default new Vuex.Store({ state:{ count:10 }, mutations:{ add(state,payload){ state.count += payload.num; } }, getters:{ formatCount(state){ return `个数:${state.count}`; } } }) {$store.getters.formatCount}
使用 store的 actions,来执行异步状态管理
export default new Vuex.Store({ state:{ count:10 }, mutations:{ add(state,payload){ state.count += payload.num; } }, getters:{ formatCount(state){ return `个数:${state.count}`; } }, actions:{ add2(context,payload){ setTimeout(function(){ context.commit("add",payload); },1000) } } }) this.$store.dispatch("add2",{num:7});//异步操作
Vuex4
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
安装
npm install vuex@next --save
使用
在src目录下,添加一个文件夹:store,在其下创建文件:index.js
import {createStore} from 'vuex' export default createStore({ state(){ return { user:{username:'tom'} } } })
在main.js,导入 store.js
import { createApp } from 'vue' import App from './App.vue' import Router from "./router/index"; import vuex from './store/index' const app = createApp(App) app.use(Router) app.use(vuex) app.mount('#app')
在组件中访问
{$store.state.user.username}
在组件内,来自store的数据是只读的,不能手工修改,改变 store中数据的唯一途径就是显示的提交mutations
export default new Vuex.Store({ state:{ count:10 }, mutations:{ add(state,payload){ //payload可以是一个对象 //state.count += payload.num; state.count += payload; } } }) //this.$store.commit("add",{num:6}); this.$store.commit("add",5);
使用store中的getters,来处理state数据,以便以统一的形式来显示数据
export default new Vuex.Store({ state:{ count:10 }, mutations:{ add(state,payload){ state.count += payload.num; } }, getters:{ formatCount(state){ return `个数:${state.count}`; } } }) {$store.getters.formatCount}
使用 store的 actions,来执行异步状态管理
export default new Vuex.Store({ state:{ count:10 }, mutations:{ add(state,payload){ state.count += payload.num; } }, getters:{ formatCount(state){ return `个数:${state.count}`; } }, actions:{ add2(context,payload){ setTimeout(function(){ context.commit("add",payload); },1000) } } }) this.$store.dispatch("add2",{num:7});//异步操作
路由守卫
在路由切换前,对路由进行检查
通过路由守卫实现修改网页title的功能
在每个路由中添加meta属性,记录title信息
import {createRouter,createWebHashHistory} from "vue-router"; export default createRouter({ history:createWebHashHistory(), routes:[ { path:'/', component:()=>import('../components/Login'), name:'login', meta:{ title:'登录' } }, { path:'/main', component:()=>import('../components/Main'), children:[ { path:'email', component:()=>import('../components/EmailConfig'), meta:{ title:'邮箱设置' } }, { path:'user', components:{ default:()=>import('../components/UserConfig'), helper:()=>import('../components/UserHelper') }, meta:{ title:'用户设置' } } ] } ] })
路由守卫
import { createApp } from 'vue' import App from './App.vue' import Router from "./router/index"; import vuex from './store/index' const app = createApp(App) Router.beforeEach((to,from,next)=>{ window.document.title = to.meta.title; next(); }) app.use(Router) app.use(vuex) app.mount('#app')
to:路由对象,即将要进入的目标
from:路由对象,当前导航要离开的路由
next:方法,一定要调用,决定后续如何导航处理
- next():继续后续的操作
- next(false):中断当前导航
- next(“/url”):跳转到参数指定的地址
使用路由守卫进行登录检查
login(){ this.$store.commit('setUser',{username:'admin'}) this.$router.push("/main"); }
router.beforeEach((to,from,next)=>{ window.document.title = to.meta.title; if(to.name != "login" && !store.state.user.username){ next("/") } next(); })
路由监听
<div id="nav"> <h4>导航</h4> <router-link to="/dept/10">10部门管理</router-link><br> <router-link to="/dept/20">20部门管理</router-link><br> <router-link to="/emp">员工管理</router-link> </div> { path:"/dept/:deptno", name:'dept', component: ()=> import('./views/Dept.vue'), meta:{ title:"部门管理" } } watch:{ $route(to,from){ this.deptno = this.$route.params.deptno; } }
Storage
- sessionStorage:会话存储,关闭浏览器,信息丢失
- localStorage:本地存储,关闭浏览器,信息还在,存储到硬盘上
- setItem(key,value):value只能是字符串
- getItem(key)
- removeItem(key)
JSON
- JSON.stringify(对象):把对象转换为字符串
- JSON.parse(字符串):把字符串转换为JSON对象
ElementUI
安装
npm i element-ui -S
修改main.js
import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' //在vue中安装 element-ui插件 Vue.use(ElementUI)
登录页前端
<template> <div> <el-form class="login-container" :model="user" :rules="rules" ref="loginForm"> <h3 class="login-title">系统登录</h3> <el-form-item prop="username"> <el-input type="text" v-model="user.username" placeholder="账号"></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" v-model="user.password" placeholder="密码"></el-input> </el-form-item> <el-form-item prop="checkCode"> <img :src="imgUrl"><a href="#" @click="getcode">看不清</a> <el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input> </el-form-item> <el-form-item> <el-button type="primary" style="width:100%;" @click="login">登录</el-button> </el-form-item> </el-form> </div> </template> <script> export default { data(){ var checkCodeValidate = (rule,value,callback)=>{ var url = "user/checkCode"; axios.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{ if(!resp.data){ callback(new Error("验证输入错误!")); }else{ callback(); } }); }; return{ imgUrl:"http://localhost:8089/user/getCode", user:{}, rules:{ username:[ {required:true,message:'请输入用户名',trigger:['blur','change']}, {min:2,max:10,message:'用户名长度必须在2-10个字符之间',trigger:['blur','change']} ], password:[ {required:true,message:'请输入密码',trigger:['blur','change']}, {min:2,max:10,message:'密码长度必须在2-10个字符之间',trigger:['blur','change']} ], checkCode:[ {required:true,message:'请输入验证码',trigger:['blur','change']}, {pattern:/^\w{4}$/,message:"验证码长度必须为4位"}, {validator:checkCodeValidate,trigger:'blur'} ] } } }, methods:{ login(){ this.$refs["loginForm"].validate(valid=>{ if(valid){ var url = "user/login"; axios.get(url,{params:this.user}).then((resp)=>{ if(resp.data){ this.$store.commit("setUsername",this.user.username); this.$router.push("/main"); }else{ this.$alert("用户名或密码错误!"); } }); } }); }, getcode(){ this.imgUrl = this.imgUrl + "?"+Math.random(); } } } </script> <style scoped="scoped"> .login-container{ width:350px; margin:100px auto 10px; border:1px solid #eaeaea; padding:35px 35px 15px 35px; border-radius: 15px; box-shadow:0 0 25px #cac6c6; } .login-title{ text-align: center; color:#505458; margin:0px auto 40px; } </style>
登录页后端
package com.neu.controller; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.imageio.ImageIO; import javax.servlet.http.HttpSession; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { /* * @RequestMapping("/") public String getLogin() { return "user/login"; } */ @RequestMapping("user/login") public boolean login(String username,String password,HttpSession session) { if("admin".equals(username) && "111".equals(password)) { session.setAttribute("username", username); return true; }else { return false; } } @RequestMapping("user/getCode") public ResponseEntity<byte[]> getCode(HttpSession session) throws IOException{ char[] arr = {'0','1','2','3','4','5','6','7','8','9'}; StringBuilder stb = new StringBuilder(); int n; for(int i = 0;i < 4;i++) { n = (int)(Math.random()*arr.length); stb.append(arr[n]); } session.setAttribute("code", stb.toString()); //把字符串放入到图片中,写入到输出流中 BufferedImage buffImg = new BufferedImage(90, 20, BufferedImage.TYPE_INT_RGB); //得到画布 Graphics g = buffImg.getGraphics(); //向画布中写入字符串 g.drawString(stb.toString(), 20, 15); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ImageIO.write(buffImg, "jpeg", bos); return new ResponseEntity<byte[]>(bos.toByteArray(),HttpStatus.CREATED); } @RequestMapping("user/checkCode") public boolean checkCode(String checkCode,HttpSession session) { String code = (String)session.getAttribute("code"); if(code != null && code.equals(checkCode)) { return true; }else { return false; } } }
Vue-Router 4
安装:
npm install vue-router npm install vue-router@4
配置
history:createWebHistory()
:HTML5 history模式,是利用html5的history API实现的,需要服务器配合,服务器需要添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。不过这个工作一般交给运维去做地址格式:
http://localhost:8080/list
history: createWebHashHistory()
:hash模式完全前端就能实现地址格式:
http://localhost:8080/#/list
// /router/index.js import {createRouter,createWebHistory} from 'vue-router' export default createRouter({ history:createWebHistory(), routes:[ { path:'/', redirect:'/login' }, { path:'/login', component:()=>import('../components/login') }, { path:'/main', component:()=>import('../components/Main'), children:[ { path:'dept', component:()=>import('../components/Dept') }, { path:'emp', component:()=>import('../components/Emp') }, { path:'user', component:()=>import('../components/User') } ] } ] })
main.js
import VueRouter from './router/index' const app = createApp(App); //路由守卫 VueRouter.beforeEach((to,from,next)=>{ console.log(to,from,next) let user = sessionStorage.getItem("user"); if(to.path != "/login" && user == null){ next("/login") } next() })
主要内容
router-view
将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。router-link
没有使用常规的a
标签,而是使用一个自定义组件router-link
来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
login.vue
<template> <input type="button" value="登录" @click="login"> </template> <script> export default { name: "login", methods:{ login(){ sessionStorage.setItem("user",{}) this.$router.push("/main") } } } </script>
main.vue
<template> <div id="top"> <!-- <router-link to="/login">注销</router-link>--> <input type="button" value="注销" @click="destroy"> </div> <div id="parent"> <div id="nav"> <ul> <li><router-link to="/main/dept">部门管理</router-link></li> <li><router-link to="/main/emp">员工管理</router-link></li> <li><router-link to="/main/user">用户管理</router-link></li> </ul> </div> <div id="main"> <router-view></router-view> </div> </div> </template> <script> export default { name: "Main", created(){ this.$router.replace("/main/dept"); }, methods:{ destroy(){ sessionStorage.removeItem("user") this.$router.replace("/login") } } } </script> <style scoped> #parent{ display: flex; } #nav{ width:200px; height: 500px; background-color: antiquewhite; } #main{ width:100%; height: 500px; background-color: cornsilk; } li{ list-style: none; } </style>
命名路由
除了 path
之外,你还可以为任何路由提供 name
。这有以下优点:
- 没有硬编码的 URL
params
的自动编码/解码。- 防止你在 url 中出现打字错误。
- 绕过路径排序(如显示一个)
const routes = [
{
path: '/user/:username',
name: 'user',
component: User
}
]
要链接到一个命名的路由,可以向 router-link
组件的 to
属性传递一个对象:
<router-link :to="{ name: 'user', params: { username: 'erina' }">
User
</router-link>
这跟代码调用 router.push()
是一回事:
router.push({ name: 'user', params: { username: 'erina' } })
在这两种情况下,路由将导航到路径 /user/erina
命名视图
App.vue
如果 router-view
没有设置名字,那么默认为 default
。
<template>
<div style="display: flex">
<div style="width:200px;background-color: peachpuff;height: 500px;">
aside
<router-view name="aside"></router-view>
</div>
<div style="background-color:cornsilk;width: 100%;">
main
<router-view name="main"></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
methods:{
}
}
</script>
路由
router/index.js
import {createRouter,createWebHashHistory} from "vue-router";
export default createRouter({
history:createWebHashHistory(),
routes:[
{
path:'/',
// component:()=>import('../components/Main'),
components:{
//default:()=>import('../components/Child3'),
aside:()=>import('../components/Child5'),
main:()=>import('../components/Child4')
}
}
]
})
嵌套命名视图
路由配置
index.js
import {createRouter,createWebHashHistory} from "vue-router";
export default createRouter({
history:createWebHashHistory(),
routes:[
{
path:'/',
component:()=>import('../components/Login'),
},
{
path:'/main',
component:()=>import('../components/Main'),
children:[
{
path:'email',
component:()=>import('../components/EmailConfig')
},
{
path:'user',
components:{
default:()=>import('../components/UserConfig'),
helper:()=>import('../components/UserHelper')
}
}
]
}
]
})
App.vue
<template>
<router-view></router-view>
</template>
Login.vue
<template>
登录页
<button @click="login">登录</button>
</template>
<script>
export default {
name: "Login",
methods:{
login(){
this.$router.push({path:'/main/email'});
}
}
}
</script>
Main.vue
<template>
<div style="display: flex">
<div style="width:200px;background-color: peachpuff;height: 500px;">
<NavMenu></NavMenu>
</div>
<div style="background-color:cornsilk;width: 100%;">
<div>
<router-view></router-view>
</div>
<div>
<router-view name="helper"></router-view>
</div>
</div>
</div>
</template>
<script>
import NavMenu from './NavMenu'
export default {
name: "Main",
components:{
NavMenu
}
}
</script>
NavMenu.vue
<template>
<router-link to="/main/email">邮箱设置</router-link>
<br>
<router-link to="/main/user">用户设置</router-link>
</template>
EmailConfig.vue
<template>
邮箱配置
</template>
UserConfig.vue
<template>
用户配置
</template>
UserHelper.vue
<template>
用户帮助
</template>
分页
服务器端
pom.xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
service
@Override
public PageInfo<Dept> paged(int pageNum, int pageSize) {
PageHelper.startPage(pageNum,pageSize);
List<Dept> list = deptMapper.getAll();
PageInfo pageInfo = new PageInfo(list);
return pageInfo;
}
controller
@RequestMapping("paged")
@ResponseBody
public PageInfo<Dept> paged(@RequestParam(defaultValue = "1") int pageNum,@RequestParam(defaultValue = "3") int pageSize){
return deptService.paged(pageNum,pageSize);
}
前端
<template>
部门管理
<table border="1" width="500">
<tr v-for="dept in pageInfo.list" :key="dept.deptno">
<td>{dept.deptno}</td>
<td>{dept.dname}</td>
<td>{dept.loc}</td>
</tr>
<tr>
<td colspan="3">
<a href="#" v-for="num in pageInfo.navigatepageNums" :key="num" @click="paged(num,pageInfo.pageSize)">[{num}]</a>
<a href="#" @click="paged(pageInfo.navigateFirstPage,pageInfo.pageSize)" v-show="pageInfo.hasPreviousPage">第一页</a>
<a href="#" @click="paged(pageInfo.prePage,pageInfo.pageSize)" v-show="pageInfo.hasPreviousPage">上一页</a>
<a href="#" @click="paged(pageInfo.nextPage,pageInfo.pageSize)" v-show="pageInfo.hasNextPage">下一页</a>
<a href="#" @click="paged(pageInfo.navigateLastPage,pageInfo.pageSize)" v-show="pageInfo.hasNextPage">最后一页</a>
<select v-model="pageInfo.pageSize" @change="changePageSize">
<option>3</option>
<option>5</option>
<option>10</option>
</select>
</td>
</tr>
</table>
</template>
<script>
export default {
name: "Dept",
data(){
return {
pageInfo:{
pageNum:1,pageSize:3
}
}
},
methods:{
paged(pageNum,pageSize){
this.$http.post('dept/paged',`pageNum=${pageNum}&pageSize=${pageSize}`)
.then((resp)=>{
this.pageInfo = resp.data;
})
},
changePageSize(){
this.paged(1,this.pageInfo.pageSize)
}
},
created() {
this.paged(1,3)
}
}
</script>
<style scoped>
</style>
ElementUI-plus
安装
npm install element-plus --save
使用
// main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
Container 布局容器
用于布局的容器组件,方便快速搭建页面的基本结构:
<el-container>
:外层容器。 当子元素中包含 <el-header>
或 <el-footer>
时,全部子元素会垂直上下排列, 否则会水平左右排列。
<el-header>
:顶栏容器。
<el-aside>
:侧边栏容器。
<el-main>
:主要区域容器。
<el-footer>
:底栏容器。
<template>
<div class="common-layout">
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
</el-container>
</div>
</template>
<style scoped>
.common-layout .el-header,
.common-layout .el-footer,
.common-layout .el-main,
.common-layout .el-aside {
display: flex;
justify-content: center;
align-items: center;
}
.common-layout .el-header,
.common-layout .el-footer {
background-color: #c6e2ff;
color: var(--el-text-color-primary);
text-align: center;
}
.common-layout .el-main {
background-color: var(--el-color-primary-light-9);
color: var(--el-text-color-primary);
text-align: center;
height: 150px;
}
</style>
<template>
<div class="common-layout">
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</div>
</template>
<style scoped>
.common-layout .el-header,
.common-layout .el-footer,
.common-layout .el-main,
.common-layout .el-aside {
display: flex;
justify-content: center;
align-items: center;
}
.common-layout .el-header,
.common-layout .el-footer {
background-color: #c6e2ff;
color: var(--el-text-color-primary);
text-align: center;
}
.common-layout .el-main {
background-color: var(--el-color-primary-light-9);
color: var(--el-text-color-primary);
text-align: center;
height: 150px;
}
.common-layout .el-aside {
background-color: var(--el-color-primary-light-8);
color: var(--el-text-color-primary);
text-align: center;
}
</style>
<template>
<div class="common-layout">
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-main>Main</el-main>
</el-container>
<el-footer>Footer</el-footer>
</el-container>
</div>
</template>
<style scoped>
.common-layout .el-header,
.common-layout .el-footer,
.common-layout .el-main,
.common-layout .el-aside {
display: flex;
justify-content: center;
align-items: center;
}
.common-layout .el-header,
.common-layout .el-footer {
background-color: #c6e2ff;
color: var(--el-text-color-primary);
text-align: center;
}
.common-layout .el-main {
background-color: var(--el-color-primary-light-9);
color: var(--el-text-color-primary);
text-align: center;
height: 150px;
}
.common-layout .el-aside {
/*var()函数中的值可以在浏览器调试中查看*/
background-color: var(--el-color-primary-light-8);
color: var(--el-text-color-primary);
text-align: center;
}
</style>
Container 属性
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
direction | 子元素的排列方向 | string | horizontal / vertical | 子元素中有 el-header 或 el-footer 时为 vertical,否则为 horizontal |
Container 插槽
插槽名 | 说明 | 子标签 |
---|---|---|
— | 自定义默认内容 | Container / Header / Aside / Main / Footer |
Header 属性
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
height | 顶栏高度 | string | — | 60px |
Header 插槽
插槽名 | 说明 |
---|---|
— | 自定义默认内容 |
Aside 属性
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
width | 侧边栏宽度 | string | — | 300px |
Aside 插槽
插槽名 | 说明 |
---|---|
— | 自定义默认内容 |
Main 插槽
插槽名 | 说明 |
---|---|
— | 自定义默认内容 |
Footer 属性
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
height | 底栏高度 | string | — | 60px |
Footer 插槽
插槽名 | 说明 |
---|---|
— | 自定义默认内容 |
CSS var() 函数
实例
定义一个名为 “–main-bg-color” 的属性,然后使用 var() 函数调用该属性:
:root {
--main-bg-color: coral;
}
#div1 {
background-color: var(--main-bg-color);
}
#div2 {
background-color: var(--main-bg-color);
}
布局
通过基础的 24 分栏,迅速简便地创建布局。
基础布局
使用列创建基础网格布局。
通过 row
和 col
组件,并通过 col 组件的 span
属性我们就可以自由地组合布局。
<template>
<el-row>
<el-col :span="24"><div class="grid-content bg-purple-dark" /></el-col>
</el-row>
<el-row>
<el-col :span="12"><div class="grid-content bg-purple" /></el-col>
<el-col :span="12"><div class="grid-content bg-purple-light" /></el-col>
</el-row>
<el-row>
<el-col :span="8"><div class="grid-content bg-purple" /></el-col>
<el-col :span="8"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="8"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
</el-row>
<el-row>
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple-light" /></el-col>
</el-row>
</template>
<style lang="scss">
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: rgba(192,132,252);
}
.bg-purple-light {
background: #e5e9f2;
}
</style>
分栏间隔
支持列间距。
行提供 gutter
属性来指定列之间的间距,其默认值为0。
<template>
<el-row :gutter="20">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
</template>
<style>
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: rgba(192,132,252);
}
.bg-purple-light {
background: #e5e9f2;
}
</style>
混合布局
通过基础的 1/24 分栏任意扩展组合形成较为复杂的混合布局。
<template>
<el-row :gutter="20">
<el-col :span="16"><div class="grid-content bg-purple" /></el-col>
<el-col :span="8"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><div class="grid-content bg-purple" /></el-col>
<el-col :span="8"><div class="grid-content bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
<el-col :span="16"><div class="grid-content bg-purple" /></el-col>
<el-col :span="4"><div class="grid-content bg-purple" /></el-col>
</el-row>
</template>
<style>
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.bg-purple {
background: rgba(192,132,252);
}
</style>
列偏移
您可以指定列偏移量。
通过制定 col 组件的 offset
属性可以指定分栏偏移的栏数。
<template>
<el-row :gutter="20">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6" :offset="6"
><div class="grid-content bg-purple"
/></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6" :offset="6"
><div class="grid-content bg-purple"
/></el-col>
<el-col :span="6" :offset="6"
><div class="grid-content bg-purple"
/></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12" :offset="6"
><div class="grid-content bg-purple"
/></el-col>
</el-row>
</template>
<style>
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.bg-purple {
background: rgba(192,132,252);
}
</style>
对齐方式
默认使用 flex 布局来对分栏进行灵活的对齐。
您可以通过justify
属性来定义子元素的排版方式,其取值为start、center、end、space-between、space-around或space-evenly。
<template>
<el-row class="row-bg">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row class="row-bg" justify="center">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row class="row-bg" justify="end">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row class="row-bg" justify="space-between">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row class="row-bg" justify="space-around">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
<el-row class="row-bg" justify="space-evenly">
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
<el-col :span="6"><div class="grid-content bg-purple" /></el-col>
</el-row>
</template>
<style>
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.bg-purple {
background: rgba(192,132,252);
}
.bg-purple-light {
background: #e5e9f2;
}
</style>
Row 属性
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
gutter | 栅格间隔 | number | — | 0 |
justify | flex 布局下的水平排列方式 | string | start/end/center/space-around/space-between/space-evenly | start |
align | flex 布局下的垂直排列方式 | string | top/middle/bottom | top |
tag | 自定义元素标签 | string | * | div |
Col 属性
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
span | 栅格占据的列数 | number | — | 24 |
offset | 栅格左侧的间隔格数 | number | — | 0 |
push | 栅格向右移动格数 | number | — | 0 |
pull | 栅格向左移动格数 | number | — | 0 |
xs | <768px 响应式栅格数或者栅格属性对象 |
number/object (例如 {span: 4, offset: 4}) | — | — |
sm | ≥768px 响应式栅格数或者栅格属性对象 |
number/object (例如 {span: 4, offset: 4}) | — | — |
md | ≥992px 响应式栅格数或者栅格属性对象 |
number/object (例如 {span: 4, offset: 4}) | — | — |
lg | ≥1200px 响应式栅格数或者栅格属性对象 |
number/object (例如 {span: 4, offset: 4}) | — | — |
xl | ≥1920px 响应式栅格数或者栅格属性对象 |
number/object (例如 {span: 4, offset: 4}) | — | — |
tag | 自定义元素标签 | string | * | div |
登录页
<template>
<div>
<el-form class="login-container">
<h3 class="login-title">系统登录</h3>
<el-form-item prop="username">
<el-input type="text" v-model="user.username" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="user.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item prop="checkCode">
<img><a href="#" >看不清</a>
<el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width:100%;" >登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "Login",
data(){
return{
user:{}
}
}
}
</script>
<style scoped="scoped">
.login-container{
width:350px;
margin:100px auto 10px;
border:1px solid #eaeaea;
padding:35px 35px 15px 35px;
border-radius: 15px;
box-shadow:0 0 25px #cac6c6;
}
.login-title{
text-align: center;
color:#505458;
margin:0px auto 40px;
}
</style>
图标
安装
npm install @element-plus/icons-vue
全局注册
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
使用
<template>
<p>
with extra class <b>is-loading</b>, your icon is able to rotate 360 deg in 2
seconds, you can also override this
</p>
<el-icon :size="20">
<Edit />
</el-icon>
<el-icon color="#409EFC" class="no-inherit">
<Share />
</el-icon>
<el-icon>
<Delete />
</el-icon>
<el-icon class="is-loading">
<Loading />
</el-icon>
<el-button type="primary">
<el-icon style="vertical-align: middle">
<Search />
</el-icon>
<span style="vertical-align: middle"> Search </span>
</el-button>
</template>
综合应用
登录页
功能
elementui的表单和表单元素
<template>
<div>
<el-form class="login-container">
<h3 class="login-title">系统登录</h3>
<el-form-item >
<el-input type="text" v-model="user.username" placeholder="账号"></el-input>
</el-form-item>
<el-form-item >
<el-input type="password" v-model="user.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item >
<el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width:100%;" >登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped="scoped">
.login-container{
width:350px;
margin:100px auto 10px;
border:1px solid #eaeaea;
padding:35px 35px 15px 35px;
border-radius: 15px;
box-shadow:0 0 25px #cac6c6;
}
.login-title{
text-align: center;
color:#505458;
margin:0px auto 40px;
}
</style>
验证码
前端
<el-form-item >
<img :src="imgUrl"><a href="#" @click="getCode">看不清</a>
<el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
</el-form-item>
return{
imgUrl:"http://localhost:8089/user/getCode"
}
getCode(){
this.imgUrl = this.imgUrl + "?"+Math.random();
}
后端
@RequestMapping("user/getCode")
public ResponseEntity<byte[]> getCode(HttpSession session) throws IOException{
char[] arr = {'0','1','2','3','4','5','6','7','8','9'};
StringBuilder stb = new StringBuilder();
int n;
for(int i = 0;i < 4;i++) {
n = (int)(Math.random()*arr.length);
stb.append(arr[n]);
}
session.setAttribute("code", stb.toString());
//把字符串放入到图片中,写入到输出流中
BufferedImage buffImg = new BufferedImage(90, 20, BufferedImage.TYPE_INT_RGB);
//得到画布
Graphics g = buffImg.getGraphics();
//向画布中写入字符串
g.drawString(stb.toString(), 20, 15);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(buffImg, "jpeg", bos);
return new ResponseEntity<byte[]>(bos.toByteArray(),HttpStatus.CREATED);
}
表单验证
在表单和表单元素上添加属性
<template>
<div >
<el-form class="login-container" :model="user" :rules="rules" ref="loginForm">
<h3 class="login-title">系统登录</h3>
<el-form-item prop="username">
<el-input type="text" v-model="user.username" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="user.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item prop="checkCode">
<img :src="imgUrl"><a href="#" @click="getCode">看不清</a>
<el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width:100%;" @click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
添加验证规则
<script>
export default {
data(){
let checkCodeValidate = (rule,value,callback)=>{
let url = "user/checkCode";
this.$http.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{
if(!resp.data){
callback(new Error("验证码输入错误!"));
}else{
callback();
}
});
};
return{
user:{},
rules:{
username:[
{required:true,message:'请输入用户名',trigger:['blur','change']},
{min:2,max:10,message:'用户名长度必须在2-10个字符之间',trigger:['blur','change']}
],
password:[
{required:true,message:'请输入密码',trigger:['blur','change']},
{min:2,max:10,message:'密码长度必须在2-10个字符之间',trigger:['blur','change']}
],
checkCode:[
{required:true,message:'请输入验证码',trigger:['blur','change']},
{pattern:/^\w{4}$/,message:"验证码长度必须为4位"},
{validator:checkCodeValidate,trigger:'blur'}
]
}
}
},
methods:{
login(){
this.$refs["loginForm"].validate(valid=>{
if(valid){
//登录
}
});
}
}
}
</script>
后端验证
@RequestMapping("user/checkCode")
public boolean checkCode(String checkCode,HttpSession session) {
String code = (String)session.getAttribute("code");
if(code != null && code.equals(checkCode)) {
return true;
}else {
return false;
}
}
axios
登录
login(){
this.$refs["loginForm"].validate(valid=>{
if(valid){
let url = "user/login";
this.$http.get(url,{params:this.user}).then((resp)=>{
if(resp.data){
this.$store.commit("setUsername",this.user.username);
this.$router.push("/main");
}else{
this.$alert("用户名或密码错误!");
}
});
}
});
}
验证验证码
let checkCodeValidate = (rule,value,callback)=>{
let url = "user/checkCode";
this.$http.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{
if(!resp.data){
callback(new Error("验证码输入错误!"));
}else{
callback();
}
});
};
vuex
登录成功后,修改state
this.$store.commit("setUsername",this.user.username);
import {createStore} from 'vuex'
export default createStore({
state: {
username: ''
},
mutations: {
setUsername(state, payload) {
state.username = payload;
}
},
getters: {
formatUsername(state) {
return `用户名:${state.username}`;
}
}
})
路由
登录成功后,跳转到主页
this.$router.push("/main");
import Login from './views/Login'
import {createRouter, createWebHistory} from 'vue-router'
export default createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'login',
component: Login
},
{
path: '/main',
name: 'main',
// 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('./views/Main.vue'),
}
]
})
键盘事件处理
回车提交表单
<template>
<div @keydown.enter="login">
//...
</div>
</template>
elementui的警告对话框alert
this.$alert("用户名或密码错误!");
完整登录页
<template>
<div @keydown.enter="login">
<el-form class="login-container" :model="user" :rules="rules" ref="loginForm">
<h3 class="login-title">系统登录</h3>
<el-form-item prop="username">
<el-input type="text" v-model="user.username" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="user.password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item prop="checkCode">
<img :src="imgUrl"><a href="#" @click="getCode">看不清</a>
<el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width:100%;" @click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data(){
let checkCodeValidate = (rule,value,callback)=>{
let url = "user/checkCode";
this.$http.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{
if(!resp.data){
callback(new Error("验证码输入错误!"));
}else{
callback();
}
});
};
return{
imgUrl:"http://localhost:8089/user/getCode",
user:{},
rules:{
username:[
{required:true,message:'请输入用户名',trigger:['blur','change']},
{min:2,max:10,message:'用户名长度必须在2-10个字符之间',trigger:['blur','change']}
],
password:[
{required:true,message:'请输入密码',trigger:['blur','change']},
{min:2,max:10,message:'密码长度必须在2-10个字符之间',trigger:['blur','change']}
],
checkCode:[
{required:true,message:'请输入验证码',trigger:['blur','change']},
{pattern:/^\w{4}$/,message:"验证码长度必须为4位"},
{validator:checkCodeValidate,trigger:'blur'}
]
}
}
},
methods:{
login(){
this.$refs["loginForm"].validate(valid=>{
if(valid){
let url = "user/login";
this.$http.get(url,{params:this.user}).then((resp)=>{
if(resp.data){
this.$store.commit("setUsername",this.user.username);
this.$router.push("/main");
}else{
this.$alert("用户名或密码错误!");
}
});
}
});
},
getCode(){
this.imgUrl = this.imgUrl + "?"+Math.random();
}
}
}
</script>
<style scoped="scoped">
.login-container{
width:350px;
margin:100px auto 10px;
border:1px solid #eaeaea;
padding:35px 35px 15px 35px;
border-radius: 15px;
box-shadow:0 0 25px #cac6c6;
}
.login-title{
text-align: center;
color:#505458;
margin:0px auto 40px;
}
</style>
主页
布局
<template>
<div>
<el-container>
<el-header>
<el-row>
<el-col :span="6">
<span class="main_title">东软人力资源管理系统</span>
</el-col>
<el-col :span="2" :offset="16" class="user_title">
ADMIN
</el-col>
</el-row>
</el-header>
<el-container class="main">
<el-aside width="200px">
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
<el-footer>Footer</el-footer>
</el-container>
</div>
</template>
<style scoped>
.el-header,
.el-footer {
height: 60px;
background-color: #409EFF;
}
a {
text-decoration: none;
}
.main_title {
font-size: 22px;
color: #fff;
height: 60px;
line-height: 60px;
}
.main{
min-height: 480px;
}
.user_title {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
Vuex
显示用户名
<span>
{ $store.state.username.toUpperCase() }(两个大括号)
</span>
导航
<el-aside width="200px" v-show="isShowMenu" >
<el-menu>
<el-sub-menu index="1">
<template #title>
<span>部门管理</span>
</template>
<el-menu-item index="1-1">
<router-link to="/dept">部门管理</router-link>
</el-menu-item>
<el-menu-item index="1-2">
<span><router-link to="/report">报表</router-link></span>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="2">
<span><router-link to="/emp">员工管理</router-link></span>
</el-menu-item>
</el-menu>
</el-aside>
图标
<el-icon><location /></el-icon>
<span>部门管理</span>
<el-icon><document /></el-icon>
<span><router-link to="/emp">员工管理</router-link></span>
下拉菜单
<el-col :span="2" :offset="16" class="user_title">
<el-dropdown class="el-dropdown-link" @command="handleCommand">
<span>
{ $store.state.username.toUpperCase() }
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="hideMenu">{ hideMenuText }</el-dropdown-item>
<el-dropdown-item command="exit">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<script>
export default {
data() {
return {
hideMenuText: '隐藏菜单',
isShowMenu: true
}
},
methods: {
handleCommand(command) {
if (command == "hideMenu") {
if (this.hideMenuText == "隐藏菜单") {
this.hideMenuText = "显示菜单";
this.isShowMenu = false;
} else {
this.hideMenuText = "隐藏菜单";
this.isShowMenu = true;
}
} else if (command == "exit") {
this.$confirm("真的要退出系统吗?", "提示").then(() => {
this.$router.push({name: 'login'});
this.$store.commit("setUsername", "");
}).catch(() => {
});
}
}
}
}
</script>
完整主页
<template>
<div>
<el-container>
<el-header>
<el-row>
<el-col :span="6">
<span class="main_title">东软人力资源管理系统</span>
</el-col>
<el-col :span="2" :offset="16" class="user_title">
<el-dropdown class="el-dropdown-link" @command="handleCommand">
<span>
{ $store.state.username.toUpperCase() }
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="hideMenu">{ hideMenuText }</el-dropdown-item>
<el-dropdown-item command="exit">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</el-header>
<el-container class="main">
<el-aside width="200px" v-show="isShowMenu" >
<el-menu>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>部门管理</span>
</template>
<el-menu-item index="1-1">
<router-link to="/dept">部门管理</router-link>
</el-menu-item>
<el-menu-item index="1-2">
<span><router-link to="/report">报表</router-link></span>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="2">
<el-icon><document /></el-icon>
<span><router-link to="/emp">员工管理</router-link></span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
<el-footer>Footer</el-footer>
</el-container>
</div>
</template>
<script>
export default {
data() {
return {
hideMenuText: '隐藏菜单',
isShowMenu: true
}
},
methods: {
handleCommand(command) {
if (command == "hideMenu") {
if (this.hideMenuText == "隐藏菜单") {
this.hideMenuText = "显示菜单";
this.isShowMenu = false;
} else {
this.hideMenuText = "隐藏菜单";
this.isShowMenu = true;
}
} else if (command == "exit") {
this.$confirm("真的要退出系统吗?", "提示").then(() => {
this.$router.push({name: 'login'});
this.$store.commit("setUsername", "");
}).catch(() => {
});
}
}
}
}
</script>
<style scoped>
.el-header,
.el-footer {
height: 60px;
background-color: #409EFF;
}
a {
text-decoration: none;
}
.main_title {
font-size: 22px;
color: #fff;
height: 60px;
line-height: 60px;
}
.main{
min-height: 480px;
}
.user_title {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.el-dropdown-link {
cursor: pointer;
color: #FFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>
部门管理
Dept.vue
<template>
<div>
<h1>部门管理</h1>
<el-button type="primary" @click="add">添加部门</el-button>
<el-button @click="handleExport">导出数据</el-button>
<el-table :data="depts">
<!-- <el-table-column label="部门编号" prop="deptno"></el-table-column> -->
<!-- <el-table-column label="部门名称" prop="dname"></el-table-column>
<el-table-column label="部门地址" prop="loc"></el-table-column> -->
<el-table-column label="部门名称">
<template #default="{row,$index}">
<template v-if="row.insert">
<el-input type="text" v-model="tempDepts[$index].deptno"></el-input>
</template>
<template v-else>
<span>{ row.deptno }</span>
</template>
</template>
</el-table-column>
<el-table-column label="部门名称">
<template #default="{row,$index}">
<template v-if="row.edit">
<el-input type="text" v-model="tempDepts[$index].dname"></el-input>
</template>
<template v-else-if="row.insert">
<el-input type="text" v-model="tempDepts[$index].dname"></el-input>
</template>
<template v-else>
<span>{ row.dname }</span>
</template>
</template>
</el-table-column>
<el-table-column label="部门地址">
<template #default="{row,$index}">
<template v-if="row.edit">
<el-input type="text" v-model="tempDepts[$index].loc"></el-input>
</template>
<template v-else-if="row.insert">
<el-input type="text" v-model="tempDepts[$index].loc"></el-input>
</template>
<template v-else>
<span>{ row.loc }</span>
</template>
</template>
</el-table-column>
<el-table-column>
<template #default="{row,$index}">
<template v-if="row.insert">
<el-button type="primary" @click="insert()">保存</el-button>
<el-button type="warning" @click="cancelAdd($index)">取消</el-button>
</template>
<template v-else-if="row.edit">
<el-button type="primary" @click="update(row,$index)">保存</el-button>
<el-button type="warning" @click="cancel(row,$index)">取消</el-button>
</template>
<template v-else>
<el-button type="primary" @click="edit(row)">编辑</el-button>
<el-button type="warning" @click="del(row,$index)">删除</el-button>
</template>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
depts: [],
tempDepts: [],
isAdd: false
}
},
created() {
this.showData();
},
methods: {
showData() {
let url = "dept/getAll";
this.$http.post(url).then(resp => {
this.depts = resp.data;
let s = JSON.stringify(this.depts);
this.tempDepts = JSON.parse(s);
});
return true;
},
del(row, index) {
this.$confirm('是否删除?', '提示').then(() => {
let url = `dept/delete?deptno=${row.deptno}`;
this.$http.get(url).then(resp => {
if (resp.data == 1) {
this.$message({
type: "success",
message: "删除成功!"
});
this.tempDepts.splice(index, 1)
this.depts.splice(index, 1)
} else {
this.$message({
type: "error",
message: "删除失败!"
});
}
});
}).catch(() => {
});
},
edit(row) {
row.edit = true;
},
cancel(row, index) {
row.edit = false;
this.tempDepts[index] = {...this.depts[index]};
},
update(row, index) {
let url = "dept/update";
this.$http.post(url, this.tempDepts[index]).then(resp => {
if (resp.data == 1) {
this.$message({
type: "success",
message: "更新成功!"
})
row.edit = false;
this.depts[index] = {...this.tempDepts[index]};
}
});
},
add() {
if (this.isAdd) {
this.$alert("不能同时添加多个部门!");
return;
}
this.isAdd = true;
this.depts.splice(0, 0, {deptno: '', dname: '', loc: '', insert: true});
this.tempDepts.splice(0, 0, {deptno: '', dname: '', loc: '', insert: true});
},
cancelAdd(index) {
this.$confirm("是否取消?", "提示").then(() => {
this.depts.splice(index, 1);
this.tempDepts.splice(index, 1);
this.isAdd = false;
}).catch(() => {
});
},
insert() {
let url = "dept/insert";
this.$http.post(url, this.tempDepts[0]).then(resp => {
if (resp.data == 1) {
this.$message({
type: "success",
message: "添加成功!"
});
this.tempDepts[0].insert = false;
this.depts[0] = {...this.tempDepts[0]};
} else {
this.$message({
type: "error",
message: "添加失败!"
});
}
this.isAdd = false;
});
},
handleExport() {
let url = "dept/exportExcel";
this.$http.get(url, {
//服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream',默认是'json'
responseType: "blob"
})
.then(res => {
if (!res) return
const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}) // 构造一个blob对象来处理数据,并设置文件类型
if (window.navigator.msSaveOrOpenBlob) { //兼容IE10
navigator.msSaveBlob(blob, this.filename)
} else {
const href = URL.createObjectURL(blob) //创建新的URL表示指定的blob对象
const a = document.createElement('a') //创建a标签
a.style.display = 'none'
a.href = href // 指定下载链接
a.download = "dept.xls" //指定下载文件名
a.click() //触发下载
URL.revokeObjectURL(a.href) //释放URL对象
}
// 这里也可以不创建a链接,直接window.open(href)也能下载
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
<style>
</style>
服务器端
控制器
package com.neu.controller;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import com.github.pagehelper.PageInfo;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.neu.po.Dept;
import com.neu.service.DeptService;
@RestController
@RequestMapping("dept")
public class DeptController {
@Autowired
private DeptService deptService;
@RequestMapping("getById")
public Dept getById(int deptno) {
Dept dept = deptService.getById(deptno);
return dept;
}
@RequestMapping("getAll")
public List<Dept> getAll() {
List<Dept> list = deptService.getAll();
return list;
}
@RequestMapping("delete")
public int delete(int deptno) {
return deptService.delete(deptno);
}
@RequestMapping("update")
public int update(@RequestBody Dept dept) {
return deptService.update(dept);
}
@RequestMapping("insert")
public int insert(@RequestBody Dept dept) {
return deptService.insert(dept);
}
@RequestMapping("exportExcel")
public ResponseEntity<byte[]> exportExcel() throws IOException {
List<Dept> list = deptService.getAll();
Workbook wb = new HSSFWorkbook();
Sheet sheet = wb.createSheet("部门");
// 起始行号
int rowNum = 1;
// 起始列号
int defaultColumnNum = 0;
int columnNum = defaultColumnNum;
Row titleRow = sheet.createRow(rowNum++);
Cell cell = titleRow.createCell(columnNum++);
cell.setCellValue("编号");
cell = titleRow.createCell(columnNum++);
cell.setCellValue("名称");
cell = titleRow.createCell(columnNum++);
cell.setCellValue("地址");
Row row = null;
for (Dept dept : list) {
row = sheet.createRow(rowNum++);
// 每一行的列号从头开始
columnNum = defaultColumnNum;
row.createCell(columnNum++).setCellValue(dept.getDeptno());
row.createCell(columnNum++).setCellValue(dept.getDname());
row.createCell(columnNum++).setCellValue(dept.getLoc());
}
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
Cell cellTitle = sheet.createRow(0).createCell(0);
cellTitle.setCellValue("部门列表");
CellStyle titleStyle = wb.createCellStyle();
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = wb.createFont();
font.setFontHeightInPoints((short)24);
//设置粗体
font.setBold(true);
font.setFontName("宋体");
font.setColor(IndexedColors.BLUE.index);
titleStyle.setFont(font);
cellTitle.setCellStyle(titleStyle);
// 创建一个字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 把工作簿内容写入到输出流中
wb.write(bos);
// 创建http协议的头
HttpHeaders headers = new HttpHeaders();
// headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 设置附件的名字
headers.setContentDispositionFormData("attachment", "dept.xls");
wb.close();
return new ResponseEntity<byte[]>(bos.toByteArray(), headers, HttpStatus.CREATED);
}
}
数据导出
jar
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
后端
@RequestMapping("exportExcel")
public ResponseEntity<byte[]> exportExcel() throws IOException {
List<Dept> list = deptService.getAll();
Workbook wb = new HSSFWorkbook();
Sheet sheet = wb.createSheet("部门");
// 起始行号
int rowNum = 1;
// 起始列号
int defaultColumnNum = 0;
int columnNum = defaultColumnNum;
Row titleRow = sheet.createRow(rowNum++);
Cell cell = titleRow.createCell(columnNum++);
cell.setCellValue("编号");
cell = titleRow.createCell(columnNum++);
cell.setCellValue("名称");
cell = titleRow.createCell(columnNum++);
cell.setCellValue("地址");
Row row = null;
for (Dept dept : list) {
row = sheet.createRow(rowNum++);
// 每一行的列号从头开始
columnNum = defaultColumnNum;
row.createCell(columnNum++).setCellValue(dept.getDeptno());
row.createCell(columnNum++).setCellValue(dept.getDname());
row.createCell(columnNum++).setCellValue(dept.getLoc());
}
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
Cell cellTitle = sheet.createRow(0).createCell(0);
cellTitle.setCellValue("部门列表");
CellStyle titleStyle = wb.createCellStyle();
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = wb.createFont();
font.setFontHeightInPoints((short)24);
//设置粗体
font.setBold(true);
font.setFontName("宋体");
font.setColor(IndexedColors.BLUE.index);
titleStyle.setFont(font);
cellTitle.setCellStyle(titleStyle);
// 创建一个字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 把工作簿内容写入到输出流中
wb.write(bos);
// 创建http协议的头
HttpHeaders headers = new HttpHeaders();
// headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 设置附件的名字
headers.setContentDispositionFormData("attachment", "dept.xls");
wb.close();
return new ResponseEntity<byte[]>(bos.toByteArray(), headers, HttpStatus.CREATED);
}
前端
<el-button @click="handleExport">导出数据</el-button>
handleExport() {
let url = "dept/exportExcel";
this.$http.get(url, {
//服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream',默认是'json'
responseType: "blob"
})
.then(res => {
if (!res) return
const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}) // 构造一个blob对象来处理数据,并设置文件类型
if (window.navigator.msSaveOrOpenBlob) { //兼容IE10
navigator.msSaveBlob(blob, this.filename)
} else {
const href = URL.createObjectURL(blob) //创建新的URL表示指定的blob对象
const a = document.createElement('a') //创建a标签
a.style.display = 'none'
a.href = href // 指定下载链接
a.download = "dept.xls" //指定下载文件名
a.click() //触发下载
URL.revokeObjectURL(a.href) //释放URL对象
}
// 这里也可以不创建a链接,直接window.open(href)也能下载
})
.catch(err => {
console.log(err)
})
}
数据导入
jar
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
后端Service
@Transactional//事务处理
public int importData(InputStream in) throws IOException {
Workbook wb = new HSSFWorkbook(in);
//得到工作簿的第一个sheet
Sheet sheet = wb.getSheetAt(0);
//得到数据最后一行的行号
int rows = sheet.getLastRowNum();
Row row;
Dept dept = null;
int id;
Integer deptno;
String dname;
String loc;
for(int i = 2;i <= rows;i++) {
//得到行对象
row = sheet.getRow(i);
deptno = (int)row.getCell(1).getNumericCellValue();//从第二列开始读取
dname = row.getCell(2).getStringCellValue();
loc = row.getCell(3).getStringCellValue();
dept = new Dept(deptno,dname,loc);
insert(dept);//调用当前类中的插入方法
}
try {
Thread.sleep(5000);//测试延迟加载效果
} catch (InterruptedException e) {
e.printStackTrace();
}
return rows - 1;
}
后端控制器
@PostMapping("import")
public int importData(MultipartFile file) throws IOException {
return deptService.importData(file.getInputStream());
}
前端
<el-upload
style="display: inline;margin-left:10px;"
:show-file-list="false"
:before-upload="beforeUpload"
:on-success="onSuccess"
:on-error="onError"
action="http://localhost:8089/dept/import"
:disabled="importDataDisabled"
>
<el-button type="success" :disabled="importDataDisabled" :icon="importDataIcon">{btnText}</el-button>
</el-upload>
//导入图标
import {UploadFilled,Loading} from '@element-plus/icons-vue'
data(){
return{
importDataDisabled:false,
importDataIcon:UploadFilled,
btnText:'导入数据'
}
}
//方法
beforeUpload(){
this.btnText = "正在导入";
this.importDataIcon = Loading;
this.importDataDisabled=true;
},
onSuccess(){
this.btnText = "导入数据";
this.importDataIcon = UploadFilled;
this.importDataDisabled=false;
this.$alert("导入成功!");
//刷新页面
this.getPaged(1,this.pageInfo.pageSize,this.keyword);
},
onError(){
this.btnText = "导入数据";
this.importDataIcon = UploadFilled;
this.importDataDisabled=false;
this.$alert("导入失败!");
}
excel文件
员工管理
<template>
<div>
<h1>员工管理</h1>
<el-button @click="add">添加员工</el-button>
<el-table :data="pageInfo.list">
<el-table-column width="80" label="编号" prop="empno"></el-table-column>
<el-table-column width="80" label="姓名" prop="ename"></el-table-column>
<el-table-column width="100" label="岗位" prop="job"></el-table-column>
<el-table-column width="80" label="经理" prop="mgr"></el-table-column>
<el-table-column width="120" label="入职日期" prop="hiredate"></el-table-column>
<el-table-column width="80" label="薪水" prop="sal"></el-table-column>
<el-table-column width="80" label="奖金" prop="comm"></el-table-column>
<el-table-column width="120" label="部门" prop="dept.dname"></el-table-column>
<el-table-column label="操作">
<template #default="{row}">
<el-button @click="edit(row)">编辑</el-button>
<el-button @click="del(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
layout="prev,pager,next,total,jumper,sizes"
:total="pageInfo.total"
v-model:currentPage="pageInfo.pageNum"
v-model:page-size="pageInfo.pageSize"
:page-sizes="[5,10,15,20]"
background
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
>
</el-pagination>
<el-dialog v-model="disableDialog" :title="dialogTitle" center width="500px">
<el-form>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="员工编号">
<el-input v-model="emp.empno" placeholder="员工编号"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="员工姓名">
<el-input v-model="emp.ename" placeholder="员工姓名"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="员工岗位">
<el-input v-model="emp.job" placeholder="员工岗位"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="员工经理">
<el-input v-model="emp.mgr" placeholder="员工经理"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="入职日期">
<el-date-picker v-model="emp.hiredate" placeholder="入职日期"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="员工薪水">
<el-input v-model="emp.sal" placeholder="员工薪水"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="员工奖金">
<el-input v-model="emp.comm" placeholder="员工奖金"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="员工部门">
<el-select v-model="emp.dept.deptno" style="width:215px">
<el-option
v-for="dept in depts"
:key="dept.deptno"
:value="dept.deptno"
:label="dept.dname"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button type="warning" @click="disableDialog=false">取消</el-button>
<el-button type="primary" @click="saveData">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
// import { ref } from 'vue'
export default {
data() {
return {
pageInfo: {},
disableDialog: false,
emp: {dept: {},
depts: [],
dialogTitle: ""
}
},
created() {
this.showData(1, 5);
this.getDepts();
},
methods: {
showData(pageNum, pageSize) {
let url = `emp/getPaged`;
this.$http.post(url, `pageNum=${pageNum}&pageSize=${pageSize}`).then((resp) => {
console.log(resp);
this.pageInfo = resp.data;
});
},
edit(row) {
this.disableDialog = true;
this.dialogTitle = "编辑员工";
this.emp = {...row};
},
add() {
this.disableDialog = true;
this.dialogTitle = "添加员工";
this.emp = {dept: {};
},
del(row) {
this.$confirm("是否删除员工?", "提示", {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let url = `emp/delete?empno=${row.empno}`;
// var url = `emp/delete?empno=1230`;
this.$http.get(url).then(resp => {
if (resp.data == 1) {
this.$message({
type: "success",
message: "删除成功!"
})
this.showData(1, this.pageInfo.pageSize);
} else {
this.$message({
type: 'error',
message: '删除失败!'
});
}
});
}
).catch(() => {
});
},
handleCurrentChange(pageNum) {
this.showData(pageNum, this.pageInfo.pageSize);
},
handleSizeChange(pageSize) {
this.showData(1, pageSize);
},
getDepts() {
var url = "dept/getAll";
this.$http.get(url).then(resp => {
this.depts = resp.data;
});
},
update() {
var url = "emp/update";
this.$http.post(url, this.emp).then(resp => {
if (resp.data == 1) {
this.$message({
type: "success",
message: "更新成功!"
});
this.disableDialog = false;
this.showData(1, this.pageInfo.pageSize);
}
});
},
saveData() {
if (this.dialogTitle == "编辑员工") {
this.update();
} else {
this.insert();
}
},
insert() {
var url = "emp/insert";
this.$http.post(url, this.emp).then(resp => {
if (resp.data == 1) {
this.$message({
type: "success",
message: "添加成功!"
});
this.disableDialog = false;
this.showData(1, this.pageInfo.pageSize);
}
});
}
}
}
</script>
<style>
</style>
下拉菜单
<el-header>
<!-- <h1>东软人力资源管理系统-{$store.state.username}</h1>
-->
<el-row>
<el-col :span="6">
<span class="main_title">东软人力资源管理系统</span>
</el-col>
<el-col :span="2" :offset="16" class="user_title">
<!-- -->
<el-dropdown class="el-dropdown-link" @command="handleCommand">
<span>
{ $store.state.username.toUpperCase() }
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="hideMenu">{ hideMenuText }</el-dropdown-item>
<el-dropdown-item command="exit">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</el-header>
data(){
return{
menuText:'隐藏菜单'
}
}
handleCommand(command){
if(command == "hideMenu"){
if(this.menuText == "隐藏菜单"){
this.menuText = "显示菜单";
}else{
this.menuText = "隐藏菜单";
}
}else if(command == "exit"){
this.$confirm("真的要退出系统吗?","提示").then(()=>{
this.$router.push("/login");
sessionStorage.clear();
}).catch(()=>{});
}
}
<el-aside v-show="menuText=='隐藏菜单'">
</el-aside>
记住用户名
复选框绑定变量
<el-checkbox v-model="remember">记住用户名</el-checkbox> data(){ return{ remember:false } }
登录成功后,判断是否选中了记住用户名
this.$http.get(url,{params:this.user}).then((resp)=>{ if(resp.data){ this.$store.commit("setUsername",this.user.username); if(this.remember){ localStorage.setItem("username",this.user.username); }else{ localStorage.removeItem("username"); } this.$router.push("/main"); }else{ this.$alert("用户名或密码错误!"); } })
在打开登录页的时候,判断localStorage中是否包含用户名
created(){ let username = localStorage.getItem("username"); if(username){ this.user.username = username; this.remember = true; } }
tabs
https://www.w3cschool.cn/vue_elementplus/vue_elementplus-d3ph3kqx.html
基础案例
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
<el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
<el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
<el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
data() {
return {
activeName: 'second'
};
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
}
}
};
</script>
选项卡样式
只需要设置 type 属性为 card 或 border-card 就可以使选项卡改变为标签风格。
<el-tabs type="border-card"> 或 <el-tabs type="card">
位置
标签一共有四个方向的设置 tabPosition=”left|right|top|bottom”
<el-tabs tab-position="left" style="height: 200px;">
<el-tab-pane label="用户管理">用户管理</el-tab-pane>
</el-tabs>
自定义标签页图标
<el-tabs type="border-card">
<el-tab-pane>
<template #label>
<el-icon><House /></el-icon>
我的行程
</template>
我的行程
</el-tab-pane>
<el-tab-pane label="消息中心">消息中心</el-tab-pane>
<el-tab-pane label="角色管理">角色管理</el-tab-pane>
<el-tab-pane label="定时任务补偿">定时任务补偿</el-tab-pane>
</el-tabs>
动态增减标签页
增减标签页按钮只能在选项卡样式的标签页下使用
<template>
<el-tabs
v-model="editableTabsValue"
type="card"
editable
@edit="handleTabsEdit"
>
<el-tab-pane
:key="item.name"
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{item.content}
</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
data() {
return {
editableTabsValue: '2',
editableTabs: [
{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content',
},
{
title: 'Tab 2',
name: '2',
content: 'Tab 2 content',
},
],
tabIndex: 2,
}
},
methods: {
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + ''
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content',
})
this.editableTabsValue = newTabName
}
//实现目标:如果关闭的tab不是当前激活的,关闭tab后,保留原来激活的tab
//如果关闭的是当前激活的tab,先激活下一个,如果没有,激活上一个
//如果关闭的是最后一个,不用设置激活的tab
if (action === 'remove') {
let tabs = this.editableTabs
let activeName = this.editableTabsValue
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1]
//如果存在可激活tab,设置激活tab的name到临时变量activeName中
if (nextTab) {
activeName = nextTab.name
}
}
})
}
this.editableTabsValue = activeName
this.editableTabs = tabs.filter((tab) => tab.name !== targetName)
}
},
},
}
</script>
自定义增加标签页触发器
<template>
<div style="margin-bottom: 20px;">
<el-button size="small" @click="addTab(editableTabsValue)">
add tab
</el-button>
</div>
<el-tabs
v-model="editableTabsValue"
type="card"
closable
@tab-remove="removeTab"
>
<el-tab-pane
v-for="(item, index) in editableTabs"
:key="item.name"
:label="item.title"
:name="item.name"
>
{item.content}
</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
data() {
return {
editableTabsValue: '2',
editableTabs: [
{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content',
},
{
title: 'Tab 2',
name: '2',
content: 'Tab 2 content',
},
],
tabIndex: 2,
}
},
methods: {
addTab(targetName) {
let newTabName = ++this.tabIndex + ''
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content',
})
this.editableTabsValue = newTabName
},
removeTab(targetName) {
let tabs = this.editableTabs
let activeName = this.editableTabsValue
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
this.editableTabsValue = activeName
this.editableTabs = tabs.filter((tab) => tab.name !== targetName)
},
},
}
</script>
关闭Tab
<el-tabs type="card" closable v-model="activeName" @tab-remove="handleRemove">
</el-tabs>
handleRemove(targetName){
let temp = this.activeName;
if(this.activeName === targetName){
this.editableTabs.forEach((tab,index)=>{
if(tab.name === this.activeName){
let nextTab = this.editableTabs[index+1]||this.editableTabs[index-1];
if(nextTab){
temp = nextTab.name;
}
}
})
}
this.activeName = temp;
//排除要删除的tab
this.editableTabs = this.editableTabs.filter(tab=>tab.name !== targetName);
}
springboot中JSON时区设置
#使用24小时的时间格式
#spring.jackson.default-property-inclusion=NON_NULL
#设置日期格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
#设置时区,GMT:格林威治时间
spring.jackson.time-zone=GMT+8