Vue阶段总结

Vue总结

vue的简介

Vue的特点和Web开发中常见的高级功能:

​ 1、解耦视图和数据

​ 2、双向数据绑定

​ 3、可复用的组件

​ 4、前端路由技术

​ 5、状态管理

​ 6、虚拟DOM(js对象)

学习vue的注意点

注意:

学习Vue框架,必须严格遵循他的语法规则,Vue代码的书写思路和以前的JQuery完全不一样。所以,在项目中使用Vue之前,必须先学习Vue的基本语法规则。

Vue 安装使用

方式一:直接CDN引入

 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>

<script src="https://cdn.jsdelivr.net/npm/vue">script>

方式二:下载和引入

// 开发环境 https://vuejs.org/js/vue.js
// 生产环境 https://vuejs.org/js/vue.min.js

方式三:NPM安装(今天先不掌握)

​ 后续通过Vue-Cli4(脚手架)方式引入,我们使用该方式

创建Vue对象

  1. 先看js代码,会发现创建了一个Vue对象

  2. 创建Vue对象的时候,传入了一个对象:{}

    2.1 {}中的el属性:该属性决定了这个Vue对象挂载到哪一个元素上,很明显,我们这里挂载到了id为app的元素上。

    2.2 {}中包含了data属性:该属性中通常会存储一些数据,好像上面例子中的str1就是直接定义出来的数据



可以在里面填入正则表达式,变量,可以进行加减乘除等的运算或者字符串拼接

定义Vue对象基本格式

	var vm = new Vue({
        el: "要绑定的标签",
        data: {
            变量名1:值1,
            变量名2:值2,
            变量名3:值3,
            ...

        },
        methods: {
            方法名1: function(){

            },
            方法名2: function(){

            },
            方法名3: function(){

            }
        }
    });

Vue常见的语法格式(重点)

插值

{{ 变量key值 }}

v-bind控制标签属性

        百度
        淘宝
        
        可以使用简写  ‘:’

