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

基础布局

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

​ 通过 row 和 col 组件,并通过 col 组件的 span 属性我们就可以自由地组合布局。

​

<template>
  <el-row>
    <el-col :span="24"><div class="grid-content bg-purple-dark" /></el-col>
  </el-row>
  <el-row>
    <el-col :span="12"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="12"><div class="grid-content bg-purple-light" /></el-col>
  </el-row>
  <el-row>
    <el-col :span="8"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="8"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="8"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
  </el-row>
  <el-row>
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple-light" /></el-col>
  </el-row>
</template>

<style lang="scss">
.el-row {
  margin-bottom: 20px;
}
.el-row:last-child {
  margin-bottom: 0;
}
.el-col {
  border-radius: 4px;
}

.grid-content {
  border-radius: 4px;
  min-height: 36px;
}

.bg-purple-dark {
    background: #99a9bf;
}

.bg-purple {
    background: rgba(192,132,252);
}

.bg-purple-light {
    background: #e5e9f2;
}
</style>

分栏间隔

支持列间距。

行提供 gutter 属性来指定列之间的间距,其默认值为0。

<template>
  <el-row :gutter="20">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
</template>

<style>
.el-row {
  margin-bottom: 20px;
}
.el-row:last-child {
  margin-bottom: 0;
}
.el-col {
  border-radius: 4px;
}

.grid-content {
  border-radius: 4px;
  min-height: 36px;
}
.bg-purple-dark {
  background: #99a9bf;
}

.bg-purple {
  background: rgba(192,132,252);
}

.bg-purple-light {
  background: #e5e9f2;
}
</style>

混合布局

通过基础的 1/24 分栏任意扩展组合形成较为复杂的混合布局。

<template>
  <el-row :gutter="20">
    <el-col :span="16"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="8"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row :gutter="20">
    <el-col :span="8"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="8"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row :gutter="20">
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="16"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="4"><div class="grid-content bg-purple" /></el-col>
  </el-row>
</template>

<style>
.el-row {
  margin-bottom: 20px;
}
.el-row:last-child {
  margin-bottom: 0;
}
.el-col {
  border-radius: 4px;
}

.grid-content {
  border-radius: 4px;
  min-height: 36px;
}
.bg-purple {
  background: rgba(192,132,252);
}
</style>

列偏移

您可以指定列偏移量。

通过制定 col 组件的 offset 属性可以指定分栏偏移的栏数。

<template>
  <el-row :gutter="20">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6" :offset="6"
      ><div class="grid-content bg-purple"
    /></el-col>
  </el-row>
  <el-row :gutter="20">
    <el-col :span="6" :offset="6"
      ><div class="grid-content bg-purple"
    /></el-col>
    <el-col :span="6" :offset="6"
      ><div class="grid-content bg-purple"
    /></el-col>
  </el-row>
  <el-row :gutter="20">
    <el-col :span="12" :offset="6"
      ><div class="grid-content bg-purple"
    /></el-col>
  </el-row>
</template>

<style>
.el-row {
  margin-bottom: 20px;
}
.el-row:last-child {
  margin-bottom: 0;
}
.el-col {
  border-radius: 4px;
}

.grid-content {
  border-radius: 4px;
  min-height: 36px;
}
.bg-purple {
  background: rgba(192,132,252);
}
</style>

对齐方式

默认使用 flex 布局来对分栏进行灵活的对齐。

您可以通过justify 属性来定义子元素的排版方式,其取值为start、center、end、space-between、space-around或space-evenly。

<template>
  <el-row class="row-bg">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row class="row-bg" justify="center">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row class="row-bg" justify="end">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row class="row-bg" justify="space-between">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row class="row-bg" justify="space-around">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
  <el-row class="row-bg" justify="space-evenly">
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple-light" /></el-col>
    <el-col :span="6"><div class="grid-content bg-purple" /></el-col>
  </el-row>
</template>

<style>
.el-row {
  margin-bottom: 20px;
}
.el-row:last-child {
  margin-bottom: 0;
}
.el-col {
  border-radius: 4px;
}

.grid-content {
  border-radius: 4px;
  min-height: 36px;
}

.bg-purple {
  background: rgba(192,132,252);
}

.bg-purple-light {
  background: #e5e9f2;
}
</style>

Row 属性

属性 说明 类型 可选值 默认值
gutter 栅格间隔 number — 0
justify flex 布局下的水平排列方式 string start/end/center/space-around/space-between/space-evenly start
align flex 布局下的垂直排列方式 string top/middle/bottom top
tag 自定义元素标签 string * div

Col 属性

属性 说明 类型 可选值 默认值
span 栅格占据的列数 number — 24
offset 栅格左侧的间隔格数 number — 0
push 栅格向右移动格数 number — 0
pull 栅格向左移动格数 number — 0
xs <768px 响应式栅格数或者栅格属性对象 number/object (例如 {span: 4, offset: 4}) — —
sm ≥768px 响应式栅格数或者栅格属性对象 number/object (例如 {span: 4, offset: 4}) — —
md ≥992px 响应式栅格数或者栅格属性对象 number/object (例如 {span: 4, offset: 4}) — —
lg ≥1200px 响应式栅格数或者栅格属性对象 number/object (例如 {span: 4, offset: 4}) — —
xl ≥1920px 响应式栅格数或者栅格属性对象 number/object (例如 {span: 4, offset: 4}) — —
tag 自定义元素标签 string * div

