Skip to the content.

学习提要

必备插件

VSCode 开发安装插件

学习内容

后端开发人员学 Vue,因此重点在学会怎么用,学会常用的组件库。

版本选择

ESLint

vue2入门

入门例子

下面为 vue2 的一个基本示例,展示了如何创建 Vue 对象,将 Vue 绑定到 DOM 上,以及 if-else,for 的语法。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h4></h4>
        <li>
            <!-- 就是 if-else -->
            <span v-if="!item.del">未删除 </span>
            <span v-else>删除了 </span> <br />
            <!-- 为 true 就显示 -->
            <span v-show="item.show">显示!</span>
        </li>
        <h4>for 循环用法</h4>
        <!-- 循环遍历 -->
        <li v-for=" item in list">
            <span></span><br>
        </li>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    // 创建一个 vue 对象,这个对象绑定到 id 为 app 的 dom 上
    // data 为在 dom 中需要使用的数据
    const vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world',
            item: {
                title: 'some',
                del: false,
                show: true
            },
            list: [{
                name: 'tom'
            }, {
                name: 'jetty'
            }]
        }
    })
</script>

</html>

组件注册

如果我们在 html 中需要多次用到同样的东西,我们可以把它抽取成一个『组件』,然后进行 html 块的复用。

组件的定义及使用如下。

<body>
    <div id="app">
        <h4>组件</h4>
        <div v-for=" item in list">
            <todo-item :title="item.name" :del="true" :show="true"></todo-item>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    Vue.component('todo-item', {
        // 组件中的具体内容(即 html)
        template: `<li><span v-if="!del">未删除 </span><span v-else>删除了 </span> <br /></li>`,
        // 为组件定义一些属性(为类定义属性值),直接传递属性给组件
        props: {
            title: String,
            del: {
                type: Boolean,
                default: false
            },
        },
        // 组件内部的数据, 要返回(return),也可以 data(){ return{} } 这样写,一样的效果。
        data: function() {
            return { name: '123'};
        },
        // 组件内部用到的一些方法,如点击事件
        methods: {},
    })
    const vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world',
            list: [{ name: 'tom'}, { name: 'jetty' }]
        }
    })
</script>

</html>

绑定事件

<body>
    <div id="app">
        <h4>组件</h4>
        <todo-list></todo-list>
    </div>
</body>
<script>
    // 子组件
    Vue.component('todo-item', {
        // 组件中的具体内容(即 html)
        template: `<li><span></span><button @click='delData'>删除</button></li>`,
        props: { title: String },
        // 组件内部用到的一些方法,如点击事件
        methods: {
            delData(args) {
                console.log("删除数据", args);
                // 假定处理逻辑是:子组件点击删除事件后,触发父组件的 delete 事件
                this.$emit('delete', 1, 2);
            }
        },
    });

    // 父组件
    Vue.component('todo-list', {
        // 父组件通过 :title 把自己的数据传递给子组件
        template: `<div><todo-item @delete='handleDel' v-for="item in list" :title="item.name"></todo-item></div>`,
        data() {
            return {
                list: [{ name: 'tom' }, { name: 'jetty' }]
            }
        },
        methods: {
            handleDel(arg1, arg2) {
                console.log("触发了父组件的 @delete 事件", arg1, arg2);
            }
        }
    })

    const vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world',
        }
    });
</script>

</html>

事件修饰符

事件处理 — Vue.js (vuejs.org)

插槽(填充标签)

插槽:定义子组件的时候,在子组件内刨了一个坑,父组件想办法往坑里填内容。

例如,我们用 todo-item 组件,我们希望可以在这个标签填充一些其他标签。

<todo-item>
    <!-- vue 2.6 的用法 -->
    <template v-slot:pre-icon>前置</template>
    <template v-slot:suf-icon>后置</template>
</todo-item>

<script>
    Vue.component('todo-item', {
        // 组件中的具体内容(即 html)
        template: `<li> 
												<slot name='pre-icon'></slot>
												<span></span>
												<slot name='suf-icon'></slot>
												<button @click='delData'>删除</button>
            		</li>`,
        props: {
            title: String,
        },
        methods: {
            delData(args) {
                console.log("删除数据", args);
                // 子组件点击删除事件后,触发父组件的 delete 事件,删除 todo-item
                this.$emit('delete', 1, 2);
            }
        },
</script>

完整代码

<body>
    <div id="app">
        <todo-list></todo-list>
    </div>
</body>
<script>
    // 子组件
    Vue.component('todo-item', {
        // 组件中的具体内容(即 html)
        template: `<li> 
                        <slot name='pre-icon'></slot>
                        <span></span>
                        <slot name='suf-icon'></slot>
                        <button @click='delData'>删除</button>
            		</li>`,
        props: {
            title: String,
        },

        methods: {
            delData(args) {
                console.log("删除数据", args);
                // 子组件点击删除事件后,触发父组件的 delete 事件,删除 todo-item
                this.$emit('delete', 1, 2);
            }
        },
    });

    // 父组件
    Vue.component('todo-list', {
        template: `<div>
                        <todo-item @delete='handleDel' v-for="item in list" :title="item.name">
                            <template v-slot:pre-icon>前置</template>
                            <template v-slot:suf-icon>后置</template>
                        </todo-item>
                    </div>`,
        data() {
            return {
                list: [{ name: 'tom' }, { name: 'jetty'}]
            };
        },
        methods: {
            handleDel(arg1, arg2) {
                console.log("触发了父组件的 @delete 事件", arg1, arg2);
            }
        }
    })

    const vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world',
        }
    });
</script>

</html>

我们也可以向插槽传递属性(父组件向子组件的插槽传递属性)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

</head>

<body>
    <div id="app">
        <todo-list></todo-list>
    </div>
</body>
<script>
    // 子组件
    Vue.component('todo-item', {
        // 组件中的具体内容(即 html)
        template: `<li> 
                    <slot name='pre-icon' :value='123'></slot>
                    <span></span>
            	</li>`,

        props: {
            title: String,
        }
    });

    // 父组件, 注意写法 v-slot:pre-icon='{value}' 这是为了后面  取出 value 值用。
    Vue.component('todo-list', {
        template: `<div>
                        <todo-item v-for="item in list" :title="item.name">
                            <template v-slot:pre-icon='{value}'>前置</template>
                        </todo-item>
                    </div>`,
        data() {
            return {
                list: [{ name: 'tom'}, { name: 'jetty'}]
            };
        }
    })

    const vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world',
        }
    });
</script>

</html>

单文件组件

优点

ES6 导入导出

//xxx.js
let a = 10
// 默认导入
import m1 from './js/xxx.js'
// 在webpack中,每个js文件都是独立的模块
// 每个模块都有独立的作用域
// 其他模块,默认无法直接访问当前模块中定义的成员。
console.log(m1)
//xxx.js
let a = 10
let b = 20
// 这个export default{} 语法叫做默认导出。
// 在一个模块中,仅允许导出一次
export default {
    a: a,
    // 属性值和属性名一直可以简写。
    b,
    say(){
        console.log("hello")
    }
}
// 按需导入
import { 成员名称 } from '模块名'

//eg
import m2,{xx} from "xxx.js"

// as 取别名
import m2,{test1 as myTest} from "xx.js"
// 按需导出
export var a = 10