v-on事件格式

        
        
        
        
        
        简写方法 “@”
        
        如果使用函数,必须在创建的Vue对象里面,添加methods
       methods:{
          add:function(){
               this.num += 5
                  },
          add2(){
               this.num += 10
                }

vue控制类名

	
可以是多个的类名

style样式(动态控制类名)

        
文字
可以使用对象
文字
可以使用数组
文字

:style="{'background-image': 'url('+userInfoFn.headImg变量+')'}"
三元表达式: :class="['iconfont样式名',item.checked变量()? 'true走这边': 'false走这边']"

控制标签是否显示

v-show

v-show是对样式层面的控制

例如

新闻

在页面中的显示是

新闻

style="display: none

v-if

v-if是对dom节点的控制

例如: v-if指令的作用: 控制标签是否展示,不展示则删除

常与 v-eles 一起用(中间可以插入别的,但是一般识别距离最近的 v-if ; v-eles)




	bool1的值为true 第一个盒子被保留,删除第二个盒子,
	为false的话,第2个盒子保留,删除第1个盒子

两者的具体应用

v-show 一般用于频繁切换操作,否则可以使用v-if

列表的渲染(v-for)

语法:v-for="(item, index) in 需要遍历的数组数组"
			元素     索引

具体用法: <li v-for="i in list1">{{ i }}li>

还可以做数组的添加和删除
unshift   >   在前面新增
push      >   在后面新增
shift     >   在前面删除
pop       >   在后面删除

对象的渲染(v-for)

语法:v-for="(value, key) in 需要遍历的对象"
		value值       key值
		
具体的用法:
           
  • {{i}} {{j}}
  • 表单数据绑定(双向数据绑定)

    v-model的值是,vue接收表单元素的值的变量名, 即v1的值就是用户填写的内容
    (甚至可以在data中设置v1的值,从而设置表单元素的默认值)

    可以设置初始值,捕获改变后的值(最新的值)

    可以做tab栏的切换

    	<div id="app">
            <input type="text" v-model="txt1">  <!-- v-model表示了用户输入的这个数据-->
            <p>{{ txt1 }}</p>
    
            <select v-model="sel1">
                <option value="0">北京</option>
                <option value="1">上海</option>
                <option value="2">广州</option>
            </select>
        </div>
        <script>
            var vm = new Vue({
                el:"#app",
                data:{
                    //通过改变txt1或者sel1,来使对应的表单元素的v-model的值发生了变化,所以这个表单元素的value就变化了(v-model看成我们之前将的value)
                    txt1: '默认值',
                    sel1: 0
                }
            })
        </script>
    
    

    模板语法的插值操作(其他不常用指令)

    v-html 往标签内部插入html文本

                data:{
                    htmlText:"

    你好!世界!

    " }

    显示:你好!世界!

    v-text 往标签内部插入普通文本(解析不了标签)

    显示:

    你好!世界!

    v-pre 在界面上直接展示胡子语法

    {{htmlText}}

    显示:{{htmlText}}

    v-cloak 隐藏数据渲染到页面之前,胡子语法在界面上的展示

    <p v-cloak>{{htmlText}}p>
    
    搭配 style 隐藏 使用
        <style>
            [v-cloak]{
                display: none;
            }
        style>
        
    如果不使用隐藏,在加载完成之前,显示:{{htmlText}} 加载完成之后,显示:<p>你好!世界!p>
    
    与加载的网络有关
    

    数组方法

    常用方法使用

    var arr = [1, 2, 3]
    // 往数组最后一位添加一个数字
    arr.push(4) // [1, 2, 3, 4]
    // 删除数组最后一个数字
    arr.pop()   // [1, 2, 3]
    console.log(arr)
    // 往数组第一位添加一个数字
    arr.unshift(0)
    console.log(arr)
    // 删除数组第一个元素
    arr.shift()
    console.log(arr)
    // splice
    // 删除第一个元素
    arr.splice(1, 2) 
    console.log(arr)
    arr.splice(1, 2, 2, 4, 5) 
    console.log(arr)
    // 合并数组
    console.log([1, 6].concat([5, 7]))
    

    push(返回数组长度)、unshift(返回数组长度)、shift(返回删除的值)、pop(返回删除的值)、splice、concat(返回新数组)

    filter方法

    过滤

            let arr = [1, 2, 3, 4, 5, 6]
    
            // filter() 过滤  ,如果是true 则保留,如果是false 则过滤
            // 不会修改原数组
            let new1 = arr.filter((item, index) => {
                return item > 3
            })
            console.log(new1, arr);// [4, 5, 6]    [1, 2, 3, 4, 5, 6]
    

    map方法

            // map() 修改数组里面的每一个元素,并且新的数组和旧的数组长度是一样的
            // 不会改变原数组
            let new2 = arr.map((item, index) => {
                return item + 2
            })
            console.log(new2);//[3, 4, 5, 6, 7, 8]
    
            let new3 = arr.map((item, index) => {
                return {id:item+2}
            })
            console.log(new3);// [{…}, {…}, {…}, {…}, {…}, {…}]
    

    reduce方法

    利用reduce方法遍历数组的每一个元素,reduce()调用结果最后返回一个最终值(最后一次return值)。

        //数组名.reduce(回调函数,pre的初始值)
        arr.reduce(function(pre, current){
            // reduce这个方法被调用时,会遍历arr这个数组的每一个元素,每遍历一个元素,就执行一次这里的代码
            // current表示当前正在遍历的这个元素
            // pre 是上一次的这个函数return的值
            // !!!因为第一次遍历没有上一个return值,所以,交给了第二个参数,设置pre的初始值
            console.log(pre, current)
            return 10
        },0)
    
    	//!!!并且reduce方法最终会返回最后一次的return值
    
    
    每一次,都返回pre
    
            // reduce: 遍历数组,不会改变原数组
    
            /*
            特点:
                1、reduce 第二个参数如果没有,name第一次的prev取数组的第一个元素,回调函数是从数组的第二个元素开始
                    如果有第二个参数,name第一次的prev的值用的就是传入reduce的第二个参数,回调函数从数组的第一个元素开始遍历
                2、reduce里面的prev的值,第一次是由reduce第二个参数决定的,其他的情况下是上一次回调函数的返回值所决定的。
                    reduce 返回值就是最后一次回调函数的返回值。
                */
    
    
    用处:可以做计算,汇总……
    
    语法:
    array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
    
    (total  必填;初始值或者计算结束后的返回值(后面没有初始值,默认初始值为数组的第一个数 prev的第一次等于数组的第一个数)
    (currentValue  必填  当前元素)
    (currentIndex  可填  当前元素的索引)
    (arr  可选  当前元素所属的数组对象)
    
    (initialValue  可选。传递给函数的初始值 不填,默认是数组的第一个数,然后从第二数开始遍历)
    
    
    
    其余prev 都是undefined 因为没有返回 return
    

    数组去重

    	var arr2 = [1, 2, 3, 1, 6, 2, 3]
    
        //ES6
        consoloe.log([...new Set(arr2)])
        console.log(Array.from(new Set(arr2)))
    
        var newArray = [];
        for(var i=0; i

    Vue的计算属性computed 的使用

                computed:{
                    //computed里面的方法必须有返回值!这个return值将来在视图中被{{total}}引用
                    ***函数 无法传入参数
                    total(){
                        var a = this.arr.reduce(function(pre, current){
    
                            console.log(pre, current)
    
                            // var total = 当前这次的 price*count + 上一次的total
                            var total = current.price*current.count + pre
                            return total
                        },0)
                        return a
                    }
                }
    

    computed内部方法有缓存的作用

    函数调用了三遍,却只执行了1遍,这是computed内部方法的缓存起了作用

    computed内部方法的另一种写法(知道有get和 set的格式)

    		....
    		computed:{
                    //computed里面的方法必须有返回值!这个return值将来在视图中被{{total}}引用
                    total:{
                        get(){
                            console.log("total_get")
                            var a = this.arr.reduce(function(pre, current){
    
                                // var total = 当前这次的 price*count + 上一次的total
                                var total = current.price*current.count + pre
                                    return total
                                },0)
                                return a
                        },
                        set(){
                            console.log("total_set")
                        },
                    }
                }
    	...
        
    
    vm.total = 3   //触发调用set方法
    

    使用:value和@input代替v-model

    v-model 本质上包含了两个操作:

    1. v-bind 绑定input元素的value属性
    2. v-on 指令绑定input元素的input事件
    <input type="text" v-model="textVal"/>
    
    <input type="text" v-bind:value="textVal" v-on:input="textVal = $event.target.value"/>
    

    本地存储

    localStorage永久存储

    语法:

    // 添加数据;setItem的value值是字符串类型的数据
    先对数组或对象进行字符串转换------JSON.stringify(数组或对象);
    
    localStorage.setItem('name','张三')// 获取数据
    取值如果是对象,数组字符串的,那么要进行转换,JSON.parse
    	例子:
            let time = localStorage.getItem('time');
            console.log(JSON.parse(time));
    
    localStorage.getItem('name'); // 张三
    
    // 删除(time)某一项数据的值
    localStorage.removeItem('time')
    
    // 清空
    localStorage.clear();
    

    注意事项:

    1. 永久存储的,除非是主动删除,不然是不会自动删除的;删除浏览器不会被删除,删除只能调用clear方法
    2. 一般浏览器存储的大小是5M
    3. 存储的值必须是字符串(先对数组,对象进行字符串转换,JSON.stringify)

    5M = 1024 * 5kb

    sessionStorage临时会话存储

    语法:

    // 添加数据;setItem的value值是字符串类型的数据
    存储的值必须是字符串(先对数组,对象进行字符串转换,JSON.stringify)
    JSON.stringify(数组或对象);
    
    sessionStorage.setItem('name','张三')// 获取数据
    取值如果是对象,数组字符串的,那么要进行转换,JSON.parse
    	例子:
            let time = localStorage.getItem('time');
            console.log(JSON.parse(time));
    sessionStorage.getItem('name'); // 张三
    
    // 删除(time)某一项数据的值
    sessionStorage.removeItem('time')
    
    // 清空
    sessionStorage.clear();
    

    注意事项:

    1. 临时存储,关闭浏览器会自动清空数据
    2. 一般浏览器存储的大小是5M

    cookie

    网站中,http请求是无状态的。也就是第一次登陆成功(发送请求),第二次请求服务器依然不知道是哪一个用户。这时候的cookie就是解决这个问题的,第一次登陆后服务器返回数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求,浏览器自动会把上次请求存储的cookie数据自动带上给服务器,服务器根据客户端的cookie来判断当前是哪一个用户。cookie存储有大小限制,不同浏览器不一样,一般是4kb,所以cookie只能存储小量数据。

    token: 用户登录凭证,服务端返回给浏览器(前后端分离项目,基本都是发送ajax请求)

    session

    session和cookie的作用有点类似,也是存储用户相关信息。不同的是cookie存储在浏览器,而session存储在服务器。

    Vue过滤器

    语法:

       过滤器    filters: {
          函数        toFix2(price参数变量名) {
                        return price.toFixed(2)(toFixed 把数字转换为字符串,结果的小数点后有指定位数的数字)
    									(用法:toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。)
                  	  },
                    }
    

    Vue指令

    深入双向数据绑定原理

    vue双向数据绑定原理:

    借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定

    vue的双向数据绑定原理是什么?

    vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的。
    vue是通过Object.defineProperty()来实现数据劫持,其中会有getter()和setter方法;当读取属性值时,就会触发getter()方法,在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会来触发这个函数;

    语法:

    Object.defineProperty(obj, prop, desc)
    
    1. `obj` 需要定义属性的当前对象
    2. `prop` 当前需要定义的属性名
    3. `desc` 属性描述符
    

    数据属性:

    通过Object.defineProperty()为对象定义属性,有两种形式,分别为数据描述符,存取描述符,下面分别描述两者的区别:

    1. value 表示它的默认值
    2. writable 如果为true标识可以被修改,如果为false标识不能被修改(默认值为false)
    3. configurable 描述属性是否配置,以及可否删除,可以认为是总开关 默认值 false(不可删除)
    4. enumerable 描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)

    例子:

            var p = {};
            // defineProperty(对象, 属性名, {value: 值}) === p.name = '张三'
            Object.defineProperty(p, 'name', {
                value: '张三',
                writable: true, // writable: 设置属性(name)是否可以修改,默认值: false(不可修改),true(可以修改)
                configurable: true, // configurable: 控制属性是否可以删除,默认值:false(不可以删除), true(可以删除)
                enumerable: true, // enumerable: 控制属性是否可以被遍历,默认值:false(不可以遍历), true(可以遍历)
            })
            console.log(p.name);
            p.name = '李四'//不可以直接修改,要先设置属性
            console.log(p.name);
            // delete p.name;//不可以直接删除,要先设置属性
            // console.log(p);
            for (let key in p) {
                console.log(key)//不可以直接遍历,要先设置属性
            }
            
    
    
    另一种方法:
            let num = 0;
            // 定义p对象上age属性; get和set方法分别对age属性的获取和赋值进行拦截,get方法的返回值就是age属性对应的值
            Object.defineProperty(p, 'age', {
                // value: 20,
                get() {//监听读取操作
                获取数据的时候 本身是没有值的 值是有另外一个函数 return 出来的
                get就是在读取name属性这个值触发的函数
                    console.log('age上的get方法')
                    // document.getElementById()
                    return num;
                },
                set(val) {//监听写入操作
                改变数据的时候 进入的set 里面
                set就是在设置name属性这个值触发的函数
                    num += (val + 100)
                    console.log('age上的set方法', val)
                }
            })
            p.age = 30;
            console.log(p.age);
            
    

    自定义指令

    除了一些内置的制定(v-model和v-show…),Vue也允许注册自定义指令。

    全局自定义指令格式:

    // 注册一个全局自定义指令 v-demo
    Vue.directive('demo(指定名)', {
    	inserted: function (el, binding) {
    		
    		console.log(el, binding);
    	},
        update(el, binding){}
    })
    

    局部指令

    // 组件中注册局部指令
    new Vue({
    	el: '#app',
    	data: {},
    	directives: {
    		demo(指令名): {
    			bind(el, binding){
    			
    			},
    			inserted: function (el, binding) {
    				cosnole.log(el, binding);
    			},
    			update(el, binding){
    			
    			},
    		}
    	}
    })
    

    钩子函数:

    一个指令定义对象可以提供如下几个钩子函数 (均为可选):

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
    • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。

    参数:

    • el:指令所绑定的元素,可以用来直接操作 DOM 。
    • binding:一个对象,包含以下属性:
      • name:指令名,不包括 v- 前缀。
      • value:指令的绑定值,例如:v-demo="1 + 1" 中,绑定值为 2
      • oldValue:指令绑定的前一个值
      • expression:字符串形式的指令表达式。例如 v--demo="1 + 1" 中,表达式为 "1 + 1"
      • modifiers:一个包含修饰符的对象。例如:v-demo.foo.bar 中,修饰符对象为 { foo: true, bar: true }

    Vue组件化开发

    组件化的思想

    1. 如果我们实现一个页面结构和逻辑非常复杂的页面时,如果全部一起实现会变得非常复杂,而且也不利于后续的维护和迭代功能。
    2. 但如果我们这时候把页面分成一个个小的功能块,每个功能块能完成属于自己这部分独立的功能,那么整个页面之后的维护和迭代也会变得非常容易。

    组件化开发的优势:可维护性高 可复用性高

    组件化思想的应用开发:

    1. 有了组件化的思想,我们在之后的开发中就要充分的利用它。
    2. 尽可能的将页面拆分成一个个小的、可复用的组件。
    3. 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

    局部组件

    通过 Vue.component 方式注册的组件,称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

        
        
        
            new Vue({
                el: "#app",
                components: {
                    // key: 组件名称, value: 组件内容
                    aheader: {//任意命名
                    //template标签------实际上 元素是被当做一个不可见的包裹元素,主要用于分组的条件判断和列表渲染。
                    // 只能用这个标签包裹
                    template : '#tmp1',
                        // 组件内的data必须是一个函数
                        data() {
                            return {
                                num: 1
                            }
                        }
                    }
                }
            })
    

    全局组件

    通过Vue CLI 进行Vue全局组件

    1、下载安装包

    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
    

    2、检查版本,并确认是否安装成功

    vue --version
    

    3、如需升级全局的 Vue CLI 包,请运行:

    npm update -g @vue/cli
    
    # 或者
    yarn global upgrade --latest @vue/cli
    

    4、创建一个项目

    vue create 自定义文件名(英文)
    例如
    vue create hello-world
    

    5、下载所需要的包工具

    会自动识别文件package-lock 里面所需要的工具包

    npm i
    

    6、开发模式运行

    npm run serve
    

    生产模式运行

    npm run build
    

    7、删除项目

    del 文件名
    

    8、转换文件夹名

    cd 文件路径
    例如:
    cd ./文件名
    

    9、清空

    cls   清空
    

    父组件和子组件

    组件和组件之间存在层级关系,而其中一种最重要的关系就是父子组件关系。

    data属性必须是一个函数,而且函数返回一个对象 ,对象保存着数据

        data() {//函数
          return {//返回一个对象
            title: 'zujianbiaoti'
          }
        }
    

    为什么data在组件中必须是一个函数呢?

    原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

    父子组件间的通讯

    父级向子级传递

    在组件中,使用选项props来声明需要从父级接收到的数据。

    props的值有两种方式:

    1. 字符串数组,数组中的字符串就是传递时的名称。
    2. 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
    父级
    <名 :任意名 = 需要传入子级的数据对象名>>
    
    子级
      props: {
        需要传入子级的数据对象名(wrapperDate): {
          type: Object,
          default: {},//默认值
          required:false/true  //必填,一定要传值
        }
        
        
       三种写法,另外另种
       一、
       props: {
               num: Number,
               }
               
      二、
      props :['num']
    
     然后子级就可以直接调用
     {{wrapperDate.cancelTxt}}
    
    子级向父级传递

    子组件向父组件传递数据,通过自定义事件

    子级
    <div @click="函数名(submitFn)(参数)">div>
    
      methods: {
        cancelFn(参数) {
          this.$emit("canceler自定义事件名" 参数);
        },
      },
      
      父级
      Wrapper>
      
        methods:{
        fn(){
          this.clickResult = this.textObj.cancelTxt
        },
      },
    

    多层父子组件之间的通讯

    例如祖父和孙子之间或者更多层

    使用:Vue提供的更高阶方法:provide/inject

    这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

    provide:Object | () => Object
    inject:Array<string> | { [key: string]: string | Symbol | Object }
    
    provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。
    
    inject 选项应该是:
    
    一个字符串数组,或
    一个对象,对象的 key 是本地的绑定名,value 是:
    在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    一个对象,该对象的:
    from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
    default property 是降级情况下使用的 value
    提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
    
    具体用法:
    // 父级组件提供 'foo'
    var Provider = {
      provide: {
        foo: 'bar'
      },
      // ...
    }
    
    // 子组件注入 'foo'
    var Child = {
      inject: ['foo'],
      created () {
        console.log(this.foo) // => "bar"
      }
      // ...
    }
    
    
    

    兄弟组件间的传递

    方法一:bus

    EventBus中央事件总线

    1、新建一个bus.js的文件,在文件中写入

    import Vue from 'vue'
    export default  new Vue
    

    2、在需要进行操作的组件中,引入bus.js

    import bus from "../../bus/bus"
    

    3、传参的组件

        事件
        showFn() {
        //用bus.$emit(第一个参数代表事件名称,第二个参数代表需要传过去的参数,第二个参数代表需要传过去的第二个参数……可以传入多个参数)
          bus.$emit("showninfo", this.showin);
        },
    

    4、接收参数的组件

    通过$on监听事件。
    
    //挂载
       mounted(){
       
        bus.$on('showninfo',val=>{
          console.log(val);
          this.showuin = val
        })
    

    this.bus. e m i t 和 t h i s . b u s . emit 和this.bus. emitthis.bus.on来进行跨组件通信了

    方法二:Vuex 提供的功能
    变更vuex中的数据或者状态
    this.$store.commit('increment')
    第一个是方法名,第二个是参数
    
    
    拿到vuex中的数据
    访问数据用:$store
    methods: {
      increment() {
        this.$store.commit('increment')
        console.log(this.$store.state.count)
      }
    }
    
    通常放到计算属性中
      computed: {
        count () {
          return this.$store.state.count
        }
      }
    }
    
    方法三:v-model

    v-model 的本质就是绑定一个属性和事件

    // 父组件
    <aa class="abc" v-model="test" ></aa> 
    
    // aa子组件实现一:
    <template>
      <div>
        <ul>
          <li>{{'里面的值:'+ msg}}</li>
          <button @click="fn2">里面改变外面</button>
        </ul>
      </div>
    </template>
    
    <script>
      export default {
        model: {    // 使用model
          prop: 'msg', //prop属性将msg作为该组件被使用时(此处为aa组件被父组件调用)v-model能取到的值,
          event: 'cc' // event中的cc就是父组件上的自定义事件,用来更新父组件上的test值
        },
        props: {
          msg: ''
        },
        methods: {
          fn2 () {
            this.$emit('cc', this.msg+2)
          }
        }
      }
    </script>
    
    // aa子组件实现方法二:
    <template>
     <div>
        <ul>
          <li>{{'里面的值:'+ value}}</li> // value会被赋值为v-model绑定的test的值。
          <button @click="fn2">里面改变外面</button>
        </ul>
      </div>
    </template>
    
    <script>
      export default {
        props: {
          value: { // 必须要使用value
         default: '',
        },
        },
    
        methods: {
          fn2 () {
            this.$emit('input', this.value+2) // 这儿必须用input 发送数据,发送的数据会被父级v-model=“test”接受到,再被value=test传回来。
          }
        }
      }
    

    插槽slot

    为什么要使用插槽?

    slot翻译为插槽,组件的插槽:

    1. 组件的插槽也是为了让我们封装的组件更加具有扩展性。
    2. 让使用者可以决定组件内容的一些内容到底展示什么。

    如何封装合适呢?抽取共性,保留不同

    1. 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
    2. 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
    3. 是搜索框,还是文字,还是菜单。由调用者自己来决定。

    匿名插槽

    如何使用slot?

    1. 在子组件中,使用特殊的元素就可以为子组件开启一个插槽。
    2. 该插槽插入什么内容取决于父组件如何使用。
    使用
    我是插槽中的默认内容!!
    被替换的内容
    
    父组件
    
        
            删除
        
        
     子组件
      
      
    当组件渲染的时候, 将会被替换为“a标签”(标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:
    

    具名插槽

    当子组件的功能复杂时,子组件的插槽可能并非是一个。

    1. 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
    2. 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
    3. 这个时候,我们就需要给插槽起一个名字

    如何使用具名插槽呢?

    1. 非常简单,只要给slot元素一个name属性即可
    父组件
    
        <Todolist>
        //用标签将内容包起来,并在标签上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称,向具名插槽提供内容。
          <template v-slot:btn>
            <a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
          </template>
        </Todolist>
        
     子组件
      <slot name="btn"></slot>
      
    当组件渲染的时候,name相等的 <slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:
    
    任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
    
    注意 v-slot 只能添加在 <template> 上(有一种情况例外)
    
    

    作用域插槽

    默认情况下,父组件使用子组件,插槽数据默认是拿父组件的数据,而不是子组件拿数据。

    作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。

    ​ 作用域插槽,作用:可以拿到子组件slot标签上自定义属性的集合

    
    父组件
    
        <Todolist>
          <template v-slot:btn={idx}>
            <a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
          </template>
        </Todolist>
        
     子组件
          <template v-slot:default="{$index}">
            <slot name="btn" :idx="$index"></slot>
          </template>
          
          {idx}是Es6的解构语法
          $index 自定义的命名
          
          default意思是默认,下一个子组件不需要给name属性,直接可以渲染替换<slot></slot>里面的内容
      孙子组件
      		<slot :$index="i"></slot>
      
    当组件渲染的时候,<slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML
    多种写法
    // 1、基本写法
    <one-comp>
      <button slot="btn" slot-scope="scope">按钮{{scope.msg}}</button>
    </one-comp>
    
    // 2、基本写法之模板写法
    <one-comp>
      <template slot="btn" slot-scope="scope">
        <button>按钮{{scope.msg}}</button>
      </template>
    </one-comp>
    
    // 3、指令写法
    <one-comp v-slot:btn="scope">
      <button>按钮{{scope.msg}}</button>
    </one-comp>
    
    // 4、指令写法之模板写法
    <one-comp>
      <template v-slot:btn="scope">
        <button>按钮{{scope.msg}}</button>
      </template>
    </one-comp>
    

    FileZilla

    FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本

    Webpack

    Webpack(自动化 模块化 前端开发构建工具)

    1. gulp和webpack

    Gulp侧重于前端开发的 整个过程 的控制管理(像是流水线),我们可以通过给gulp配置不同的task(通过Gulp中的gulp.task()方法配置,比如启动server、sass/less预编译、文件的合并压缩等等)来让gulp实现不同的功能,从而构建整个前端开发流程。

    1. 生成项目依赖文件
    // 执行后生成package.json文件
    npm init -y
    
    2. 安装依赖(node环境在12.10.0下!)

    nvm install 12.10.0

    nvm use 12.10.0

    npm i [email protected] [email protected] -g

    // 最后的参数-D是安装到package.json的开发依赖devDependencies(开发环境)对象里,也可以用 --save-dev代替
    npm install webpack@4.44.1 webpack-cli@3.3.12 -D
    
    // 全局安装webpack和webpack-cli
    npm i webpack@4.44.1 webpack-cli@3.3.12 -g
    
    // -S是--save的简写,这样安装的话,会安装到dependencies(生产环境)对象里,也可以用 --save代替
    npm install jquery -S
    
    // package.json
    {
      "name": "webpack-demo",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "webpack": "^4.40.2",
        "webpack-cli": "^3.3.9"
      },
      "dependencies": {
        "jquery": "^3.4.1"
      }
    }
    
    

    devDependencies与dependencies的区别:

    在发布npm包的时候,本身dependencies下的模块会作为依赖,一起被下载;devDependencies下面的模块就不会自动下载了;但对于项目而言,npm install 会自动下载devDependencies和dependencies下面的模块。

    3.创建入口文件
    `index.html`
    
    引入js文件:<script src="./index.js"></script>
    
     js文件
     import $ from 'jquery'
    $('ul li:even').css({background: 'red'})
    $('ul li:odd').css({background: 'green'})
    

    浏览器打开会出现报错,因为浏览器并不兼容import引入模块这种方式,所以我们要用到webpack打包

    4、通过webpack打包
    // 执行命令  output输出
    webpack index.js -o dist/bundle.js
    

    出现 “无法将webpack 识别为……的报错

    出现这个报错,这是因为命令行执行时候会找全局的webpack,但是我们并没有安装全局的webpack,所以我们可以安装全局webpack,或者是使用脚本方式启动

    执行package.json文件中添加的start命令

    {
      "name": "webpack-demo",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
          
          
        "start": "webpack index.js -o dist/bundle.js"
          
          
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "webpack": "^4.40.2",
        "webpack-cli": "^3.3.9"
      },
      "dependencies": {
        "jquery": "^3.4.1"
      }
    }
    
    

    跑一下

    // 生成 dist文件夹和bundle.js文件
    npm run start
    

    然后再把index.html原来引入index.js的地方改成是通过webpack生成的bundle.js

    
    
    <script src="./dist/bundle.js">script>
    

    在根目录里,建立webpack.config.js:

    const path = require('path');
    
    module.exports = {
      entry: path.join(__dirname, './index.js'),	// dirname代表索引到文件所在目录
      output: {
        path: path.join(__dirname, './dist'),
        filename: 'bundle.js'
      }
    }
    

    package.json:

    文件修改命令

    "scripts": {
        "start": "webpack --config webpack.config.js"
      }
    
    webpack-dev-server

    这时候如果修改index.html的背景颜色red改成是gray,会发现浏览器刷新也没有效果,需要再跑一次npm run start命令才有用,这时候就需要webpack-dev-server(热重载)

    安装:

    npm install webpack-dev-server@3.11.2 -D
    

    package.json:

    "scripts": {
        "start": "webpack-dev-server --config webpack.config.js --open --port 3002 --hot"
      }
    // --open 自动打开浏览器
    // --port 服务监听的端口 3002
    // --hot  自动更新
    
    
    html-webpack-plugin

    安装:npm install [email protected] -D

    webpack.config.js

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: path.join(__dirname, './index.js'),
      output: {
        path: path.join(__dirname, './dist'),
        filename: 'bundle.js'
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: path.join(__dirname, './index.html'),
          filename: 'index.html'
        })
      ]
    }
    

    删掉index.html文件里面的bundle.js引用,因为html-webpack-plugin会自动把打包出来的bundle自动加到我们的index.html代码里

    因为webpack默认是不识别.css文件的,需要我们通过 loader.css 文件进行解释成正确的模块。

    安装css-loader和style-loader
    npm install css-loader@5.2.4 style-loader@2.0.0 -D 
    //index.css -> bundle.js -> style-loader -> 
    

    2、按键修饰符

    在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

    
    
    

    3、表单修饰符

    表单上常用的 trimnumber

    想去除用户输入的前后空格:

    
    

    希望在input的change之后再更新输入框的值

    
    

    .trim与.lazy可以合并使用:

    二者没有先后顺序

    
    

    Watch

    watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。

    简单点说,就是实时监听某个数据的变化。

    普通监听

        
        

    {{msg}}

    watch: { msg(val, oldVal){ console.log(val, oldVal) } } 跟data 同级

    立即监听

    如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

    
    
    跟data 同级
      watch: {
    监听变化的数据名
        num: {
          handler(val, oldVal) {
            console.log(val, oldVal);
          },
          // 组件注入页面时就立即监听
          immediate: true
        }
      }
    

    immediate需要搭配handler一起使用,其在最初绑定时,调用的函数也就是这个handler函数。

    深度监听

    当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。

    {{obj.age}}

    注意:

    1、如果监听的数据是一个对象,那么 immediate: true失效;

    2、一般使用于对引用类型的监听,深度监听,如监听一个Object,只要Object里面的任何一个字段发生变化都会被监听,但是比较消耗性能,根据需求使用,能不用则不用。

    3、因为上面代码obj是引用数据类型,val, oldVal指向一致,导致看到的结果一样。

    deep优化

    我们可以通过点语法获取对象中的属性,然后转为字符串,即是对深度监听的优化

    
    
    
    

    Watch与Computed的区别

    • watch中的函数是不需要调用的,computed内部的函数调用的时候不需要加()
    • watch(属性监听),监听的是属性的变化,而computed(计算属性),是通过计算而得来的数据
    • watch需要在数据变化时执行异步或开销较大的操作时使用,而对于任何复杂逻辑或一个数据属性,在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性computed。
    • computed 属性的结果会被缓存,且computed中的函数必须用return返回最终的结果
    • watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;

    Watch与Computed的使用场景

    • computed

      • 当一个结果受多个属性影响的时候就需要用到computed
      • 最典型的例子: 购物车商品结算的时候
    • watch

      • 当一个数据的变化需要有额外操作的时候就需要用watch
      • 搜索数据
    • 总结:

      • 一个值的结果受其他值的影响,用computed
      • 一个值的变化将时刻影响其他值,用watch

    Mixins混入

    mixins就是定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改。同一个生命周期,混入对象会比组件的先执行。

    1、导出mixins

    在src下创建 mixins/index.js,写入:

    export const MixinsFn = {
        created() { 
            console.log("这是mixins触发的created")
        }
    }
    

    2、引用mixins

    
    
    

    我们会发现,mixins中的createdabout中的created 优先执行。

    ref与$refs

    vue中获取页面里的某个元素(标签或组件),可以给它绑定ref属性,有点类似于给它添加id名。

    1、简单使用

    
     
    
     
    
    

    2、子组件中的数据获取及方法调用

    子组件:

    
     
    
     
    
    

    父组件:

    
     
    
     
    
    

    获取Dom节点

    
    
    
    methods:{
        fn() {
          console.log(this.$refs.btn); //可以获取Dom节点----
    	this.$refs.btn.innerText 可以获取Dom节点的内容-----获取child组件的num
        },
    }
    

    vue生命周期

    什么是生命周期: 从Vue创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。

    **生命周期钩子函数:**就是生命周期事件的别名;

    初始

    **beforeCreate:**实例刚刚在内存中被创建出来,此时还没有初始化 datamethods 属性。

    **created:**实例已经在内存中创建好,此时 datamethods 已经创建好,此时还没有开始编译模板

    和 methods、data 是同级
    初始
      beforeCreate() {
        console.log("1.1---------------beforeCreate");
        console.log(1.1, this.num, this.fn, document.getElementById("op"));
      },
      created() {
        console.log("1.2-------------created");
        console.log(1.2, this.num, this.fn, document.getElementById("op"));
      },
          
    挂载
      beforeMount() {
        console.log("2.1---------------beforeMount");
        console.log(2.1, this.num, this.fn, document.getElementById("op"));
      },
      mounted() {
        console.log("2.2-------------mounted");
        console.log(2.2, this.num, this.fn, document.getElementById("op"));
      },
          
    更新
      beforeUpdate(){  //视图跟新之前
        console.log("3.1-------------beforeUpdate");
        console.log(3.1, this.num, this.$refs.myp.innerHTML);
      },
      updated(){  //视图跟新之后
        console.log("3.2-------------updated");
        console.log(3.2, this.num, this.$refs.myp.innerHTML);
      },
          
    销毁
      beforeDestory(){  
        
      },
      destoryed(){
         
      }
    

    keep-alive包含的组件是不需要再重新创建(Create)

    挂载

    **beforeMount:**此时已经完成了模板编译,但是还没有挂载到页面中;

    **mounted:**这个时候已经把编译好的模板挂载到页面指定的容器里;

    beforeMount: data 的数据可以访问和修改,而且此时的模板已经编译好了,还没有更新到页面中

    mounted: 此时编译的模板更新到页面中了

    更新

    **beforeUpdate:**状态更新之前执行此函数,此时的 data 中的数据是最新,但是界面上显示的还是旧的,因为此时还没有开始重新渲染DOM节点;

    **updated:**实例更新完毕之后调用此函数,此时data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了;

    beforeUpdate: 此时修改输入框的内容,data中的数据已经更新了,但是页面上显示的还是旧数据;

    **updated:**此时 data 的内容已经更新,页面显示的也是最新的数据。

    销毁

    **beforeDestroy:**实例销毁之前调用,在这一步,实例让然完全可用。

    **destroyed:**实例销毁后调用,调用后,Vue实例指向的所以东西会被解绑,所有的事件监听器会被移除,所有的子实力也会被销毁。

    销毁声明周期(使用keep-alive组件是不会触发销毁生命周期)

    活跃和不活跃

    要搭配keep-alive组件来使用

     // 从不活跃进入活跃状态
      // activated() {
      //   console.log('activated');
      // },
      
      // // 从活跃进入不活跃
      // deactivated() {
      //   console.log('deactivated')
      // },
    

    Vue-router(路由)

    路由的概念

    简单来说路由 就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不 同的资源,请求不同的页面是路由的其中一种功能。

    1.后端路由

    概念:根据不同的用户的不同URL请求,发送到服务器以后返回不同的内容。
    本质:是URL请求地址与服务器资源之间的对应关系。

    1. 后端渲染的好处,相对于发送ajax请求拿数据,可以提高首屏渲染的性能,也有利于SEO的优化;
    2. 后端路由的缺点:
      1. 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码
      2. 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情

    2.前端路由

    概念:根据不同的用户事件,显示不同的页面内容
    本质:用户事件与事件处理函数之间的对应关系

    前后端分离阶段:

    • 随着Ajax的出现,有了前后端分离的开发模式;
    • 后端只提供API来返回数据(json,xml),前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中
    • 这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上
    • 并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可
    • 目前很多的网站依然采用这种模式开发

    单页面应用阶段:

    • 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由
    • 也就是前端来维护一套路由规则

    前端路由的核心是什么呢?

    • 改变URL,但是页面不进行整体的刷新

    前端路由规则

    URL的hash和HTML5的history模式

    1、hash模式

    在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取; 特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com (opens new window),因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

    2、history模式

    history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

    //写在导出模块里面 export default
    
    export default new VueRouter({
      routes,
      mode: 'hash'
     //还可以是 'history'
      //两者区别: hash 后面跟着# 之后才是path值
      //http://192.168.6.170:8080/#/me   http://192.168.6.170:8080/#/cart
        
      //history   /后面才是path值
      //http://192.168.6.170:8080/cate   http://192.168.6.170:8080/home
    })
    

    Vue-router基本使用

    目前前端流行的三大框架,都有自己的路由实现:

    • Angular的ngRouter
    • React的ReactRouter
    • Vue的vue-router

    vue-router是Vue的官方路由插件,它和Vue是深度集成的,适合用于构建单页面应用 https://router.vuejs.org/zh/ 。

    vue-router是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来;在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.

    安装:

    npm install vue-router
    

    一般项目中建议在cli创建项目时就直接选择需要路由,并搭配history模式。

    选择router 模式

    在index.js文件中,明确安装模块功能(并实例化)

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    

    对index.js文件进行路由设置

    //导入模块
    import VueRouter from 'vue-router'
    import Vue from 'vue'
    // 导入路由对象
    Vue.use(VueRouter)
    // 定义路由规则
    const routes = [
      {
        path: '/',
        component: () => import('../views/index.vue') ,
        children:[
          //定义方法一:
          {
            path: '/', // 浏览器访问
          //name的作用用于之后传参获取定义的名(此名是唯一)
            name: 'Home',
            component: Home
          children:[
                  {
                    path: 'cate',
                    component: () => import('../views/cate.vue')   // 路由懒加载
                  }
      		  ]
          },
        //定义方法二:
          {
            path: 'cate',
            component: () => import('../views/cate.vue')   // 路由懒加载
          },
        
          {
            path: '/',
            redirect: '/cart'		// 这就是路由的重定向,重新定义跳转路径
          },
          {
            path: 'cart',     // 改成这个之后,原来的/就没有对用的组件了
            component: () => import('../views/cart.vue') 
          },
              
          {
            path: 'me',
            component: () => import('../views/Mehome.vue') 
          },
        
          {
        path: '*',		// 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
        component: () => import('@/views/Page404.vue')
          }
        
        ]
      },
    
    ]
    // 创建路由实例 并导出
    export default new VueRouter({
      // 路由模式
      mode: 'hash', // history hash
      // 路由规则(路由映射关系)
      routes, 
    })
    

    懒加载的方式

    // 方式一: 结合Vue的异步组件和Webpack的代码分析
    const User = resolve => { require.ensure(['@/views/User.vue'], () => { resolve(require('@/views/User.vue')) }) };
    
    // 方式二: AMD写法
    const User = resolve => require(['@/views/User.vue'], resolve);
    
    // 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
    const Home = () => import(/* webpackChunkName: "user" */ '../views/User.vue')
    

    路由跳转方式

    使用 router-link 标签来实现跳转

    
    
    ///跳转之后,内容将替换
    //然后通过 `router-view` 来显示页面。`router-link` 最终会被渲染为a标签。
    
    
    

    router-link 默认会被解析为a标签,如果想让它转换成其他的标签,就需要添加tag属性:

    User
    

    此时,router-link 就被解析为li标签。

    点击跳转到指定的页面

    设置点击事件 方法
    
        jumpToAbout() {
    
    	push 在末尾添加	
          this.$router.push({
    push里面写的是对象
    
            // path: '/home'
            name: 'Home'
    	name是在定义路由规则里面设置的,等同于 path的作用
    
          })
    
    
    简写:
    // 简写
    this.$router.push('/user')
    

    除了push,还有

    go、 整数是前进,负数是后退

    forward、 前进

    back、 后退

    replace、替换----replace(新的访问 例如 “/name”)

    这几个来触发不同情况的跳转。

    router.push(location, onComplete?, onAbort?)
    router.push(location).then(onComplete).catch(onAbort)
    router.replace(location, onComplete?, onAbort?)
    router.replace(location).then(onComplete).catch(onAbort)
    router.go(n)
    router.back()
    router.forward()
    

    携带参数

        jumpFn(){
          this.$router.push({
            // path:""
            name:'About',
            携带的参数的方法名
            query:{
              id:123456,
              name:"张三"
            }
          })
        },
        
        在网页上显示:http://192.168.6.170:8080/cate?id=123456&name=张三
        
    在子组件中可以拿到 - 通过生命周期初始函数得到
    然后可以放入data 之后可以直接调用
    
        export default {
      data(){
        return {
          id:null,
          name:null
        }
      },
      生命周期初始函数
          created(){
    
            console.log(this.$route.query);
            this.id = this.$route.query.id
            this.name = this.$route.query.name
          }
        }
    

    使用params传参

    使用params传参,得到的结果与使用query传参得到的结果有以下区别:

    this.$router.push({name: "User", params: {userId: 123}})    // http://localhost:8081/user/123
    this.$router.push({name: "User", query: {userId: 123}})       // http://localhost:8081/?userId=123
    
    在路由的js文件中:
       '/editbrand': {
            path: '/editbrand/:id', //品牌管理编辑
            name: 'editbrand',
            component: () =>
                import(/* webpackChunkName: "editbrand" */ '../views/editbrand/Editbrand.vue'),
        },
    
         
         
          
    组件中:
          this.$router.push({
            name:'editbrand',
           params:{id:row.id}
          })
    
    * 重调强调:

    编程式导航中,使用name进行路径跳转,携带参数可以通过params和query,其中query会将参数携带在导航路径上,而使用path进行路径跳转,无法携带params,只能携带query。

    补充:

    params参数传参写法相当于在路径直接添加:

    //App.vue中:
    this.$router.push('/user/12');
    
    // router/index.js中:
    path: '/user/:userId',
        
    // User.vue中:
    created(){
        console.log(this.$route.params.userId);    // 获取到用户id12
    }
    

    路由的重定向

    const routes = [
      {
        path: '/',
        redirect: '/home'		// 这就是路由的重定向,重新定义跳转路径
      },
      {
        path: '/home',    // 改成这个之后,原来的/就没有对用的组件了
        component: () => import('@/views/Home.vue')
      },
      
      ...  ...
      {
        path: '*',		// 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
        component: () => import('@/views/Page404.vue')
      }
    ]
    

    vue-router 有几种导航钩子?

    1、全局守卫: router.beforeEach
    
    2、全局解析守卫: router.beforeResolve
    
    3、全局后置钩子: router.afterEach
    
    4、路由独享的守卫: beforeEnter
    
    5、组件内的守卫: beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
    
    全局守卫

    router.beforeEach

    全局前置守卫

    导航或者路由被触发时,全局前置守卫被调用,解析执行完成之后,导航再跳转

    守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

    注册:全局前置守卫
    const router = new VueRouter({ ... })
    
    router.beforeEach((to, from, next) => {
      // ...
    })
    
    
    接收三个参数
    to: (去)
    Route: 即将要进入的目标 路由对象
    
    from: (来)
    Route: 当前导航正要离开的路由
    
    
    next
    : Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    一般会使用  next(): 进行管道中的下一个钩子。
    
    
    	next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    
        next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重  	置到 from 路由对应的地址。
    
        next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 	  next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的   	 to prop 或 router.push 中的选项。
    
        next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给
        router.onError() 注册过的回调。
    
    全局解析守卫

    router.beforeResolve

    router.beforeResolve注册方法和全局前置守卫一样
    
    这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
    
    全局后置钩子

    router.afterEach

    这些钩子不会接受 next 函数也不会改变导航本身

    注册:
    router.afterEach((to, from) => {
      // ...
    })
    
    路由独享的守卫

    beforeEnter

    可以在路由配置上直接定义 beforeEnter 守卫:
    
    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
    守卫与全局前置守卫的方法参数是一样的。
    
    组件内的守卫

    beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave

    可以在路由组件内直接定义以下路由导航守卫:
    
    beforeRouteEnter
    beforeRouteUpdate (2.2 新增)
    beforeRouteLeave
    
    const Foo = {
      template: `...`,
      beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
          
        //可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
        //beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
          
      },
        例如:
        beforeRouteEnter (to, from, next) {
          next(vm => {
            // 通过 `vm` 访问组件实例
          })
        }
        
        
      beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
      },
      beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
    
    //这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
    beforeRouteLeave (to, from, next) {
      const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
      if (answer) {
        next()
      } else {
        next(false)
      }
    }
    

    完整的导航解析流程

    1. 导航被触发。
    2. 在失活的组件里调用 beforeRouteLeave 守卫。
    3. 调用全局的 beforeEach 守卫。
    4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
    5. 在路由配置里调用 beforeEnter
    6. 解析异步路由组件。
    7. 在被激活的组件里调用 beforeRouteEnter
    8. 调用全局的 beforeResolve 守卫 (2.5+)。
    9. 导航被确认。
    10. 调用全局的 afterEach 钩子。
    11. 触发 DOM 更新。
    12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

    $refs 和 $el的用法

    $refs
    $refs 
    一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。
    dom还没有渲染完成,是不能通过ref调用dom的。
    
    加在普通元素上,获取的是dom元素
    
    <div ref="system">测试</div>
    // 获取
    mounted() {
      console.log(this.$refs.system);
    }
    
    
    加在子组件上,获取的是组件实例,可以使用组件的所有方法
    // this.$ref.xxx.方法名()
    // 父组件
    <contact-info ref="contactInfo"/>
    import ContactInfo from './ContactInfo'
    components: { ContactInfo },
    mounted() {
       this.$refs.contactInfo.initVal(data) // 调用子组件方法
    }
    // 子组件
    methods: {
      initVal(data){
        Object.keys(this.contactInfo).forEach(val=>{
          this.contactInfo[val] = data[val]
        })
      }
    }
    
    
    具体使用:
    组件内(元素内)设置
    <div ref="自定义名称">
    </div>
    
    this.$refs 拿到
    如果是组件实例,可以使用组件的所有方法
    this.$refs.方法名
    
    
    $el
    $el
    Vue 实例使用的根 DOM 元素。
    $el读取的是组件实例挂载的dom元素
    
    // 子组件
    <template>
      <div>
        测试
      </div>
    </template>
    <script>
    export default {
      name: 'TestComs'
    };
    </script>
    
    
    // 父组件
       <test ref="testCom" />
       <div ref="test">11</div>
      mounted() {
        console.log(this.$refs.testCom, '组件ref'); // 获取组件实例
          
        console.log(this.$refs.testCom.$el, '组件el'); // 获取组件实例的dom元素
          //获得  
    测试
    console.log(this.$refs.test, '元素ref'); // 获取dom元素 //获得
    11
    console.log(this.$refs.test.$el, '元素el'); // $el用于vue组件,普通dom元素不能用 //获得undefined },

    $set

    Vue中data中变量的数据值发生改变,界面没有跟着更新,是什么原因(Vue数据双向绑定失效)

    1.如果data里面定义了对象,对象里面的键值没有,getter/setter函数没法监听到属性值的数据变化,会导致此现象的发生。

    解决方法:

    Vue.set(obj, key, value);
    // or
    this.$set(obj, key, value);
    
    使用方法:
    接收三个参数
    this.$set(原数组或者原对象,索引值或者键值对名,需要赋的值)
    

    Vuex----store

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

    说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。

    一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。

    安装;

    npm install vuex --save
    

    在store文件夹中的index.js中写入

    //引入模块
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    //导出模块
    export default new Vuex.Store({
      state: {
        num: 0,
        uerinfo:{}
      },
      mutations: {
        increment (state,user) {
          state.uerinfo = user
        }
      }
    })
    

    在miain.js文件中引入store

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

    vuex有以下常用的几个核心属性概念:

    State

    • State

    (个人理解)存放数据的容器

    vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。

      state: {
      数据,对象key : value值的方式
        num: 0,
        uerinfo:{}
      },
    
    组件中
    
    <template>
     <div>
        <h3>{{$store.state.num}}</h3>
     </div>
    </template>
    
    
    也可以使用computed
    computed: {
      num(){
      	return this.$store.state.num
      }
    }
    
    // 标签中
    <h3>{{num}}</h3>
    

    Getters

    • Getters

    vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

    封装异步操作

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        num: 2
      },
      getters: {
        // 这里的参数state可以让我们快速获取到仓库中的数据
        doubleNum(state) { 
          return state.num * 2;
        }
      }
    })
    
      
      
      组件中
    <template>
     <div>
        <h3>{{num}}</h3>
     </div>
    </template>
     
    <script>
    export default {
      computed: {
        num(){
          return this.$store.getters.doubleNum
        }
      }
    };
    </script>
      
    

    Mutations

    • Mutations

    个人理解:存放方法,然后此方法被别的组件调用

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        num: 2
      },
      mutations: {
        // payload专业名称为“载荷”,其实就是个参数
        addNum(state, payload) {
          state.num += payload;
        }
      }
    })
      
      
      
      调用的组件
      <template>
     <div>
        <h3>{{num}}</h3>
        <button @click="btnClick">累加2</button>
     </div>
    </template>
     
    <script>
    export default {
      computed: {
        num(){
          return this.$store.state.num
        }
      },
      methods: {
        btnClick(){
          // 使用commit来触发事件,第二个参数是要传递给payload的数据
          this.$store.commit('addNum', 2)
        }
      }
    };
    </script>
      
    如:
       this.$store.commit('increment',uerRes.data)
       this.$store.commit('函数方法名',传入的参数)
       
       并且它会接受 state 作为第一个参数
    

    Actions

    • Actions

    Action 类似于 mutation,不同在于:

    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作。
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        num: 2
      },
      mutations: {
        addNum(state, payload) {
          state.num += payload;
        }
      },
      actions: {
        // context是一个对象,包含了commit和state
        AsyncAddNum(context,payload) { 
          setTimeout(() => {
            context.commit('addNum', payload)
          }, 1000)
        }
      }
    })
      
      
      组件中
    <template>
     <div>
        <h3>{{num}}</h3>
        <button @click="btnClick">累加2</button>
     </div>
    </template>
     
    <script>
    export default {
      computed: {
        num(){
          return this.$store.state.num
        }
      },
      methods: {
        btnClick(){
          // dispatch是分发到意思,其实也是触发Actions中的方法
          this.$store.dispatch('AsyncAddNum', 2)
        }
      }
    };
    </script>
      
      
    

    当然,上面actions中的写法有点累赘,我们还可以改写:

    AsyncAddNum({ commit },payload) { 
      setTimeout(() => {
        commit('addNum', payload)
      }, 1000)
    }
    
    // 如果你还想获取state中的值,可以这样:
    AsyncAddNum({ commit,state },payload) { 
      console.log(state.num);		// 2
      setTimeout(() => {
        commit('addNum', payload)
      }, 1000)
    }
    

    Modules

    • Modules

    把累加单独抽出来作为一个模块,在store下新建一个 add/index.js 文件:

    export default {
        namespaced: true,	//	命名空间,为true时,可以在store中把当前模块文件夹名称(add),当作模块名使用
        state: {
            num: 2
        },
        getters: {
            doubleNum(state) {
                return state.num * 1;
            }
        },
        mutations: {
            addNum(state, payload) {
                state.num += payload;
            }
        },
        actions: {
            AsyncAddNum({ commit }, payload) {
                setTimeout(() => {
                    commit('addNum', payload)
                }, 300)
            }
        }
    }
    

    把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:

    引入存放模块的文件
    import add from './add'
    
    export default new Vuex.Store({
      ...,
    	modules: {
        add
      }
    })
    
    命名空间

    如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

    const moduleA ={
        namespaced:true,  //开启namespace:true,该模块就成为命名空间模块了
        state:{
            count:10,
            countA:888
        },
        getters:{...},
        mutations:{...},
        actions:{...}
    }
                   
                   
                   
                   
    可以在单个模块中通过添加namespaced:true的方式使其成为带命名空间的模块。
                   
    组件中如何获取带有命名空间moduleA中的state数据?
    1、基本方式:
        this.$store.state.moduleA.countA
    2、mapState辅助函数方式:
        ...mapState({
            count:state=>state.moduleB.countB
        }) 
          
                 
                 
    组件中调用命名空间模块中的getters
    共有三种方式,如下:
    //1.
    commonGetter(){
        this.$store.getters['moduleA/moduleAGetter']
    },
    //2.
    ...mapGetters('moduleA',['moduleAGetter']),此处的moduleA,不是以前缀的形式出现!!!
    //3.别名状态下
    ...mapGetters({
        paramGetter:'moduleA/moduleAGetter'
    }),
        
        
    组件中调用命名空间模块中的Mutations
    共有三种方式,如下:
    //1,3加个前缀moduleA/,都可以实现。2使用辅助函数未变名称的特殊点!!!
    //1.
    commonMutation(){
        this.$store.commit('moduleA/moduleAMutation');
    },
    //2.
    ...mapMutations('moduleA',['moduleAMutation']),
    //3.别名状态下
    ...mapMutations({
        changeNameMutation:'moduleA/moduleAMutation'
    }),
        
        
        
    组件中调用命名空间模块中的Actions(与mutations一致)
    共有三种方式,如下:
    1,3加个前缀moduleA/,都可以实现。2使用辅助函数未变名称的特殊点!!!
    //1.
    commonAction(){
        this.$store.dispatch('moduleA/moduleAAction')
    },
    //2.
    ...mapActions('moduleA',['moduleAAction']),
    //3.别名状态下
    ...mapActions({
        changeNameAction:'moduleA/moduleAAction'
    })
    
    
    在带命名空间的模块中,如何将action注册为全局actions
         两个条件:
               ①添加 root: true
               ②并将这个 action 的定义放在函数 handler 中
               
    //storeAction在命名空间moduleA中,但是它是一个全局actions
    const moduleA = {
        namespaced:true,
        storeAction:{
            root:true,  //条件1
            handler(namespacedContext, payload){//条件2:handler
                //namespacedContext 上下文信息
                //payload 载荷,即参数
                console.log(namespacedContext)
                console.log(payload)
                alert("我是模块A中的全局storeAction")
            }
        }
    }
    
    
    
    
    拆分写法

    实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js 进行拆分:

    state.js

    export default {
        num1: 0,
        title: '标题'
    }
    

    mutations.js

    export default {
        cutNum(state, payload) {
            state.num1 -= payload;
        }
    }
    

    actions.js

    export default {
        AsyncCutNum({ commit }, payload) {
            setTimeout(() => {
                commit('cutNum', payload)
            }, 300)
        }
    }
    

    modules.js

    import add from './add'
    export default {
        add
    }
    

    最后,在 store/index.js 中:

    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import mutations from './mutations'
    import actions from './actions'
    import modules from './modules'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      mutations,
      actions,
      modules
    })
    

    辅助函数

    mapState

    当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

    mapState 函数返回的是一个对象

    // 在单独构建的版本中辅助函数为 Vuex.mapState
    import { mapState } from 'vuex'
    
    export default {
      // ...
      computed: mapState({
      
        // 箭头函数可使代码更简练(方法一)
        count: state => state.count,
    
        // (方法二)传字符串参数 'count' 等同于 `state => state.count`
        countAlias: 'count',
    
        // (方法三)为了能够使用 `this` 获取局部状态,必须使用常规函数
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      })
    }
    

    当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

    computed: mapState([
      // 映射 this.count 为 store.state.count
      'count'
    ])
    

    mapState 函数返回的是一个对象

    我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符 (opens new window),我们可以极大地简化写法:

    computed: {
      localComputed () { /* ... */ },
      // 使用对象展开运算符将此对象混入到外部对象中
      ...mapState({
        // ...
      })
    }
    
    mapGetters

    mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

    import { mapGetters } from 'vuex'
    
    export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
        ...mapGetters([
          'doneTodosCount',
          'anotherGetter',
          // ...
        ])
      }
    }
    
    mapMutations
    import { mapMutations } from 'vuex'
    
    export default {
      // ...
      methods: {
        ...mapMutations([
          'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
    
          // `mapMutations` 也支持载荷:
          'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
        ]),
        ...mapMutations({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
        })
      }
    }
    
    mapActions
    import { mapActions } from 'vuex'
    
    export default {
      // ...
      methods: {
        ...mapActions([
          'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
    
          // `mapActions` 也支持载荷:
          'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
        ]),
        ...mapActions({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
        })
      }
    }
    

    内置组件

    keep-alive

    缓存加载

    作用:主要用于保留组件状态或避免重新渲染。

    includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

        用在包裹着占位符,包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
        
        		
        
        
        include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
    	exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
    	max - 数字。最多可以缓存多少组件实例。
    	
    	给组件起名,以此来确定是需要操作那个组件
    	export default {
    	  name:"Home",
    	  }
    	  
    
    
    逗号分隔字符串
    	
        
    正则表达式 (使用 `v-bind`)
        
            
            
    数组 (使用 `v-bind`)
       
    

    最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

    axios 访问 API

    axios

    安装

    npm install axios
    
    yarn add axios
    

    引入模块

    import axios from "axios";
    import qs from "qs"
    
    
    读取 JSON 数据
    methods;{
    	Fn(){
    		 axios.post('http://192.168.113.249:8081/cms/phoneRegin',{
                    params: {
                      ID: 12345
                    }
    		}
    	}.then(res=>{})
    	.catch(function (error) {
                console.log(error);
              });
    
    }
    
    
    方法二
    // 直接在 URL 上添加参数 ID=12345
    axios.get('/user?ID=12345')
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
    
    
    
    
    请求头
                    axios.get('http://192.168.113.249:8081/cms/phoneRegin',{
                      headers:{
                        'x-auth-token':'  '
                      }
    
    
    
    post请求
        axios
          .post('https://www.runoob.com/try/ajax/demo_axios_post.php')
          .then(response => (this.info = response))
          .catch(function (error) { // 请求失败处理
            console.log(error);
          });
      }
    
    post请求 传参说明
    axios.post('/user', {
        firstName: 'Fred',        // 参数 firstName
        lastName: 'Flintstone'    // 参数 lastName
      })
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
    

    qs

    qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库。

    用法之一:解析 URI 编码

    qs.stringify({phone:this.username,password:this.pwd})
    
    

    项目

    初始化样式

    <style lang="less" scoped>
        //
        在这里可以直接写样式或者引入less文件*
        scoped 样式只作用于当前组件
        //
        
    .header{
      height: 50px;
      background-color: #333;
    }
    </style>
    

    安装初始化样式库reset-css:

    npm i reset-css 或者   yarn add reset-css
    

    安装成功后在main.js中引入即可:

    import "reset-css"
    

    网站数据请求模块

    发送请求:

    安装axios模块:
    npm i axios
    

    尝试在app.vue中做数据请求:

    import axios from "axios"
    export default {
      	...
       created(){
        //get请求
        axios.get("http://192.168.113.249:8081/cms/products/recommend")
        .then(res=>{
          	console.log(res.data);
        })
        .catch(err=>{
          	console.log(err);
        })
    },
    }
    

    代理配置:

    vue.config.js 进行配置:

    module.exports = {
        devServer: {
            port: 8080,
            proxy: {
                '/api': {
                    target: "http://192.168.113.249:8081/cms",
                    pathRewrite: {
                        '^/api': ''
                    }
                }
            }
        }
    }
    

    由于配置文件修改了,这里一定要记得重新 yarn serve (跑一下)!!

    API与Request封装

    src 下新建 request目录 ,在request目录下新建 request.js

    request.js 中:

    import axios from "axios"
    
    const instance = axios.create({
        baseURL:"http://192.168.113.249:8081/cms",
        timeout:5000
    })
    
    instance.interceptors.request.use(config=>{
        console.log("每一次发起请求前,都会先执行这里的代码");
        console.log(config); //config本次请求的配置信息
        return config
    },err=>{
        return Promise.reject(err)
    })
    =
    instance.interceptors.response.use(res=>{
        console.log("每一次接收到响应,都会先执行这里的代码,再去执行成功的那个回调函数then");
        return res
    },err=>{
        return Promise.reject(err)
    })
    
    export default instance
    

    为了更好地管理我们的这些接口,我们把所有请求都抽取出来在一个api.js中

    request目录下新建api.js,api.js 中:

    import request from './request'
    
    // 请求精品推荐数据
    export const JingpinAPI = () => request.get('/products/recommend')
    

    如:

    request.js文件中

    import axios from 'axios';
    
    const instance = axios.create({
        timeout:15000,
        baseURL:'',
    })
    
    instance.interceptors.request.use((config)=>{
        const token = localStorage.getItem('token')
        //设置请求头
        if(token){
            config.headers['X-Nideshop-Token'] = token
        }
        return config
    },(err)=>{
        return Promise.reject(err)
    })
    
    instance.interceptors.response.use((reset)=>{
        return reset.data;
    },(err)=>{
        return Promise.reject(err)
    })
    
    export default instance;
    

    api.js文件中

    import request from './request';
    import qs from 'qs';
    
    //post请求
    export const getUserByToken = (data)=>request.post('http://kumanxuan1.f3322.net:8360/admin/auth/getUserByToken',qs.stringify(data))
    
    //get请求
    export const getGoods = (data)=>request.get('http://kumanxuan1.f3322.net:8360/admin/goods',{params:data}) 
    

    导航栏点击之后的样式显示:

    <li :class="$route.path==='/home'?'active':''">首页</li>
    
    三元表达式:
    true 执行前面的,false 执行后面的
    

    点击之后设置路由跳转

    <li @click="$router.push('/home')" :class="$route.path==='/home'?'active':''">首页</li>
    

    拼图验证滑块

    插件参考:https://gitee.com/monoplasty/vue-monoplasty-slide-verify

    安装插件

    npm install --save vue-monoplasty-slide-verify
    或者
    yarn add vue-monoplasty-slide-verify
    

    main.js入口文件引中入

    import SlideVerify from 'vue-monoplasty-slide-verify' // 拼图验证码
    
    Vue.use(SlideVerify)
    

    在组件中使用

    <template>
    	<slide-verify :l="42" :r="20" :w="362" :h="140" @success="onSuccess" @fail="onFail" @refresh="onRefresh" :style="{ width: '100%' }" class="slide-box" ref="slideBlock" :slider-text="msg"></slide-verify>
    </template>
    
    <script>
    export default {
      data() {
        return {
          msg: "向右滑动"
        };
      },
      methods: {
        // 拼图成功
        onSuccess(times) {
          let ms = (times / 1000).toFixed(1);
          this.msg = "login success, 耗时 " + ms + "s";
        },
        // 拼图失败
        onFail() {
          this.onRefresh(); // 重新刷新拼图
        },
        // 拼图刷新
        onRefresh() {
          this.msg = "再试一次";
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    /deep/.slide-box {
        width: 100%;
        position: relative;
        box-sizing: border-box;
        canvas {
            position: absolute;
            left: 0;
            top: -120px;
            display: none;
            width: 100%;
            box-sizing: border-box;
        }
        .slide-verify-block{
            width: 85px;
            height: 136px;
        }
        .slide-verify-refresh-icon {
            top: -120px;
            display: none;
        }
        &:hover {
            canvas {
                display: block;
            }
            .slide-verify-refresh-icon {
                display: block;
            }
        }
    }
    </style>
    

    点击获取验证码按钮的逻辑

    可以正常获取验证码的前提是:手机号格式正确

    所以,点击获取验证码的逻辑如下:

    1、如果校验手机号格式不正确,则return

    2、滑块拼图验证不通过,则return

    3、验证成功后,发起请求,获取验证码成功,则进行倒计时

    【百度】结合运营商之后的手机号码的正则:

    /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
    
    <div class="btn checkcode-btn" @click="getCode">获取验证码</div>
    ...
    <script>
        getCode(){
            // 1、验证手机号是否正确
            if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
                alert("请输入正确的手机号");
                this.$refs.phone.focus();
                return
            } 
            alert("手机号格式正确");
            
            // 2、进行滑块验证
               
            // 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
    		
      
          
        },
    
    </script> 
    

    倒计时及其展示

    <div class="btn checkcode-btn" @click="getCode">
        <span v-show="!isShowCount">获取验证码</span>
        <span v-show="isShowCount">{{count}} s</span>
    </div>
    <script>
    	methods:{
            countdown(){
                // 计时的方法
                // 倒计时,实际上就是每隔1秒,count减去1
                
                // 每次点击先让count为60
                this.count=60;
                let timer = null;
                timer = setInterval(()=>{
                    this.count--
                    if(this.count===0){
                        // 清除定时器 
                        clearInterval(timer)
                    }
                },1000);
            },
            getCode(){
                // 1、验证手机号是否正确
                /* if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
                    alert("请输入正确的手机号");
                    this.$refs.phone.focus();
                    return
                } */
                // 2、进行滑块验证
                if (this.msg == "再试一次" || this.msg == "向右滑动") {
                    alert("请先进行滑块验证");
                    return 
                }
                // 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
                // 这里先展示秒数
                this.countdown();
                this.isShowCount=true;
               
                
            },
    	}
    </script>
    

    连续点击倒计时bug

    此时连续点击倒计时会有bug,数字越跳越快,主要是重复开启倒计时造成的。

    其实我们只需要把事件给到 “获取验证码” 所在的span,就可以解决

    获取验证码 {{count}} s

    请求头和请求拦截器

    接口上有需要我们修改请求头Content-Type字段,并使用qs.stringnify进行格式转换:

    需要在请求拦截器加上:

    instance.interceptors.request.use(config=>{
        
        if (config.url === "/sendSMS" || config.url === "/wechatUsers/PCLogin") {
            config.headers["Content-Type"] = "application/x-www-form-urlencoded";
        }
        return config
    },err=>{
        return Promise.reject(err)
    })
    

    安装qs模块:

    npm i qs
    

    api.js中:

    import qs from "qs"
    // 发送短信验证码请求
    export const SendSMSAPI = params => request.post("/sendSMS",qs.stringify(params));
    

    作业

    day01

    聊天对话框

    解题思路:利用v-for遍历数组,利用v-model获取变化的值

    //先引入Vue的js文件
    <script src="./vue.js"></script>
    
    
    创建Vue对象
    new Vue({
        el:"选择器",
        data:{//数据
        	arr:[],
            val:""
            //val:"A"(意思是默认值是A)
    	},
        methods: {
            //方法
        }
        
    })
    
    
    //获取变化的值:用v-model 一般是用在父级元素上,获取子级元素的值的变化
    //还可以用在输入框,获取输入框的值
     v-model = "val"
     v-model = "text"
    
    
    //遍历数组---用  v-for"(item, index) in 需要遍历的数组数组"
    //谁需要遍历,就谁用v-for
    <div v-for='item in arr'><span>{{item.content}}</span></div>
        
    //判断name是A还是B,分别对应给class值,控制类名
    //用三元表达式
     :class="item.name==='A'?'atalk':'btalk'"
    
    //三元表达式 表达式?"等于(true)的值":"不等于(false)的值"
    
    
    //点击事件,点击给数组添加值,然后再遍历
    @click = '事件名'
    
    事件中调用data的值,需要用this
    
    methods:{
       talkTxt(){
           this.arr.push(`{name:${this.val},content:${this.text}}`)
           this.text = ""//点击之后自动清空文本框
               }
            }
     
    
    

    选项卡-tab栏

    思路:事件点击方法,传参数,带参数,然后将参数存起来;利用三元表达式判断是否等于参数值,给类名值

     @click='add(1)'
     
     三元表达式:
     :class='val===1?"current":""'
    

    todolist-微博新增发言功能

    思路:用数组将内容装起来,然后遍历获取数组的item内容和index索引值,点击删除,删除对应索引值的数组内容。

    用v-for ’(item,index)in arr ‘

    
    

    学生管理系统

    思路:用来 v-for = “(item,index)in arr” ;v-show 显示隐藏(true;false);将索引和item值存储,然后传给下个方法;

    JavaScript如何判断一个值是不是数字?

    第一种方法:isNaN()

    数字返回false 字符串返回true

    缺点:值有一个空串或是一个空格,isNaN将把c当作数字0来处理,所以检查不严谨。

    第二种方法:正则表达式

    /1+.?[0-9]* / / / 判 断 字 符 串 是 否 为 数 字 , 判 断 正 整 数 用 / [ 1 − 9 ] + [ 0 − 9 ] ∗ ] ∗ / //判断字符串是否为数字 ,判断正整数用 /^[1-9]+[0-9]*]* ////[19]+[09]]/

    reg.test( num )

    第三种方法: 利用typeof的返回值


    1. 0-9 ↩︎

    你可能感兴趣的:(html,css,前端)