登录页

<template>
  <div>
    <el-form class="login-container">
      <h3 class="login-title">系统登录</h3>
      <el-form-item prop="username">
        <el-input type="text" v-model="user.username" placeholder="账号"></el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input type="password" v-model="user.password" placeholder="密码"></el-input>
      </el-form-item>
      <el-form-item prop="checkCode">
        <img><a href="#" >看不清</a>
        <el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" style="width:100%;" >登录</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: "Login",
  data(){
    return{
      user:{}
    }
  }
}
</script>

<style scoped="scoped">
.login-container{
  width:350px;
  margin:100px auto 10px;
  border:1px solid #eaeaea;
  padding:35px 35px 15px 35px;
  border-radius: 15px;
  box-shadow:0 0 25px #cac6c6;
}
.login-title{
  text-align: center;
  color:#505458;
  margin:0px auto 40px;
}
</style>

图标

安装

npm install @element-plus/icons-vue

全局注册

import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

使用

<template>
  <p>
    with extra class <b>is-loading</b>, your icon is able to rotate 360 deg in 2
    seconds, you can also override this
  </p>
  <el-icon :size="20">
    <Edit />
  </el-icon>
  <el-icon color="#409EFC" class="no-inherit">
    <Share />
  </el-icon>
  <el-icon>
    <Delete />
  </el-icon>
  <el-icon class="is-loading">
    <Loading />
  </el-icon>
  <el-button type="primary">
    <el-icon style="vertical-align: middle">
      <Search />
    </el-icon>
    <span style="vertical-align: middle"> Search </span>
  </el-button>
</template>

综合应用

登录页

功能

elementui的表单和表单元素
<template>
    <div>
        <el-form class="login-container">
            <h3 class="login-title">系统登录</h3>
            <el-form-item >
                <el-input type="text" v-model="user.username" placeholder="账号"></el-input>
            </el-form-item>
            <el-form-item >
                <el-input type="password" v-model="user.password" placeholder="密码"></el-input>
            </el-form-item>
            <el-form-item >
                <el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" style="width:100%;" >登录</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>

<style scoped="scoped">
    .login-container{
        width:350px;
        margin:100px auto 10px;
        border:1px solid #eaeaea;
        padding:35px 35px 15px 35px;
        border-radius: 15px;
        box-shadow:0 0 25px #cac6c6;
    }
    .login-title{
        text-align: center;
        color:#505458;
        margin:0px auto 40px;
    }
</style>
验证码

前端

<el-form-item >
    <img :src="imgUrl"><a href="#" @click="getCode">看不清</a>
    <el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
</el-form-item>
return{
 imgUrl:"http://localhost:8089/user/getCode"
}
getCode(){
    this.imgUrl = this.imgUrl + "?"+Math.random();
}

后端

@RequestMapping("user/getCode")
    public ResponseEntity<byte[]> getCode(HttpSession session) throws IOException{
        char[] arr = {'0','1','2','3','4','5','6','7','8','9'};
        StringBuilder stb = new StringBuilder();
        int n;
        for(int i = 0;i < 4;i++) {
            n = (int)(Math.random()*arr.length);
            stb.append(arr[n]);
        }
        
        session.setAttribute("code", stb.toString());
        
        //把字符串放入到图片中,写入到输出流中
        BufferedImage buffImg = new BufferedImage(90, 20, BufferedImage.TYPE_INT_RGB);
        //得到画布
        Graphics g = buffImg.getGraphics();
        //向画布中写入字符串
        g.drawString(stb.toString(), 20, 15);
        
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(buffImg, "jpeg", bos);
        
        return new ResponseEntity<byte[]>(bos.toByteArray(),HttpStatus.CREATED);
    }
表单验证

在表单和表单元素上添加属性

<template>
    <div >
        <el-form class="login-container" :model="user" :rules="rules" ref="loginForm">
            <h3 class="login-title">系统登录</h3>
            <el-form-item prop="username">
                <el-input type="text" v-model="user.username" placeholder="账号"></el-input>
            </el-form-item>
            <el-form-item prop="password">
                <el-input type="password" v-model="user.password" placeholder="密码"></el-input>
            </el-form-item>
            <el-form-item prop="checkCode">
                <img :src="imgUrl"><a href="#" @click="getCode">看不清</a>
                <el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" style="width:100%;" @click="login">登录</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>

添加验证规则