单文件组件开发

Vue 文件结构说明

每个 .Vue 文件,都是一个 vue 组件(叫做单文件组件),它由三部分组成:

定义组件 Demo

<template>
  <div>
    <h3>这是组件Home </h3>
  </div>
</template>

<script>
export default {
  name: "Home",
  data() {
    return { msg: "hello vue" }
  },
  methods: {},
  filters: {}
}
</script>

<style scoped>
h3 {
  color: bisque;
}
</style>

定义组件

<template>
  <div>
    <h3>这是Son 组件</h3>
  </div>
</template>

<script>
export default {
  name: "Son"
}
</script>

全局组件

import Home from "@/components/Home";
import Vue from "vue";

Vue.component('Home', Home)

私有组件

import Son from "./Son"

console.log(Son.name);
export default {
  name: "Home",
  data() {
    return { msg: "hello vue" }
  },
  methods: {},
  filters: {},
  // 定义私有组件
  components: {
    'my-son': Son
  }
}

组件化Vue

可以认为组件是特殊的 Vue 实例

组件和实例的相同和区别:

为什么组件中的 data 必须定义为一个方法并返回一个对象

因为这样,能够保证每次创建的组件实例,都有自己的一块唯一的数据内存,防止组件之间数据的干扰。

组件样式控制

父组件的样式会影响子组件,如何解决?

默认情况下,组件中定义的样式是全局生效的。如何样式只在当前组件内生效?

给 style 加上 scope 属性,即可。如何做到的?只要为组件添加了 scope 那么当前组件(不包括引入的组件)所有的 标签 都会使用同一个属性。

<style scope> </style>

组件数据通信

父传子

在父组件中,以标签形式使用子组件时,可以通过属性绑定,为子组件传递数据。

在子组件中,如果向父组件传递过来的数据,必须先定义 props 数组来接收

接收完 props 数据,可以直接在子组件的 template 区域使用


代码

子组件

<template>
  <div>
    <button @click="objFromParent.a++">a自增</button>
    <h1>子组件---->-----> </h1>
  </div>
</template>

<script>
import _ from 'loadsh'

export default {
  name: "Son",
  // 而 data 中的数据 可读可写
  data() {
    // 建议使用转存的数据,以便满足修改的请求。
    // 对于对象类型的数据, 存储的是地址值,我们需要把数据拷贝一份,不修改源数据。
    // 深拷贝 安装 lodash npm install lodash -S
    return {
      infoFormParent: this.pmsg,
      objFromParent: _.cloneDeep(this.obj)
    }
  },
  // 子组件需要使用 props 数组,接收外界传递过来的数据,接收到的数据可以直接在Son中使用
  // 通过 props 接收的数据,是只读的。不要为它们重新赋值。
  props: ['pmsg', 'obj']
}
</script>

父组件

<template>
  <div>
    <h1>父组件</h1>
    <button @click="sendData">发送数组给子组件</button>
    <!--在使用组件的时候,通过 属性绑定,把数据传递给子组件-->
    <my-son :pmsg="parentMsg" :obj="obj"></my-son>
  </div>
</template>

<script>
import Son from "@/components/Father2Son/Son";

export default {
  name: "Parent",
  data() {
    return {
      parentMsg: '继承我的花呗',
      obj: { a: 10, b: 20 }
    }
  },
  methods: {
    sendData() {}
  },
  components: {
    'my-son': Son
  }
}
</script>

渲染调用

<template>
  <div id="app">
    <Parent></Parent>
  </div>
</template>

<script>
import Parent from "@/components/Father2Son/Parent";
import Vue from "vue";
Vue.component('Parent', Parent)

export default {
  components: {Parent}
}

</script>

子传父

通过事件绑定机制,子传数据给父

父为子绑定事件,然后子把自己的数据传递过去。

父亲调用方法会接收到子的数据,这时候就得到了子的数据。

代码Demo

子组件

<template>
  <div>
    <h1>子组件</h1>
    <button @click="btnHandler">触发func事件</button>
    <button @click="btnHandler2">触发func2事件,带参数</button>
  </div>
</template>

<script>
export default {
  name: "Son",
  data() {
    return {
      msg: ': 我是子组件的值'
    }
  },
  methods: {
    btnHandler() {
      //$emit表示触发事件 , 在子组件中,通过  this.$emit() 触发父组件 为子组件绑定的 func 事件。
      // func 是父组件为子组件绑定的事件。
      this.$emit('func') // 调用父组件给子组件的事件 func
    },
    btnHandler2() {
      this.$emit('func2', this.msg) // 调用父组件给子组件的事件 func
    }
  }
}
</script>

父组件

<template>
  <div>
    <h1>父组件</h1>
    <!--在使用组件的时候,通过 属性绑定,把数据传递给子组件-->
    <my-son @func="show" @func2="show2"></my-son>
  </div>
</template>

<script>
import Son from "@/components/Son2Father/Son";

export default {
  name: "Parent",
  data() {
    return {}
  },
  methods: {
    show(){
      console.log("有人调用了父组件的show方法!")
    },
    show2(args){
      console.log("父组件的 show2 带有参数"+args)
    }
  },
  components: {
    'my-son': Son
  }
}
</script>

兄弟传兄弟

思路

定义一个公共的Vue实例,如 bus.js 实例名称为 bus。

数据发送方,调用 bus.$emit() 触发 bus 上的某个事件,从而把数据发送出去。

在数据接收方,使用 bus.$on() 自定义事件,并指定事件处理函数。


代码示例

公共Vue实例 bus.js

import Vue from 'vue'

const bus = new Vue()
export default bus

发送数据方

<template>
  <div>
    <h1>哥哥</h1>
    <button @click="sendMsgToDD">哥哥给弟弟数据</button>
  </div>
</template>

<script>
import bus from './bus'

export default {
  name: "GG",
  data() {
    return {
      msg: '哥哥有糖给弟弟'
    }
  },
  methods: {
    sendMsgToDD() {
      // 在数据发送方,调用bus.$emit() 触发 bus 上的某个事件,从而把数据发送出去
      bus.$emit('ooo', this.msg)
    }
  }
}
</script>

接收数据方

<template>
  <div>
    <h3>弟弟</h3>
  </div>
</template>

<script>
import bus from './bus'

export default {
  name: "DD",
  data() {
    return {}
  },
  created() {
    // 在数据接收方 使用 bus.$on 自定义事件,并指定事件处理函数
    bus.$on('ooo', data => {
      console.log("弟弟拿到了哥哥的数据,哥哥的数据是:"+data)
    })
  }
}
</script>

使用 this.$refs 来获取元素和组件

基本使用

1.把要获取的 DOM 元素,添加 ref 属性,创建一个 DOM 对象的引用,指定的值,就是引用的名称

<p ref="myP">这是父组件</p>

2.如果要获取某个引用所对应的 DOM 对象,则直接使用 this.$refs.引用名称

console.log(this.$refs.myP)

3.也可使用 ref 为组件添加引用;可以使用 this.$refs.组件名称,拿到组件的引用,从而调用组件上的方法和获取组件 data 上的数据

this.$refs 获取 DOM

<template>
  <div>
    <!--  通过 ref 获取到的 DOM 元素的引用,就是一个元素的 DOM 对象  -->
    <h3 id="h3" @click="getContent" ref="myh3">123</h3>
  </div>
