Vue.js


Vue.js

概述

  1. Vue是一个专注于构建web用户界面的 JavaScript 库
  2. 作者:尤雨溪,中国人,早前就职于 Google
  3. 思想:MVVM(Model-View-ViewModel)
  4. 一个渐进式的框架

使用

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参数特性

  1. 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>
    
  2. number:如果想自动将用户输入值类型转换为数值类型,可以使用number修饰符

    可通过vue开发者工具查看对象数据类型

    <input type="text" v-model.number="km">千米<br>
    
  3. 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>
    

样式绑定

  1. 绑定 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>
    
  2. 可以把一个数组传递给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>
    
  3. 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>
    
  4. 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

  1. vue.js开发的标准工具

  2. Node.js:是一个基于 Chrome V8引擎的 JavaScript运行时环境(需要安装)

  3. npm:是随着node.js一起安装的包管理工具,允许用户从npm服务器下载别人编写好的第三方包到本地

    测试:npm –version

  4. 配置npm源:

    npm config set registry http://192.168.1.190:5001/repository/dhee-group-npm/
    
    外网
    npm config set registry http://registry.npm.taobao.org/
    
  5. 安装vuecli

    npm i -g @vue/cli@4.5.13
    
    //安装最新版
    npm i -g @vue/cli
    

创建项目

vue create 项目名

单页面应用

在Idea中打开项目

选择上面创建好的Vue项目

运行项目

npm run serve

组件

  1. 定义:可以复用的Vue实例,且带有一个名字

  2. 使用

    • 定义组件

                  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相同

  3. vue组件命名(非单文件组件中)

    在模板中(挂载点)的标签名(组件名),使用短横线命名法(单词之间用短横线分隔),所有字符小写

  4. 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>
      

父组件向子组件传值

  1. 使用 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"
                    ]
                };
    
  2. 在子组件中,可以向使用data的属性一样,来使用props中的属性

  3. 注意:在子组件中不要去改变属性的值

通过 自定义事件 子组件向父组件传值

  1. 在 子组件中 触发自定义事件(什么时候想向父组件传值,什么时候触发)

    this.$emit("retval",this.n);
    
  2. 父组件中,订阅子组件触发的自定义事件

    <my-btn :num="num" @retval="getValue($event)"></my-btn>
    
  3. 注意:自定义的事件名与组件命名规则相同

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>

路由

  1. 作用:在应用中切换组件

  2. 使用

    • 创建组件

    • 导入路由文件

      <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>
      

路由传参

  1. 向切换后的组件传递参数

  2. 动态路由

    //路由表
    {
        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;
    
  3. 查询参数(查询参数会显示在地址栏中)

    //路由表
    {
        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;
    
  4. 注意:path不能与params同时使用,params会被忽略掉

Vue-Cli

  1. vue.js开发的标准工具

  2. Node.js:是一个基于 Chrome V8引擎的 JavaScript运行时环境

  3. npm:是随着node.js一起安装的包管理工具,允许用户从npm服务器下载别人编写好的第三方包到本地

    测试:npm –version

  4. 安装 cnpm

    npm install -g cnpm --registry=http://registry.npm.taobao.org
    
  5. 安装 Vue-Cli

    cnpm install -g @vue/cli
    
  6. 启动项目

    npm run serve
    
  7. 安装路由(脚手架插件)

    vue add router
    

    npm(cnpm) install vue-router
    

Axios

  1. 安装:

    vue add axios
    
  2. 定义:易用、简洁且高效的http库

  3. 作用:用户向后台服务器发送异步请求,并处理响应的结果

  4. 配置:

    //基础路径配置
    axios.defaults.baseURL="http://localhost:8089/"
    //允许传递证书
    axios.defaults.withCredentials=true
    
  5. 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);
    });
    
  6. 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

  1. 定义:易用、简洁且高效的http库

  2. 安装

    npm install axios
    
  3. 配置

    /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
    
  4. 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("*");
        }
    }
    
  5. 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("登录失败!")
              }
            })
    
  6. 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);
        }
    
  7. 封装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

  1. 安装

    npm install --save vuex
    
  2. Vuex是Vue的状态管理库,用来管理状态(应用程序中的信息或数据)。它将状态存储在一个中心位置,使得任何组件都很容易与之交互

  3. 使用

    • 在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 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

  1. 安装

    npm install vuex@next --save
    
  2. 使用

    • 在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});//异步操作
      

路由守卫

  1. 在路由切换前,对路由进行检查

  2. 通过路由守卫实现修改网页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”):跳转到参数指定的地址
  3. 使用路由守卫进行登录检查

    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();
    })
    

路由监听

  1. <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

  1. sessionStorage:会话存储,关闭浏览器,信息丢失
  2. localStorage:本地存储,关闭浏览器,信息还在,存储到硬盘上
  3. setItem(key,value):value只能是字符串
  4. getItem(key)
  5. removeItem(key)

JSON

  1. JSON.stringify(对象):把对象转换为字符串
  2. JSON.parse(字符串):把字符串转换为JSON对象

ElementUI

  1. 安装

    npm i element-ui -S
    
  2. 修改main.js

    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    
    //在vue中安装 element-ui插件
    Vue.use(ElementUI)
    
  3. 登录页前端

    <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>
    
  4. 登录页后端

    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

  1. 安装:

    npm install vue-router
    
    
    npm install vue-router@4
    
  2. 配置

    • 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')
                    }
                ]
            }
        ]
    })
    
  3. 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()
    })
    
  4. 主要内容

    • router-view 将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。
    • router-link 没有使用常规的 a 标签,而是使用一个自定义组件 router-link 来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
  5. 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>
    
  6. 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-headerel-footer 时为 vertical,否则为 horizontal

Container 插槽

插槽名 说明 子标签
自定义默认内容 Container / Header / Aside / Main / Footer

Header 属性

属性 说明 类型 可选值 默认值
height 顶栏高度 string 60px

Header 插槽

插槽名 说明
自定义默认内容

Aside 属性

属性 说明 类型 可选值 默认值
width 侧边栏宽度 string 300px

Aside 插槽

插槽名 说明
自定义默认内容

Main 插槽

插槽名 说明
自定义默认内容
属性 说明 类型 可选值 默认值
height 底栏高度 string 60px
插槽名 说明
自定义默认内容

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 分栏,迅速简便地创建布局。

基础布局

​ 使用列创建基础网格布局。

​ 通过 rowcol 组件,并通过 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>

记住用户名

  1. 复选框绑定变量

    <el-checkbox  v-model="remember">记住用户名</el-checkbox>
    
    data(){
        return{
            remember:false
        }
    }
    
  2. 登录成功后,判断是否选中了记住用户名

    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("用户名或密码错误!");
        }
    })
    
  3. 在打开登录页的时候,判断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

文章作者: FFFfrance
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 FFFfrance !