<script>
    export default {
        data(){
            let checkCodeValidate = (rule,value,callback)=>{
                let url = "user/checkCode";
                this.$http.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{
                    if(!resp.data){
                        callback(new Error("验证码输入错误!"));
                    }else{
                        callback();
                    }
                });
            };

            return{
                user:{},
                rules:{
                    username:[
                        {required:true,message:'请输入用户名',trigger:['blur','change']},
                        {min:2,max:10,message:'用户名长度必须在2-10个字符之间',trigger:['blur','change']}
                    ],
                    password:[
                        {required:true,message:'请输入密码',trigger:['blur','change']},
                        {min:2,max:10,message:'密码长度必须在2-10个字符之间',trigger:['blur','change']}
                    ],
                    checkCode:[
                        {required:true,message:'请输入验证码',trigger:['blur','change']},
                        {pattern:/^\w{4}$/,message:"验证码长度必须为4位"},
                        {validator:checkCodeValidate,trigger:'blur'}
                    ]
                }
            }
        },
        methods:{
            login(){
                this.$refs["loginForm"].validate(valid=>{
                    if(valid){
                        //登录
                    }
                });
            }
        }
    }
</script>

后端验证

@RequestMapping("user/checkCode")
    public boolean checkCode(String checkCode,HttpSession session) {
        String code = (String)session.getAttribute("code");
        
        if(code != null && code.equals(checkCode)) {
            return true;
        }else {
            return false;
        }
        
    }
axios

登录

login(){
                this.$refs["loginForm"].validate(valid=>{
                    if(valid){
                        let url = "user/login";
                        this.$http.get(url,{params:this.user}).then((resp)=>{
                            if(resp.data){
                this.$store.commit("setUsername",this.user.username);
                                this.$router.push("/main");
                            }else{
                                this.$alert("用户名或密码错误!");
                            }
                        });
                    }
                });
            }

验证验证码

let checkCodeValidate = (rule,value,callback)=>{
                let url = "user/checkCode";
                this.$http.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{
                    if(!resp.data){
                        callback(new Error("验证码输入错误!"));
                    }else{
                        callback();
                    }
                });
            };
vuex

登录成功后,修改state

this.$store.commit("setUsername",this.user.username);
import {createStore} from 'vuex'

export default createStore({
    state: {
        username: ''
    },
    mutations: {
        setUsername(state, payload) {
            state.username = payload;
        }
    },
    getters: {
        formatUsername(state) {
            return `用户名:${state.username}`;
        }
    }
})
路由

登录成功后,跳转到主页

this.$router.push("/main");
import Login from './views/Login'
import {createRouter, createWebHistory} from 'vue-router'

export default createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/',
            name: 'login',
            component: Login
        },
        {
            path: '/main',
            name: 'main',
            // route level code-splitting
            // this generates a separate chunk (about.[hash].js) for this route
            // which is lazy-loaded when the route is visited.
            component: () => import('./views/Main.vue'),
         }
    ]
})
键盘事件处理

回车提交表单

<template>
    <div @keydown.enter="login">
        //...
    </div>
</template>	
elementui的警告对话框alert
this.$alert("用户名或密码错误!");
完整登录页
<template>
    <div @keydown.enter="login">
        <el-form class="login-container" :model="user" :rules="rules" ref="loginForm">
            <h3 class="login-title">系统登录</h3>
            <el-form-item prop="username">
                <el-input type="text" v-model="user.username" placeholder="账号"></el-input>
            </el-form-item>
            <el-form-item prop="password">
                <el-input type="password" v-model="user.password" placeholder="密码"></el-input>
            </el-form-item>
            <el-form-item prop="checkCode">
                <img :src="imgUrl"><a href="#" @click="getCode">看不清</a>
                <el-input type="text" v-model="user.checkCode" placeholder="验证码"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" style="width:100%;" @click="login">登录</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>

<script>
    export default {
        data(){
            let checkCodeValidate = (rule,value,callback)=>{
                let url = "user/checkCode";
                this.$http.post(url,`checkCode=${this.user.checkCode}`).then(resp=>{
                    if(!resp.data){
                        callback(new Error("验证码输入错误!"));
                    }else{
                        callback();
                    }
                });
            };

            return{
                imgUrl:"http://localhost:8089/user/getCode",
                user:{},
                rules:{
                    username:[
                        {required:true,message:'请输入用户名',trigger:['blur','change']},
                        {min:2,max:10,message:'用户名长度必须在2-10个字符之间',trigger:['blur','change']}
                    ],
                    password:[
                        {required:true,message:'请输入密码',trigger:['blur','change']},
                        {min:2,max:10,message:'密码长度必须在2-10个字符之间',trigger:['blur','change']}
                    ],
                    checkCode:[
                        {required:true,message:'请输入验证码',trigger:['blur','change']},
                        {pattern:/^\w{4}$/,message:"验证码长度必须为4位"},
                        {validator:checkCodeValidate,trigger:'blur'}
                    ]
                }
            }
        },
        methods:{
            login(){
                this.$refs["loginForm"].validate(valid=>{
                    if(valid){
                        let url = "user/login";
                        this.$http.get(url,{params:this.user}).then((resp)=>{
                            if(resp.data){
                this.$store.commit("setUsername",this.user.username);
                                this.$router.push("/main");
                            }else{
                                this.$alert("用户名或密码错误!");
                            }
                        });
                    }
                });
            },
            getCode(){
                this.imgUrl = this.imgUrl + "?"+Math.random();
            }
        }
    }
</script>