</template>

<script>
export default {
  name: "Home1",
  methods: {
    getContent() {
      // 不要在vue中操作 DOM
      // console.log(document.getElementById("h3").innerHTML);
      console.log(this.$refs.myh3)
    }
  }
}
</script>

ref 直接引用组件并调用组件的方法和数据 ★★★★★

可以使用 ref 属性直接调用子组件的方法属性!

实现父调用子的方法

<template>
  <div>
    <!--  通过 ref 获取到的 DOM 元素的引用,就是一个元素的 DOM 对象  -->
    <h3 id="h3" @click="getContent" ref="myh3">123</h3>
    <my-son ref="son"></my-son>
  </div>
</template>

<script>
import Son from "@/components/GetDocumnet/Son"

export default {
  name: "Home1",
  methods: {
    getContent() {
      // 不要在vue中操作 DOM
      // console.log(document.getElementById("h3").innerHTML);
      console.log(this.$refs.myh3)
      this.$refs.son.add()
    }
  },
  components: {
    "my-son": Son
  }
}
</script>

<template>
  <div>
    <h3>这是Son组件 </h3>
  </div>
</template>

<script>
export default {
  name: "Son",
  data() {
    return {
      sonMsg: 0
    }
  },
  methods: {
    add() {
      this.sonMsg++;
    }
  }
}
</script>

触发组件更新(原理)

Vue 是如何触发组件更新的?

Vue 是数据驱动的,数据改变的时候视图才会改变。

状态 data vs 属性 props

响应式更新

vue 在实例化的时候,会对 data 下的数据做一个 getter/setter 的转换。即,在操作数据的时候,都会经过一个代理层,而代理层是通过 getter/setter 操作数据的。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中(虚拟 DOM 中用到的数据)把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

例如,下面的代码就会触发组件重新渲染。

<template>
  <div id="app">
    <!-- DOM 里用到了 num,因此会触发 updated 方法 -->
    <div :data="num"></div>
    <button @click="cc">111</button>
    <!-- DOM 没有用到 c,因此不会触发 updated 方法 -->  
    <button @click="cc2">222</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  components: { HelloWorld },
  data() { return { num: 1 } },
  updated() { console.log("触发了更新"); },
  methods: {
    cc(){
      console.log("click");
      this.num = 2;
    },    
    cc2() {
      console.log("click");
      this.c = 2;
    }
  }
}
</script>

单文件组件的注册

定义全局组件

// 全局组件注册
import Vue from "./js/vue.js"
// 名称尽量小写,中间用-隔开
Vue.component("my-test", {
    template: `<div> 这是我定义的组件 </div>`
})
const vm = new Vue({
    el: '#app',
    data: { msg: 'hello ' }
})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h5></h5>
        <my-test></my-test>
    </div>
</body>

</html>

定义私有组件

const vm = new Vue({
    el: '#app2',
    data: {
        info: '000'
    },
    components: {
        // '组件名称':{/* 组件配置对象 */}
        'my-test2':{
            tenplate: `<div>这是私有组件my-test2</div>`
        }
    }
})

@ 的作用

组件化:从页面 UI 的角度分析,把页面中可复用的 UI 结构,抽离为单独的组件;实现 UI 的复用。

双向数据绑定

v-model 指令双向数据绑定,只要 vm 监听到 data 中任何一条数据的变化,都会重新执行 el 区域的所有指令。

<input v-mode="in_val">
[表单输入绑定 Vue.js (vuejs.org)](https://cn.vuejs.org/guide/essentials/forms.html#text)

虚拟DOM

Vue 使用的虚拟 DOM,使用树形结构组织标签之前的曾经关系。在进行 for 遍历的时候,会要求绑定一个 key,如下

<div>
    <li v-for="item in list" :key='item.somekey'>
        <span></span><br>
    </li>
</div>

有时会有人写成这种

<div>
    <li v-for="(item, index) in list" :key='index'>
        <span></span><br>
    </li>
</div>

如果会对 list 中的数据进行添加删除,意味着每个 li 的 index 可能会发生变化,需要频繁修改 for 中的产生的 DOM,降低性能。建议在只需展示数据(数据不会变动)的场景下用 index 作为 key,其他情况下不要使用值会变动的 key。

操作DOM

计算属性和监听器

computed

必须是响应式数据才行

下面的例子展示了计算属性的优点

<!-- 在模板中写逻辑 -->
<div id="example">
  
</div>

<!-- 使用计算属性 -->
<div id="example">
  <p>Original message: ""</p>
  <p>Computed reversed message: ""</p>
</div>
<script>
let vm = new Vue({
  el: '#example',
  data: { message: 'Hello' },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})
</script>

使用方法也可以达成上述目的,但是计算属性会对结果进行缓存,计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。如果没有发生改变不会重新计算,而方法每次都要进行计算。

计算属性 getter 和 setter

watch

监听数据是否发生变化(通过监听变化,来书写响应的逻辑,如密码长度检测)

<div id="watch-example">
	<input v-model="question">
</div>

<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
       // question 发生变化的话,就执行下列代码
       // some code
    }
  },
})
</script>

computed vs watch

生命周期

生命周期:实例的生命周期,就是一个阶段,从创建到运行,再到销毁的阶段。 生命周期函数:在实例的生命周期中,在特定阶段执行的一些特定的事件,这些事件,叫做生命周期函数;

生命周期函数 = 生命周期钩子 = 生命周期事件

生命周期函数

[生命周期钩子 Vue.js (vuejs.org)](https://cn.vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagram)

函数式组件

指令

常见指令

vue 中的指令,只有 `` 是用在内容节点中的,其它所有的指令,都是用在属性节点中的。

数据模板

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            msg: "hello",
            array: [1, 2, 3, 4, 5],
            elem: "<span style='color:red'>Hello</span>"
        },
        method: {
            show: function(){
                console.log("ok");
            }
        }
    });
</script>

v-text 基本不用

会把原来的内容清空。插值表达式只会把占位符处的数据进行解析替换。

<h3 v-text="msg">
    12313
</h3>
// 显示 hello。12313会被覆盖掉的。

v-text 中使用简单的语句

<h3 v-text="msg + 666">
    12313
</h3>
// 显示hello666

<h3 v-text="msg + 'abc' ">
</h3>
// 显示 hellabc    

v-text 不存在闪烁问题。

场景:向元素的内容区域中,渲染指定的文本。

v-html

<h3 v-html="elem">
    // 可以解析html标签
</h3>

v-bind:属性绑定;用的很频繁

为 html 属性节点动态绑定数据的,如:

<buttuon v-bind:title="mytitle">按钮</button>

应用场景:如果元素的属性值,需要动态地进行绑定,则需要使用 v-bind:指令

简写形式:

v-on:事件绑定

<div v-on:click="show">按钮</div> 绑定事件不传参

<div v-on:click="show('hello')">按钮</div> 绑定事件传参

<div @click="show('hello')">按钮</div> 简写

v-model:双向数据绑定

几种实现双向绑定的做法

csdn

v-bind

v-for

v-if /v-show

自定义指令

vue 可以自定义指令,但是不推荐使用。

[自定义指令 Vue.js (vuejs.org)](https://cn.vuejs.org/guide/reusability/custom-directives.html#object-literals)
// Vue.component('全局组件名称',{/* 指令的配置对象 */})
// Vue.directive("focus", { /* 指令的配置对象 */ }

自定义指令

总结:CSS 样式这类操作写在 bind 中,JS 这类操作写在 inserted 中。

Vue.directive("focus", {
    bind: function (el) {
        // el.focus();
        // doing something
        console.log(el)
    },
    inserted: function(el){
        el.focus()
    }
})

自定义指令传参

通过第二个占位符传递参数,获得参数的值通过 .value 获取

Vue.directive("focus", {
    bind: function (el, param) {
        // el.focus();
        // doing something
        el.style.color = param.value
    }
})

跨层级组件获取

后期补充

常规用法

定义使用过滤器:处理文本显示格式

了解实例生命周期和生命周期函数

使用 axios 发起 Ajax 请求

带数据交互的案例

Vue 常见的过渡动画(不重要)

过滤器

全局过滤器

全局过滤器代码示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
</head>

<body>
    <div id="app">
        传入的参数是 time,然后调用方法 dataFormat
        <h3></h3>
    </div>
</body>
<script>
    Vue.filter('dataFormat', function (originVal) {
        const date = new Date(originVal);
        let years = date.getFullYear()
        let month = date.getMonth() + 1
        let day = date.getDay()
        // 魔法字符串${}是占位符
        return `${years}-${month}-${day}`;
    });

    const vm = new Vue({
        el: '#app',
        data: {
            time: '2020-01-22 23:11:23'
        }
    });
</script>
</html>

Promise、async、await

Promise

概念:

ES6 中的新语法,Promise 是一个构造函数;每个 new 出来的 Promise 实例对象,都代表一个异步操作。

JS 解析引擎是单线程的;宿主环境(浏览器、Node 环境)是多线程的。

异步的任务会放到异步回调函数的队列中。当 js 把自己栈中的任务执行完后,才会执行异步回调函数队列中的任务。

作用

解决了回调地狱的问题;

Promise用法

异步代码回顾

/**
JS解析引擎是单线程的;宿主环境(浏览器、Node环境)是多线程的。

异步的任务会放到异步回调函数的队列中。当js把自己栈中的任务执行完后,才会执行异步回调函数队列中的任务。
*/

回调地狱代码示例:node.js

const fs = require('fs')

fs.readFile('./files/1.txt', 'utf-8', (err, dataStr1) => {
    if (err) return console.log(err.message);
    console.log(dataStr1);
    fs.readFile('./files/2.txt', 'utf-8', (err, dataStr1) => {
        if (err) return console.log(err.message);
        console.log(dataStr1);
        fs.readFile('./files/3.txt', 'utf-8', (err, dataStr1) => {
            if (err) return console.log(err.message);
            console.log(dataStr1);
        })
    })
})

Promise 不会减少代码量,但是可以解决回调地狱的问题。

创建形式上的异步操作

const p = new Promise()

创建具体的异步操作;只要 new 了就会立即执行!

// 只要new了,就会立即执行!
const p = new Promise(function(successCb,errorCb){
    // 定义具体的异步操作
})
// 定义成功和失败的回调
p.then(successCallback,errorCallback);

查看下 Promise 的原型链

prototype

const fs = require('fs')

//==================无效写法================
function getContentByPath(fPath) {
    // js主线程只负责new出这个Promise,具体的执行交给浏览器执行了
    const p = new Promise(function () {
        fs.readFile(fPath, 'utf-8', (err, dataStr1) => {
            if (err) return console.log(err.message);
            console.log(dataStr1);
            // return dataStr1; 所以这个返回值是无效的。
        })
    })
}
getContentByPath('./files/1.txt')
//==================无效写法================


//==================有效写法================
function getContentByPath2(fPath) {
    // js主线程只负责new出这个Promise,具体的执行交给浏览器执行了.回调函数从哪里来?
    const p = new Promise(function (successCallback, errorCallback) {
        fs.readFile(fPath, 'utf-8', (err, dataStr1) => {
            if (err) return errorCallback(err);
            successCallback(dataStr1)
        })
    });
    return p;
}

const r1 = getContentByPath2('./files/1.txt')
// 成功回调  失败回调
r1.then(function (info) { console.log(info); console.log("success"); }, function (err) { console.log(err); });
//==================有效写法================

实际我们不会自己封装 Promise,会使用其他人封装的方法。

async和await

ES7 中 async 和 await 可以简化 Promise 调用,提高 Promise 代码的阅读性和理解性。

function getContentByPath(fpath){
    return new Promise(function(successCb,errorCb){
        fs.readFile(fpath, 'utf-8',(err,data)=>{
            if(err) return errorCb(err)
            successCb(data)
        })
    })
}

const data = await getContentByPath("./fs.txt")

// 如果一个方法内部用了await那么这个方法必须修饰为async
async function test(){
	const data = await getContentByPath("./fs.txt")
}

axios

之前发起请求的方式

axios的使用

get请求

使用 axios 发起 get 请求

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
    <script src="js/axios.js"></script>
</head>

<body>
    <div id="app">
        <button @click='getInfo'>GET</button>
    </div>
    <script>
        const vm = new Vue({
            el: '#app',
            methods: {
                getInfo() {
                    const result = axios.get('http://www.liulongbin.top:3005/api/get', { params: { name: 'zs', age: 20 } });
                    result.then(function (res) {
                        console.log(res);
                    })
                }
            }
        });
    </script>
</body>
</html>

结合 async await

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
    <script src="js/axios.js"></script>
</head>

<body>
    <div id="app">
        <button @click='getInfo'>GET</button>
    </div>
    <script>
        const vm = new Vue({
            el: '#app',
            methods: {
                async getInfo() {
                    const result = await axios.get('http://www.liulongbin.top:3005/api/get', {
                        params: {
                            name: 'zs',
                            age: 20
                        }
                    });
                    console.log(result);
                }
            }
        });
    </script>
</body>
</html>

解构赋值

const user = {
    name: 'zs',
    age: 20,
    gender: 'man'
}

// 把name属性解放出来,当作常量去使用。
// const { name } = user
// console.log(name);

// 给age取别名:userage
const { name, age: userage } = user
console.log(name, userage);

这样我们获取数据的时候,就可以用解构赋值,只得到我们想要的那部分数据了!

async function getInfo() {
    const {data:retVal} = await axios.get('http://www.liulongbin.top:3005/api/get', {
        params: {
            name: 'zs',
            age: 20
        }
    });
    console.log(result);
}

post请求

async postInfo() {
    const { data: retVal } = await axios.post('http://www.liulongbin.top:3005/api/post', { name: 'ls', gender: 'man' })
    console.log(retVal.data);
}

Vue推荐用法

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
    <script src="js/axios.js"></script>
</head>

<body>
    <div id="app">
        <button @click='getInfo'>GET</button>
        <button @click='postInfo'>POST</button>
    </div>
    <script>
        // 通过这个属性,全局设置 请求的 根路径。
        axios.defaults.baseURL = 'http://www.liulongbin.top:3005'
        Vue.prototype.$http = axios;
        const vm = new Vue({
            el: '#app',
            methods: {
                async getInfo() {
                    // 请求数据的时候会。 baseURL + 路径 = 'http://www.liulongbin.top:3005' + '/api/get'
                    const { data: retVal } = await this.$http.get('/api/get', {
                        params: {
                            name: 'zs',
                            age: 20
                        }
                    });
                    console.log(retVal.data);
                },
                async postInfo() {
                    const { data: retVal } = await this.$http.post('/api/post', { name: 'ls', gender: 'man' })
                    console.log(retVal.data);
                }
            }
        });
    </script>
</body>
</html>

axois的传参

this.$http.get('/user/10',{params:{name:'zs',age:22}}) // ===> http://127.0.0.1:8080/user/10?name=zs&age=2

案例

带有数据库的品牌管理案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
    <script src="js/axios.js"></script>
    <link rel="stylesheet" href="css/bootstrap.css">
</head>

<body>
    <div id="app">

        <div class="panel panel-primary inline">
            <div class="panel-heading inline">
                <h3 class="panel-title">添加新品牌</h3>
            </div>

            <div class="panel-body form-inline">
                <div class="input-group">
                    <div class="input-group-addon">品牌名称</div>
                    <input type="text" class="form-control" v-model='name'>
                </div>

                <div class="input-group">
                    <button class="btn btn-primary" @click='add'>添加</button>
                </div>

                <div class="input-group">
                    <div class="input-group-addon">按名称搜索</div>
                    <input type="text" class="form-control" v-model='keywords'>
                </div>
            </div>
        </div>

        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>name</th>
                    <th>time</th>
                    <th>operate</th>
                </tr>
            </thead>
            <tbody>
                <!-- 很巧妙啊 in search() search用来过滤 -->
                <tr v-for="item in search()" :key='item.id'>
                    <td></td>
                    <td></td>
                    <td></td>
                    <td><a href="#" @click.prevent="remove(item.id)">删除</a></td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script>
    axios.defaults.baseURL = 'http://liulongbin.top:3005';
    Vue.prototype.$http = axios;

    // 定义全局过滤器
    Vue.filter('dataFormat', function (originVal) {
        const dt = new Date(originVal);
        const y = dt.getFullYear();
        const m = (dt.getMonth() + 1 + '').padStart(2, '0');
        const d = (dt.getDay() + '').padStart(2, '0');
        return `${y}-${m}-${d}`
    })

    const vm = new Vue({
        el: '#app',
        data: {
            brandList: [],
            name: '',
            keywords: ''
        },
        created() {
            //在created中发起首屏数据的请求
            this.getBandList()
        },
        methods: {
            async getBandList() {
                const { data: res } = await this.$http.get('/api/getprodlist');
                // console.log(res);
                // return res.message; 返回的是一个promise对象。
                // 应该这么写
                this.brandList = res.message;
            },
            
            async add() {
                const { data: res } = await this.$http.post('/api/addproduct', { name: this.name });
                if (res.status !== 0) return alert('添加失败!');
                this.getBandList();
                this.name = '';
            },
            
            search() {
                return this.brandList.filter(item=>item.name.includes(this.keywords))
            },
            
            async remove(id) {
                const { data: res } = await this.$http.get('/api/delproduct/' + id);
                if (res.status !== 0) return alert('删除失败');
                else this.getBandList();
            }
        }
    });
</script>

</html>

Demo

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
</head>
<style>
    /* 定义入场之前和离场之后的样式 */
    .v-enter,
    .v-leave-to {
        transform: translateX(150px);
    }

    /* 定义入场阶段和离场阶段的样式 */
    .v-enter-active,
    .v-leave-active {
        transition: all 0.8s ease;
    }
</style>

<body>
    <div id="app">
        <button @click='flag=!flag'>toggle</button>
        <!-- 1.使用vue提供的transition标签 包裹需要添加动画的元素 name默认以v为前缀。 -->
        <transition name='v'>
            <h3 v-if='flag'>asfaf</h3>
        </transition>
    </div>
    <script>
        const vm = new Vue({
            el: '#app',
            data: { flag: true },
            methods: { }
        })
    </script>
</body>

</html>

三方动画库

Vue 不支持 animate4.0

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/animate.min.css">
    <script src="js/vue.js"></script>
</head>

<body>
    <div id="app">
        <button @click='flag=!flag'>toggle</button>
        <!-- 1.使用vue提供的transition标签 包裹需要添加动画的元素 -->
        <transition enter-active-class="bounceInDown" leave-active-class="bounceOutDown">
            <h3 v-show='flag' class="animated">aasffasfsasfasfsfaf</h3>
        </transition>
    </div>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                flag: true
            }
        })
    </script>