<style scoped="scoped">
    .login-container{
        width:350px;
        margin:100px auto 10px;
        border:1px solid #eaeaea;
        padding:35px 35px 15px 35px;
        border-radius: 15px;
        box-shadow:0 0 25px #cac6c6;
    }
    .login-title{
        text-align: center;
        color:#505458;
        margin:0px auto 40px;
    }
</style>

主页

布局
<template>
  <div>
    <el-container>
      <el-header>
        <el-row>
          <el-col :span="6">
            <span class="main_title">东软人力资源管理系统</span>
          </el-col>
          <el-col :span="2" :offset="16" class="user_title">
            ADMIN
          </el-col>
        </el-row>
      </el-header>
      <el-container class="main">
        <el-aside width="200px">          
        </el-aside>

        <el-main>
          <router-view></router-view>
        </el-main>

      </el-container>
      <el-footer>Footer</el-footer>
    </el-container>
  </div>
</template>

<style scoped>
.el-header,
.el-footer {
  height: 60px;
  background-color: #409EFF;
}

a {
  text-decoration: none;
}

.main_title {
  font-size: 22px;
  color: #fff;
  height: 60px;
  line-height: 60px;
}

.main{
  min-height: 480px;
}

.user_title {
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
Vuex

显示用户名

<span>
    { $store.state.username.toUpperCase() }(两个大括号)
</span>
导航
<el-aside width="200px" v-show="isShowMenu" >
          <el-menu>
            <el-sub-menu index="1">
              <template #title>                
                <span>部门管理</span>
              </template>
              <el-menu-item index="1-1">
                <router-link to="/dept">部门管理</router-link>
              </el-menu-item>
              <el-menu-item index="1-2">
                <span><router-link to="/report">报表</router-link></span>
              </el-menu-item>
            </el-sub-menu>
            <el-menu-item index="2">
              <span><router-link to="/emp">员工管理</router-link></span>
            </el-menu-item>
          </el-menu>
</el-aside>
图标
<el-icon><location /></el-icon>
<span>部门管理</span>

<el-icon><document /></el-icon>
<span><router-link to="/emp">员工管理</router-link></span>
下拉菜单
<el-col :span="2" :offset="16" class="user_title">
            <el-dropdown class="el-dropdown-link" @command="handleCommand">
                        <span>
              { $store.state.username.toUpperCase() }
                            <i class="el-icon-arrow-down el-icon--right"></i>
                        </span>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item command="hideMenu">{ hideMenuText }</el-dropdown-item>
                  <el-dropdown-item command="exit">退出登录</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
</el-col>
<script>
export default {
  data() {
    return {
      hideMenuText: '隐藏菜单',
      isShowMenu: true
    }
  },
  methods: {
    handleCommand(command) {
      if (command == "hideMenu") {
        if (this.hideMenuText == "隐藏菜单") {
          this.hideMenuText = "显示菜单";
          this.isShowMenu = false;
        } else {
          this.hideMenuText = "隐藏菜单";
          this.isShowMenu = true;
        }
      } else if (command == "exit") {
        this.$confirm("真的要退出系统吗?", "提示").then(() => {
          this.$router.push({name: 'login'});
          this.$store.commit("setUsername", "");
        }).catch(() => {
        });
      }
    }
  }
}
</script>
完整主页
<template>
  <div>
    <el-container>
      <el-header>
        <el-row>
          <el-col :span="6">
            <span class="main_title">东软人力资源管理系统</span>
          </el-col>
          <el-col :span="2" :offset="16" class="user_title">
            
            <el-dropdown class="el-dropdown-link" @command="handleCommand">
                        <span>
              { $store.state.username.toUpperCase() }
                            <i class="el-icon-arrow-down el-icon--right"></i>
                        </span>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item command="hideMenu">{ hideMenuText }</el-dropdown-item>
                  <el-dropdown-item command="exit">退出登录</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </el-col>
        </el-row>
      </el-header>
      <el-container class="main">
        <el-aside width="200px" v-show="isShowMenu" >
          <el-menu>
            <el-sub-menu index="1">
              <template #title>
                <el-icon><location /></el-icon>
                <span>部门管理</span>
              </template>
              <el-menu-item index="1-1">
                <router-link to="/dept">部门管理</router-link>
              </el-menu-item>
              <el-menu-item index="1-2">
                <span><router-link to="/report">报表</router-link></span>
              </el-menu-item>
            </el-sub-menu>
            <el-menu-item index="2">
              <el-icon><document /></el-icon>
              <span><router-link to="/emp">员工管理</router-link></span>
            </el-menu-item>
          </el-menu>
        </el-aside>

        <el-main>
          <router-view></router-view>
        </el-main>

      </el-container>
      <el-footer>Footer</el-footer>
    </el-container>
  </div>
</template>

<script>
export default {
  data() {
    return {
      hideMenuText: '隐藏菜单',
      isShowMenu: true
    }
  },
  methods: {
    handleCommand(command) {
      if (command == "hideMenu") {
        if (this.hideMenuText == "隐藏菜单") {
          this.hideMenuText = "显示菜单";
          this.isShowMenu = false;
        } else {
          this.hideMenuText = "隐藏菜单";
          this.isShowMenu = true;
        }
      } else if (command == "exit") {
        this.$confirm("真的要退出系统吗?", "提示").then(() => {
          this.$router.push({name: 'login'});
          this.$store.commit("setUsername", "");
        }).catch(() => {
        });
      }
    }
  }
}
</script>

<style scoped>
.el-header,
.el-footer {
  height: 60px;
  background-color: #409EFF;
}

a {
  text-decoration: none;
}

.main_title {
  font-size: 22px;
  color: #fff;
  height: 60px;
  line-height: 60px;
}

.main{
  min-height: 480px;
}

.user_title {
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.el-dropdown-link {
  cursor: pointer;
  color: #FFF;
}

.el-icon-arrow-down {
  font-size: 12px;
}
</style>

部门管理

Dept.vue

<template>
  <div>
    <h1>部门管理</h1>
    <el-button type="primary" @click="add">添加部门</el-button>
    <el-button @click="handleExport">导出数据</el-button>
    <el-table :data="depts">
      <!-- <el-table-column label="部门编号" prop="deptno"></el-table-column> -->
      <!-- <el-table-column label="部门名称" prop="dname"></el-table-column>
      <el-table-column label="部门地址" prop="loc"></el-table-column> -->
      <el-table-column label="部门名称">
        <template #default="{row,$index}">
          <template v-if="row.insert">
            <el-input type="text" v-model="tempDepts[$index].deptno"></el-input>
          </template>
          <template v-else>
            <span>{ row.deptno }</span>
          </template>
        </template>
      </el-table-column>
      <el-table-column label="部门名称">
        <template #default="{row,$index}">
          <template v-if="row.edit">
            <el-input type="text" v-model="tempDepts[$index].dname"></el-input>
          </template>
          <template v-else-if="row.insert">
            <el-input type="text" v-model="tempDepts[$index].dname"></el-input>
          </template>
          <template v-else>
            <span>{ row.dname }</span>
          </template>
        </template>
      </el-table-column>
      <el-table-column label="部门地址">
        <template #default="{row,$index}">
          <template v-if="row.edit">
            <el-input type="text" v-model="tempDepts[$index].loc"></el-input>
          </template>
          <template v-else-if="row.insert">
            <el-input type="text" v-model="tempDepts[$index].loc"></el-input>
          </template>
          <template v-else>
            <span>{ row.loc }</span>
          </template>
        </template>
      </el-table-column>
      <el-table-column>
        <template #default="{row,$index}">
          <template v-if="row.insert">
            <el-button type="primary" @click="insert()">保存</el-button>
            <el-button type="warning" @click="cancelAdd($index)">取消</el-button>
          </template>
          <template v-else-if="row.edit">
            <el-button type="primary" @click="update(row,$index)">保存</el-button>
            <el-button type="warning" @click="cancel(row,$index)">取消</el-button>
          </template>
          <template v-else>
            <el-button type="primary" @click="edit(row)">编辑</el-button>
            <el-button type="warning" @click="del(row,$index)">删除</el-button>
          </template>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      depts: [],
      tempDepts: [],
      isAdd: false
    }
  },
  created() {
    this.showData();
  },
  methods: {
    showData() {
      let url = "dept/getAll";
      this.$http.post(url).then(resp => {
        this.depts = resp.data;
        let s = JSON.stringify(this.depts);
        this.tempDepts = JSON.parse(s);
      });
      return true;
    },
    del(row, index) {
      this.$confirm('是否删除?', '提示').then(() => {
        let url = `dept/delete?deptno=${row.deptno}`;
        this.$http.get(url).then(resp => {
          if (resp.data == 1) {
            this.$message({
              type: "success",
              message: "删除成功!"
            });
            this.tempDepts.splice(index, 1)
            this.depts.splice(index, 1)
          } else {
            this.$message({
              type: "error",
              message: "删除失败!"
            });
          }
        });
      }).catch(() => {
      });
    },
    edit(row) {
      row.edit = true;
    },
    cancel(row, index) {
      row.edit = false;
      this.tempDepts[index] = {...this.depts[index]};
    },
    update(row, index) {
      let url = "dept/update";
      this.$http.post(url, this.tempDepts[index]).then(resp => {
        if (resp.data == 1) {
          this.$message({
            type: "success",
            message: "更新成功!"
          })
          row.edit = false;
          this.depts[index] = {...this.tempDepts[index]};
        }
      });
    },
    add() {
      if (this.isAdd) {
        this.$alert("不能同时添加多个部门!");
        return;
      }
      this.isAdd = true;
      this.depts.splice(0, 0, {deptno: '', dname: '', loc: '', insert: true});
      this.tempDepts.splice(0, 0, {deptno: '', dname: '', loc: '', insert: true});
    },
    cancelAdd(index) {
      this.$confirm("是否取消?", "提示").then(() => {
        this.depts.splice(index, 1);
        this.tempDepts.splice(index, 1);
        this.isAdd = false;
      }).catch(() => {
      });
    },
    insert() {
      let url = "dept/insert";
      this.$http.post(url, this.tempDepts[0]).then(resp => {
        if (resp.data == 1) {
          this.$message({
            type: "success",
            message: "添加成功!"
          });
          this.tempDepts[0].insert = false;
          this.depts[0] = {...this.tempDepts[0]};
        } else {
          this.$message({
            type: "error",
            message: "添加失败!"
          });
        }

        this.isAdd = false;
      });
    },
    handleExport() {
      let url = "dept/exportExcel";

      this.$http.get(url, {
        //服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream',默认是'json'
        responseType: "blob"
      })
          .then(res => {
            if (!res) return
            const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}) // 构造一个blob对象来处理数据,并设置文件类型

            if (window.navigator.msSaveOrOpenBlob) { //兼容IE10
              navigator.msSaveBlob(blob, this.filename)
            } else {
              const href = URL.createObjectURL(blob) //创建新的URL表示指定的blob对象
              const a = document.createElement('a') //创建a标签
              a.style.display = 'none'
              a.href = href // 指定下载链接
              a.download = "dept.xls" //指定下载文件名
              a.click() //触发下载
              URL.revokeObjectURL(a.href) //释放URL对象
            }
            // 这里也可以不创建a链接,直接window.open(href)也能下载
          })
          .catch(err => {
            console.log(err)
          })
    }
  }

}
</script>

<style>
</style>

服务器端

控制器

package com.neu.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import com.github.pagehelper.PageInfo;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.neu.po.Dept;
import com.neu.service.DeptService;

@RestController
@RequestMapping("dept")
public class DeptController {

    @Autowired
    private DeptService deptService;

    @RequestMapping("getById")
    public Dept getById(int deptno) {
        Dept dept = deptService.getById(deptno);

        return dept;
    }

    @RequestMapping("getAll")
    public List<Dept> getAll() {
        List<Dept> list = deptService.getAll();

        return list;
    }

    @RequestMapping("delete")
    public int delete(int deptno) {

        return deptService.delete(deptno);
    }

    @RequestMapping("update")
    public int update(@RequestBody Dept dept) {

        return deptService.update(dept);
    }

    @RequestMapping("insert")
    public int insert(@RequestBody Dept dept) {

        return deptService.insert(dept);
    }

    @RequestMapping("exportExcel")
    public ResponseEntity<byte[]> exportExcel() throws IOException {
        List<Dept> list = deptService.getAll();

        Workbook wb = new HSSFWorkbook();
        Sheet sheet = wb.createSheet("部门");

        // 起始行号
        int rowNum = 1;
        // 起始列号
        int defaultColumnNum = 0;

        int columnNum = defaultColumnNum;

        Row titleRow = sheet.createRow(rowNum++);

        Cell cell = titleRow.createCell(columnNum++);
        cell.setCellValue("编号");

        cell = titleRow.createCell(columnNum++);
        cell.setCellValue("名称");

        cell = titleRow.createCell(columnNum++);
        cell.setCellValue("地址");

        Row row = null;
        for (Dept dept : list) {
            row = sheet.createRow(rowNum++);

            // 每一行的列号从头开始
            columnNum = defaultColumnNum;

            row.createCell(columnNum++).setCellValue(dept.getDeptno());
            row.createCell(columnNum++).setCellValue(dept.getDname());
            row.createCell(columnNum++).setCellValue(dept.getLoc());
        }

        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));

        Cell cellTitle = sheet.createRow(0).createCell(0);
        cellTitle.setCellValue("部门列表");

        CellStyle titleStyle = wb.createCellStyle();
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        Font font = wb.createFont();
        font.setFontHeightInPoints((short)24);
        //设置粗体
        font.setBold(true);
        font.setFontName("宋体");
        font.setColor(IndexedColors.BLUE.index);

        titleStyle.setFont(font);

        cellTitle.setCellStyle(titleStyle);

        // 创建一个字节数组输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        // 把工作簿内容写入到输出流中
        wb.write(bos);

        // 创建http协议的头
        HttpHeaders headers = new HttpHeaders();