</body>

</html>

v-for的列表过渡

.v-enter,
.v-leave-to{
    opacity:0,
    transform:translateY(100px);
}

.v-enter-active,
.v-leave-active{
    transition:all 0.8s ease;
}

具体 Demo

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/animate.min.css">
    <script src="js/vue.js"></script>
</head>

<body>
    <div id="app">
        <input v-model="name"> <button @click="add">添加</button>
        <!-- 默认会用span 包裹 li。我们指定tag的话,就会用我们指定的tag包裹 -->
        <transition-group tag='ul' enter-active-class="bounceInDown" leave-active-class="bounceOutDown">
            <li v-for="item in list" :key="item.id" @click="del(item.id)" class="animated"></li>
        </transition-group>
    </div>

</body>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            list: [
                { "id": 1, "name": 'd1' },
                { "id": 2, "name": 'd2' },
                { "id": 3, "name": 'd3' }
            ],
            newId: 4,
            name: "123"
        },
        methods: {
            add() {
                const newInfo = { "id": this.newId++, "name": this.name }
                console.log(name);
                this.list.push(newInfo);
                this.name = ''
            },
            // 有问题,不过没事,就了解一下。
            del(id) {
                const i = this.list.findIndex(item=>item.id===id);
                this.list.splice(i, 1);
            }
        },
    })
</script>

</html>

SPA

component组件

通过 component 的 is 属性,动态指定要渲染的组件。

<template>
  <div id="app">
    <h1>App 根组件</h1>
    <!--  注意 是字符串 'GG' 按字符串名称来搜索的!  -->
    <component :is="'GG'"></component>
  </div>
</template>

<script>
// import Parent from "@/components/Father2Son/Parent";
import GG from '@/components/Son2Son/GG'
import DD from '@/components/Son2Son/DD'
import Vue from "vue";

Vue.component('GG', GG)
Vue.component('DD', DD)
export default {}
</script>

<template>
  <div id="app">
    <h1>App 根组件</h1>
    <button @click="comName='GG'">GG</button> &nbsp;&nbsp;&nbsp;
    <button @click="comName='DD'">DD</button>
    <!--  注意 是字符串 'GG' 按字符串名称来搜索的!  -->
    <component :is="comName"></component>
  </div>
</template>

<script>
// import Parent from "@/components/Father2Son/Parent";
import GG from '@/components/Son2Son/GG'
import DD from '@/components/Son2Son/DD'
import Vue from "vue";

Vue.component('GG', GG)
Vue.component('DD', DD)
export default {
  data() {
    return {
      comName: 'GG'
    }
  },
  methods: {}
}
</script>

锚链接及常规url的区别

1.普通的 URL 地址:会刷新整个页面;会追加浏览历史记录;

2.锚链接:不会触发页面的整体刷新;会追加浏览历史记录;(锚链接时页面内的跳转)

SPA相关概念

原始实现SPA

使用 component 标签的 :is 属性来切换组件

总结:单页面应用程序中,实现组件切换的根本技术点,就是==监听 window.onhashchange 事件==;

路由

路由常用属性

import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'


Vue.use(VueRouter)
// 创建路由规则
const router = new VueRouter({
    // 路由规则的数组
    routes: [
        // 每一个路由规则,都是一个对象,这个对象中,必须有 path 属性和 component 属性
        // 其中path 是 hash 地址,component 是前面 hash 地址对应要展示的组件。
        {path: '/home', component: Home},
        {path: '/about', component: About},
        // 在某个路由规则中嵌套子路由规则? path.component有个同级属性 children属性
        {
            path: '/movie',
            component: Movie,
            children: [{path: '/movie/tab1', component: tab1}, {path: '/movie/tab2', component: tab2}]
        },
        {path: '/', component: About},

    ],
    linkActiveClass: 'my-active'
})

什么是路由

路由就是对应关系;

  1. 后端路由的定义:URL 地址到后端处理函数之间的关系
  2. 前端路由的定义:hash 到组件之间的对应关系
  3. 前端路由的目的:为了实现单页面应用程序的开发
  4. 前端路由的三个组成部分
    1. 链接
    2. 组件
    3. 链接 和 组件之间的对应关系

Vue中使用 vue-router ★

安装导入并注册路由模块

创建路由链接

<!--  router-link 就是 第一步,创建路由的 hash 链接的  -->
<!--  to 属性,表示点击此链接,要跳转到哪个 hash 地址,注意:to 属性中,大家不需要以 # 开头  -->
<router-link to="/home">首页</router-link>
<router-link to="/move">电影</router-link>

创建并在 main.js 中导入路由相关组件

import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import Home from '@/components/router/Home'
import About from '@/components/router/About'
import Movie from '@/components/router/Movie'

Vue.use(VueRouter)
// 创建路由规则
const router = new VueRouter({
    // 路由规则的数组
    routes: [
        // 每一个路由规则,都是一个对象,这个对象中,必须有 path 属性和 component 属性
        // 其中path 是 hash 地址,component 是前面 hash 地址对应要展示的组件。
        { path: '/home', component: Home },
        { path: '/about', component: About },
        { path: '/movie', component: Movie },
        { path: '/', component: About },
    ]
})

Vue.config.productionTip = false
new Vue({
    render: h => h(App),
    // 指定路由规则对象
    router: router
}).$mount('#app')

创建路由规则

// 创建路由规则
const router = new VueRouter({
    // 路由规则的数组
    routes: [
        // 每一个路由规则,都是一个对象,这个对象中,必须有 path 属性和 component 属性
        // 其中path 是 hash 地址,component 是前面 hash 地址对应要展示的组件。
        {path: '/home', component: Home},
        {path: '/about', component: About},
        {path: '/movie', component: Movie},
        {path: '/', component: About},
    ]
})

在页面上放路由容器

<!-- 这是路由容器,将来通过路由规则,匹配到的组件,都会被展示到这个 容器中 -->
<router-view></router-view>

<template>
  <div id="app">
    <h1>App 根组件</h1>
    <hr>
    <router-link to="/home">首页</router-link>
    <router-link to="/about">关于</router-link>
    <router-link to="/movie">电影</router-link>
    <!--  路由容器组件,路由匹配到的组件 会被替换到 router-view里显示  -->
    <router-view></router-view>
  </div>
</template>

路由高亮

<style scoped>
.router-link-active {
  color: red;
  font-weight: bold;
}
</style>
const router = new VueRouter({
    // 路由规则的数组
    routes: [
        // 每一个路由规则,都是一个对象,这个对象中,必须有 path 属性和 component 属性
        // 其中path 是 hash 地址,component 是前面 hash 地址对应要展示的组件。
        {path: '/home', component: Home},
        {path: '/about', component: About},
        {path: '/movie', component: Movie},
        {path: '/', component: About},
    ],
	// 用到的 UI组件库中提供了默认的高亮效果,用这个
    linkActiveClass: 'my-active'
})

嵌套路由

App.vue有 <router-link to="/movie">电影</router-link><router-view></router-view>

App.vue 下的 Move.vue 也有,那么路由的写法如下:

import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import Home from '@/components/router/Home'
import About from '@/components/router/About'
import Movie from '@/components/router/Movie'
import tab1 from '@/components/router/tab/Tab1'
import tab2 from '@/components/router/tab/Tab2'

Vue.use(VueRouter)
// 创建路由规则
const router = new VueRouter({
    // 路由规则的数组
    routes: [
        // 每一个路由规则,都是一个对象,这个对象中,必须有 path 属性和 component 属性
        // 其中path 是 hash 地址,component 是前面 hash 地址对应要展示的组件。
        {path: '/home', component: Home},
        {path: '/about', component: About},
        // 在某个路由规则中嵌套子路由规则? path.component有个同级属性 children属性
        {
            path: '/movie',
            component: Movie,
            children: [{path: '/movie/tab1', component: tab1}, {path: '/movie/tab2', component: tab2}]
        },
        {path: '/', component: About},

    ],
    linkActiveClass: 'my-active'
})

Vue.config.productionTip = false
new Vue({
    render: h => h(App),
    // 指定路由规则对象
    router: router
}).$mount('#app')

redirect 重定向

在路由规则中,通过 redirect 属性,指向一个新地址,就能够实现路由的重定向

// 创建路由规则
const router = new VueRouter({

    routes: [
        // 重定向,实现根地址的默认选择
        { path: '/', redirect: '/home' },
        { path: '/home', component: Home },
        { path: '/about', component: About },
        {
            path: '/movie',
            component: Movie,
            redirect: '/move/tab1'
            children: [ { path: '/movie/tab1', component: tab1 }, { path: '/movie/tab2', component: tab2 } ]
        },
    ],
    linkActiveClass: 'my-active'
})

路由传参

在路由后面加上冒号实现路由传参。

==当 router-link 的 to 地址,要动态进行拼接的时候,一定要把 to 设置呈属性绑定的形式==

<template>
  <div> <ul> <router-link tag="li" v-for="item in mlist" :key="item.id" :to="'/mdetail/' +item.id"> </router-link> </ul> </div>
</template>

<script>
export default {
  name: "MoveList",
  data() {
    return {
      mlist: [
        { id: 1, name: '雷神' },
        { id: 2, name: '死侍' },
        { id: 3, name: '钢铁侠' },
      ]
    }
  }
}
</script>
<style scoped>
li {
  cursor: pointer;
}
</style>

import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import MoveList from "@/components/router/MoveList";
import MoveDetail from "@/components/router/MoveDetail";

Vue.use(VueRouter)

const router = new VueRouter({
    routes: [
        { path: '/', component: MoveList },
        // 把路由规则中,参数项位置,前面加上 : 表示这是一个参数项
        { path: '/mdetail/:id', component: MoveDetail },
    ]
})
Vue.config.productionTip = false
new Vue({
    render: h => h(App),
    // 指定路由规则对象
    router: router
}).$mount('#app')

模板字符串传递参数