//		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 设置附件的名字
        headers.setContentDispositionFormData("attachment", "dept.xls");

        wb.close();

        return new ResponseEntity<byte[]>(bos.toByteArray(), headers, HttpStatus.CREATED);

    }

}

数据导出

jar

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>

后端

@RequestMapping("exportExcel")
    public ResponseEntity<byte[]> exportExcel() throws IOException {
        List<Dept> list = deptService.getAll();

        Workbook wb = new HSSFWorkbook();
        Sheet sheet = wb.createSheet("部门");

        // 起始行号
        int rowNum = 1;
        // 起始列号
        int defaultColumnNum = 0;

        int columnNum = defaultColumnNum;

        Row titleRow = sheet.createRow(rowNum++);

        Cell cell = titleRow.createCell(columnNum++);
        cell.setCellValue("编号");

        cell = titleRow.createCell(columnNum++);
        cell.setCellValue("名称");

        cell = titleRow.createCell(columnNum++);
        cell.setCellValue("地址");

        Row row = null;
        for (Dept dept : list) {
            row = sheet.createRow(rowNum++);

            // 每一行的列号从头开始
            columnNum = defaultColumnNum;

            row.createCell(columnNum++).setCellValue(dept.getDeptno());
            row.createCell(columnNum++).setCellValue(dept.getDname());
            row.createCell(columnNum++).setCellValue(dept.getLoc());
        }

        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));

        Cell cellTitle = sheet.createRow(0).createCell(0);
        cellTitle.setCellValue("部门列表");

        CellStyle titleStyle = wb.createCellStyle();
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        Font font = wb.createFont();
        font.setFontHeightInPoints((short)24);
        //设置粗体
        font.setBold(true);
        font.setFontName("宋体");
        font.setColor(IndexedColors.BLUE.index);

        titleStyle.setFont(font);

        cellTitle.setCellStyle(titleStyle);

        // 创建一个字节数组输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        // 把工作簿内容写入到输出流中
        wb.write(bos);

        // 创建http协议的头
        HttpHeaders headers = new HttpHeaders();