<router-link tag="li" v-for="item in mlist" :key="item.id" :to='`/mdetail/${item.id}/${item.name}`'>  </router-link>
const router = new VueRouter({
    routes: [
        { path: '/', component: MoveList },
        { path: '/mdetail/:id', component: MoveDetail },
        { path: '/mdetail/:id/:name', component: MoveDetail },
    ]
})

获得路由参数

思路

路由规则中开启路由传参数 ==props:true==

页面设置 props 属性接收数据 ==props:[‘id’,’name’]==

props:[] 外界传递过来的数据,数据都是只读的。

代码

import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import MoveList from "@/components/router/MoveList";
import MoveDetail from "@/components/router/MoveDetail";

Vue.use(VueRouter)

const router = new VueRouter({
    routes: [
        { path: '/', component: MoveList },
        // props true 表示,为当前路由规则,开启 props 传参
        { path: '/mdetail/:id/:name', component: MoveDetail, props: true },
    ]
})
Vue.config.productionTip = false
new Vue({
    render: h => h(App),
    // 指定路由规则对象
    router: router
}).$mount('#app')

获得参数

<template>
  <div>
    电影详情
    <h4>==</h4>
  </div>
</template>

<script>
export default {
  name: "MoveDetail",
  // 接收 路由传递过来的参数
  props: ['id', 'name']
}
</script>

其它方式:不推荐使用!

直接使用 this.$route.params 来获取参数;写起来太麻烦,不推荐。

命名路由

什么是命名路由:就是为路由规则,添加了一个 name。

思路

为路由添加一个 name 属性,如name:'movedetail'

在 router-link 添加 :to="{name:'movedetail',params:{id:item.id,name:item.name}}"

代码示例

<router-link tag="li" v-for="item in mlist" :key="item.id" :to="{name:'movedetail',params:{id:item.id,name:item.name}}"></router-link>

import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import MoveList from "@/components/router/MoveList";
import MoveDetail from "@/components/router/MoveDetail";

Vue.use(VueRouter)

const router = new VueRouter({
    routes: [
        { path: '/', component: MoveList },
        // props true 表示,为当前路由规则,开启 props 传参
        { path: '/mdetail/:id/:name', component: MoveDetail, props: true,name:'movedetail' },
    ]
})
Vue.config.productionTip = false
new Vue({
    render: h => h(App),
    // 指定路由规则对象
    router: router
}).$mount('#app')

编程式(JS)导航

概念普及

之前所学的 router-link 是标签跳转

除了使用 router-link 是标签跳转之外,还可以使用 JavaScript 来实现路由的跳转


什么是编程式导航:使用 vue-router 提供的 JS API 实现路由跳转的方式,叫做编程式导航;

编程式导航的用法:


this.$route 路由参数对象

this.$router 是路由导航对象

vm 实例上的 router 属性,是来挂载路由对象的

在 new VueRouter({/* 配置对象 */}) 的时候,配置对象中,有一个 routes 属性,是来创建路由规则的。

跳转路由


思路

为标签绑定点击事件:@click="getDetail"

点击事件中使用:this.$router.push(`/mdetail/${item.id}/${item.name}`)js /mdetail 是路由地址,后面的是传过去的参数

参数接收的方式 还是通过 props

代码

<template>
  <div> <li tag="li" v-for="item in mlist" :key="item.id" @click="getData(item)"> </li> </div>
</template>

<script>
export default {
  name: "JSDaoHan",
  data() {
    return {
      mlist:
          [ {id: 1, name: '雷神'},
            {id: 2, name: '死侍'},
            {id: 3, name: '钢铁侠'},]
    }
  },
  methods: {
    getData(item) {
      this.$router.push(`/mdetail/${item.id}/${item.name}`)
    }
  }
}
</script>

const router = new VueRouter({
    routes: [
        { path: '/', component: JSDaoHan },
        // props true 表示,为当前路由规则,开启 props 传参
        { path: '/mdetail/:id/:name', component: MoveDetail, props: true },
    ]
})

路由后退

路由导航守卫

介绍

检测用户有无权限!提供了一层拦截!

案例需求:只允许登录的情况下访问 后台首页,如果不登录,默认跳转回登录页面;

API 语法

const router = new VueRouter({
    routes: [
        { path: '/', component: JSDaoHan },
        { path: '/mdetail/:id/:name', component: MoveDetail, props: true },
    ]
})
// 在访问这个路由对象,每一个路由规则之前,都需要先调用 指定的回调函数,如果回调函数放行了,就看得到想看的组件,反之,就无法看到。
// to: 是要去的哪个页面路由相关的参数
// from: 从哪个页面即将离开
// next: 一个函数,相对于 node 里面 express 中的 next 函数
router.beforeEach( (to, from, next)=>{ /* 导航守卫 处理逻辑 */ } )

实现登录拦截

路由代码

import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import Login from "@/components/routerShouWei/Login";
import Home from "@/components/routerShouWei/Home";

Vue.use(VueRouter)

const router = new VueRouter({
    routes: [
        { path: '/', redirect: '/login' },
        { path: '/login', component: Login },
        { path: '/home', component: Home },
    ]
})
router.beforeEach((to, from, next) => {
    // to.path 表示我们下一刻要访问哪个地址
    // from.path 表示我们上一刻,所访问的是哪个地址
    // 如果访问 /login 说明要登录,没必要拦截
    if (to.path === '/login') return next()
    // 拿到 token 看用户是否登录
    const token = window.sessionStorage.getItem('user')
    // 未登录则跳转到登录页面
    if (!token) return next('/login')
    // 登录了则放行
    next()
})
Vue.config.productionTip = false
new Vue({
    render: h => h(App),
    // 指定路由规则对象
    router: router
}).$mount('#app')

登录页面

<template>
  <div>
    <p>姓名:<input type="text" v-model="name"></p>
    <p>密码:<input type="text" v-model="password"></p>
    <button @click="login">登录</button>
  </div>
</template>

<script>
export default {
  name: "Login",
  data() {
    return {
      'name': '',
      'password': ''
    }
  },
  methods: {
    login() {
      if (this.name == "123" && this.password == "123") {
        // 登录成功保存token
        // eslint-disable-next-line no-unused-vars
        const token = "sfasfjaskfaskfhaasjkfhasjkfhaskfasfs";
        window.sessionStorage.setItem("user", token)
        this.$router.push("/home")
      } else {
        alert("用户名 或 密码错误")
      }
    }
  }
}
</script>

登录后的页面

<template>
  <div> <h3>后台主页,不等于不允许访问!</h3> </div>
</template>

<script>
export default {
  name: "Home"
}
</script>

案例

数据列表组件