//		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 设置附件的名字
        headers.setContentDispositionFormData("attachment", "dept.xls");

        wb.close();

        return new ResponseEntity<byte[]>(bos.toByteArray(), headers, HttpStatus.CREATED);

    }

前端

<el-button @click="handleExport">导出数据</el-button>
handleExport() {
      let url = "dept/exportExcel";

      this.$http.get(url, {
        //服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream',默认是'json'
        responseType: "blob"
      })
          .then(res => {
            if (!res) return
            const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}) // 构造一个blob对象来处理数据,并设置文件类型

            if (window.navigator.msSaveOrOpenBlob) { //兼容IE10
              navigator.msSaveBlob(blob, this.filename)
            } else {
              const href = URL.createObjectURL(blob) //创建新的URL表示指定的blob对象
              const a = document.createElement('a') //创建a标签
              a.style.display = 'none'
              a.href = href // 指定下载链接
              a.download = "dept.xls" //指定下载文件名
              a.click() //触发下载
              URL.revokeObjectURL(a.href) //释放URL对象
            }
            // 这里也可以不创建a链接,直接window.open(href)也能下载
          })
          .catch(err => {
            console.log(err)
          })
    }

数据导入

jar

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>

后端Service

@Transactional//事务处理
    public int importData(InputStream in) throws IOException {
        Workbook wb = new HSSFWorkbook(in);
        //得到工作簿的第一个sheet
        Sheet sheet = wb.getSheetAt(0);
        //得到数据最后一行的行号
        int rows = sheet.getLastRowNum();

        Row row;
        Dept dept = null;
        int id;
        Integer deptno;
        String dname;
        String loc;

        for(int i = 2;i <= rows;i++) {
            //得到行对象
            row = sheet.getRow(i);
            deptno = (int)row.getCell(1).getNumericCellValue();//从第二列开始读取
            dname = row.getCell(2).getStringCellValue();
            loc = row.getCell(3).getStringCellValue();

            dept = new Dept(deptno,dname,loc);

            insert(dept);//调用当前类中的插入方法

        }

        try {
            Thread.sleep(5000);//测试延迟加载效果
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return rows - 1;
    }

后端控制器

@PostMapping("import")
public int importData(MultipartFile file) throws IOException {
    return deptService.importData(file.getInputStream());
}

前端

<el-upload
        style="display: inline;margin-left:10px;"
        :show-file-list="false"
        :before-upload="beforeUpload"
        :on-success="onSuccess"
        :on-error="onError"
        action="http://localhost:8089/dept/import"
        :disabled="importDataDisabled"
    >
      <el-button type="success" :disabled="importDataDisabled" :icon="importDataIcon">{btnText}</el-button>
</el-upload>

//导入图标
import {UploadFilled,Loading} from '@element-plus/icons-vue'

data(){
    return{
        importDataDisabled:false,
        importDataIcon:UploadFilled,
        btnText:'导入数据'
    }
}
//方法
beforeUpload(){
    this.btnText = "正在导入";
    this.importDataIcon = Loading;
    this.importDataDisabled=true;				
},
onSuccess(){
    this.btnText = "导入数据";
    this.importDataIcon = UploadFilled;
    this.importDataDisabled=false;

    this.$alert("导入成功!");
    //刷新页面
    this.getPaged(1,this.pageInfo.pageSize,this.keyword);
},
onError(){
    this.btnText = "导入数据";
    this.importDataIcon = UploadFilled;
    this.importDataDisabled=false;

    this.$alert("导入失败!");
}

excel文件

员工管理

<template>
  <div>
    <h1>员工管理</h1>
    <el-button @click="add">添加员工</el-button>
    <el-table :data="pageInfo.list">
      <el-table-column width="80" label="编号" prop="empno"></el-table-column>
      <el-table-column width="80" label="姓名" prop="ename"></el-table-column>
      <el-table-column width="100" label="岗位" prop="job"></el-table-column>
      <el-table-column width="80" label="经理" prop="mgr"></el-table-column>
      <el-table-column width="120" label="入职日期" prop="hiredate"></el-table-column>
      <el-table-column width="80" label="薪水" prop="sal"></el-table-column>
      <el-table-column width="80" label="奖金" prop="comm"></el-table-column>
      <el-table-column width="120" label="部门" prop="dept.dname"></el-table-column>
      <el-table-column label="操作">
        <template #default="{row}">
          <el-button @click="edit(row)">编辑</el-button>
          <el-button @click="del(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <el-pagination
        layout="prev,pager,next,total,jumper,sizes"
        :total="pageInfo.total"
        v-model:currentPage="pageInfo.pageNum"
        v-model:page-size="pageInfo.pageSize"
        :page-sizes="[5,10,15,20]"
        background
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
    >
    </el-pagination>

    <el-dialog v-model="disableDialog" :title="dialogTitle" center width="500px">
      <el-form>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="员工编号">
              <el-input v-model="emp.empno" placeholder="员工编号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="员工姓名">
              <el-input v-model="emp.ename" placeholder="员工姓名"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="员工岗位">
              <el-input v-model="emp.job" placeholder="员工岗位"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="员工经理">
              <el-input v-model="emp.mgr" placeholder="员工经理"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="入职日期">
              <el-date-picker v-model="emp.hiredate" placeholder="入职日期"></el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="员工薪水">
              <el-input v-model="emp.sal" placeholder="员工薪水"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="员工奖金">
              <el-input v-model="emp.comm" placeholder="员工奖金"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="员工部门">
              <el-select v-model="emp.dept.deptno" style="width:215px">
                <el-option
                    v-for="dept in depts"
                    :key="dept.deptno"
                    :value="dept.deptno"
                    :label="dept.dname"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button type="warning" @click="disableDialog=false">取消</el-button>
        <el-button type="primary" @click="saveData">保存</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script>
// import { ref } from 'vue'
export default {
  data() {
    return {
      pageInfo: {},
      disableDialog: false,
      emp: {dept: {},
      depts: [],
      dialogTitle: ""
    }
  },
  created() {
    this.showData(1, 5);
    this.getDepts();
  },
  methods: {
    showData(pageNum, pageSize) {
      let url = `emp/getPaged`;

      this.$http.post(url, `pageNum=${pageNum}&pageSize=${pageSize}`).then((resp) => {
        console.log(resp);
        this.pageInfo = resp.data;
      });
    },
    edit(row) {
      this.disableDialog = true;
      this.dialogTitle = "编辑员工";
      this.emp = {...row};
    },
    add() {
      this.disableDialog = true;
      this.dialogTitle = "添加员工";
      this.emp = {dept: {};
    },
    del(row) {
      this.$confirm("是否删除员工?", "提示", {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
            let url = `emp/delete?empno=${row.empno}`;
            // var url = `emp/delete?empno=1230`;
            this.$http.get(url).then(resp => {
              if (resp.data == 1) {
                this.$message({
                  type: "success",
                  message: "删除成功!"
                })
                this.showData(1, this.pageInfo.pageSize);
              } else {
                this.$message({
                  type: 'error',
                  message: '删除失败!'
                });
              }
            });
          }
      ).catch(() => {
      });
    },
    handleCurrentChange(pageNum) {
      this.showData(pageNum, this.pageInfo.pageSize);
    },
    handleSizeChange(pageSize) {
      this.showData(1, pageSize);
    },
    getDepts() {
      var url = "dept/getAll";

      this.$http.get(url).then(resp => {
        this.depts = resp.data;
      });
    },
    update() {
      var url = "emp/update";
      this.$http.post(url, this.emp).then(resp => {
        if (resp.data == 1) {
          this.$message({
            type: "success",
            message: "更新成功!"
          });
          this.disableDialog = false;
          this.showData(1, this.pageInfo.pageSize);
        }
      });
    },
    saveData() {
      if (this.dialogTitle == "编辑员工") {
        this.update();
      } else {
        this.insert();
      }
    },
    insert() {
      var url = "emp/insert";
      this.$http.post(url, this.emp).then(resp => {
        if (resp.data == 1) {
          this.$message({
            type: "success",
            message: "添加成功!"
          });
          this.disableDialog = false;
          this.showData(1, this.pageInfo.pageSize);
        }
      });
    }
  }
}
</script>

<style>
</style>

下拉菜单

<el-header>
        <!-- <h1>东软人力资源管理系统-{$store.state.username}</h1>
         -->
        <el-row>
          <el-col :span="6">
            <span class="main_title">东软人力资源管理系统</span>
          </el-col>
          <el-col :span="2" :offset="16" class="user_title">
            <!--  -->
            <el-dropdown class="el-dropdown-link" @command="handleCommand">
                        <span>
              { $store.state.username.toUpperCase() }
              <el-icon class="el-icon--right">
                <arrow-down />
              </el-icon>
                        </span>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item command="hideMenu">{ hideMenuText }</el-dropdown-item>
                  <el-dropdown-item command="exit">退出登录</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </el-col>
        </el-row>
      </el-header>
data(){
    return{
        menuText:'隐藏菜单'
    }
}

handleCommand(command){
    if(command == "hideMenu"){
        if(this.menuText == "隐藏菜单"){
            this.menuText = "显示菜单";
        }else{
            this.menuText = "隐藏菜单";
        }
    }else if(command == "exit"){
        this.$confirm("真的要退出系统吗?","提示").then(()=>{
            this.$router.push("/login");
            sessionStorage.clear();
        }).catch(()=>{});
    }
}

<el-aside v-show="menuText=='隐藏菜单'">
</el-aside>

记住用户名

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