<template>
  <div>
    <h1>品牌列表案例</h1>
    <el-button type="primary" @click="addDialogShow">添加新品牌</el-button>
    <!--  品牌列表数据  -->
    <el-table :data="brandList" border stripe style="width:100%">
      <el-table-column type="index" label="索引" width="100%"></el-table-column>
      <el-table-column prop="id" label="编号"></el-table-column>
      <el-table-column prop="name" label="品牌名称"></el-table-column>
      <el-table-column prop="ctime" label="创建时间">
        <template slot-scope="scope">
          
        </template>
      </el-table-column>

      <el-table-column label="操作">
        <template slot-scope="scope">
          <!--    如果在 表格的 column 渲染数据,必须使用 作用域插槽才行    -->
          <el-button type="primary" :search="scope.row.id">查询</el-button>
          <el-button type="success" :search="scope.row.id">修改</el-button>
          <el-button @click="deleteData(scope.row.id)" type="danger" :search="scope.row.id">删除</el-button>
        </template>
      </el-table-column>

    </el-table>

    <!--  添加新品牌的对话框  -->
    <el-dialog title="添加品牌" :visible.sync="add" width="50%">
      <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px">
        <el-form-item label="品牌名称" prop="name">
          <el-input v-model="addForm.name" v-focus></el-input>
        </el-form-item>
      </el-form>

      <span slot="footer" class="dialog-footer">
        <el-button @click="add = false">取 消</el-button>
        <el-button type="primary" @click="addNewBrand">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "BrandList",
  data() {
    return {
      // 品牌列表数据
      brandList: [
        {id: 1, name: '123', ctime: '2020-11-11'},
        {id: 2, name: '1234', ctime: '2020-11-5'}

      ],
      add: false,
      addForm: {
        name: '',
        ctime: new Date()
      },
      addFormRules: {
        name: [
          {required: true, message: '请输入活动名称', trigger: 'blur'},
          {min: 2, max: 55, message: '长度在 2 到 55 个字符', trigger: 'blur'}
        ]
      }
    }
  },

  methods: {
    async getBrandList() {
      const {data: res} = await this.$http.get("/api/getprodlist")
      if (res.status != 0) return alert("数据获取失败")
      // 数据获取成功
      this.brandList = res.message
    },
    addDialogShow() {
      this.add = true
    },
    addDialogClosed() {
      this.$refs.addFormRef.resetFields()
    },
    addNewBrand() {
      this.$refs.addFormRef.validate(async valid => {
        if (!valid) return
        const {data: res} = await this.$http.post('/api/addproduct', {name: this.addForm.name})
        if (res.status !== 0) return this.$message.error("添加失败!")
        this.$message.success("添加成功!")
        this.add = false
        this.getBrandList()
      })
    },
    // 删除
    async deleteData(id) {
      const data = await this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).catch(err => err)
      if (data !== 'confirm') return this.$message.error("取消删除")
      const {data: res} = await this.$http.get("/api/delproduct/" + id)
      if (res.status !== 0) return this.$message.error("删除失败")
      this.$message.success("删除成功")
      this.getBrandList()
    }
  },

  created() {
    this.getBrandList();
  }
}
</script>

<style scoped>
.el-button {
  margin-bottom: 10px;
}
</style>

根组件数据展示

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: 'App'
}
</script>

路由配置

import Vue from 'vue'
import App from './App.vue'
import Router from "vue-router"
import BrandList from "./components/BrandList";
import axios from "axios"
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';


Vue.use(ElementUI);

axios.defaults.baseURL = "http://www.liulongbin.top:3005"
Vue.prototype.$http = axios
Vue.use(Router)

const routes = new Router({
    routes: [
        {path: '/', component: BrandList}
    ],
    mode: 'hash'
})

Vue.config.productionTip = false


// 定义全局过滤器
Vue.filter('dataFormat', (originVal) => {
    const dt = new Date(originVal)

    const y = dt.getFullYear();
    const m = (dt.getMonth() + 1 + '').padStart(2, '0');
    const d = (dt.getDate() + '').padStart(2, '0');
    return `${y}-${m}-${d}`
})

// 定义全局聚焦指令
Vue.directive('focus', {
    // 当被绑定的元素插入到 DOM 中时……
    inserted: function (el) {
        // 聚焦元素
        console.log(el);
        el.children[0].focus()
    }
})

new Vue({
    render: h => h(App),
    router: routes
}).$mount('#app')

app打包需要添加这个 vue.config.js

防止打包后的页面一片空白。

vue的路由模式需要改为 mode: 'hash',保证路由可正确跳转。

module.exports = {
    assetsDir: 'static',
    parallel: false,
    publicPath: './',
}

执行打包命令 npm run build 打包到了 dist 文件下,用 HBuilder 创建一个 H5+APP 的项目,把 dist 中的内容拷贝过去,然后打包为 app 。

项目中的问题

骨架屏

Vue页面骨架屏 - SegmentFault 思否 可以用 vv-ui

图片过大,上传过慢

Vue+Vant 压缩图片,提高上传速度。

<van-uploader :max-size="4 * 1024 * 1024" capture="camera" class="uploader" accept="image/*"
              :after-read="afterRead">
    <van-swipe-item>
        <van-button plain type="info" icon="plus" class="re-btn">
            拍照识别
        </van-button>
    </van-swipe-item>
</van-uploader>
 // base64 转 file
    dataURLtoFile(dataurl, filename) {
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, {type: mime});
    },
    afterRead(file) {
      console.log(file)
      // 图片大于500kb就压缩
      if (file.file.size > 512000) {
        let canvas = document.createElement('canvas') // 创建Canvas对象(画布)
        let context = canvas.getContext('2d')
        let img = new Image()
        img.src = file.content // 指定图片的DataURL(图片的base64编码数据)
        let files = file;
        img.onload = () => {
          let size = files.file.size / 512000
          canvas.width = img.naturalWidth / Math.sqrt(size)
          canvas.height = img.naturalHeight / Math.sqrt(size)
          context.drawImage(img, 0, 0, canvas.width, canvas.height)
          files.content = canvas.toDataURL(files.file.type, 0.92) // 0.92为默认压缩质量
          let myFile = this.dataURLtoFile(files.content, files.file.name)//dataURLtoFile为自己封装的函数,将base64转为file
          console.log(files)
          let formDatas = new FormData()
          formDatas.append('file', myFile)
          this.upload(formDatas)//上传的封装函数
        }
      } else { //小于10M直接上传
        let formData = new FormData()
        formData.append('file', file.file)
        console.log(formData)
        this.upload(formData)//上传的封装函数
      }
    },
    async upload(formData) {
      const {data: response} = await this.$http.post('/upload', formData)
      if (response.code == 200) {
        Toast.success(response.msg);
        window.sessionStorage.setItem("classify_result", JSON.stringify(response))
        window.sessionStorage.getItem("classify_result")// 此处获得 图片回显的 url 地址。
        this.$router.push("/result/classify")
      } else {
        Toast.fail(response.msg);
      }
    },

axios 等网络请求出现异常

try{
    // 处理异常,才发现 js 有 try catch
}catch(error){ 
}
// =====================
async upload(formData) {
    try {
        const {data: response} = await this.$http.post('/upload', formData)
        if (response.code == 200) {
            Toast.success(response.msg);
            window.sessionStorage.setItem("classify_result", JSON.stringify(response))
            window.sessionStorage.getItem("classify_result")// 此处获得 图片回显的 url 地址。
            this.$router.push("/result/classify")
        } else {
            Toast.fail(response.msg);
        }
    } catch (error) {
        Toast("请求未响应,请检查网络是否正常!");
    }
}

跨域问题

后端已经允许跨域了,也响应成功了,但是前端无法接受到数据,提示跨域错误。(为什么?好像是前端接受数据的时候发现后端传过来的数据和自己不是同源,所以依旧是跨域错误,取消凭证即可)

axios.defaults.crossDomain = false
axios.defaults.withCredentials = false // 不用凭证
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'