Vue基础

Vue概述

渐进式JavaScript框架

声明式渲染—>组件系统—>客户端路由—>集中式状态管理—>项目构建

  • 易用:熟悉HTML、CSS、JavaScript可快速上手Vue
  • 灵活:在一个库和一套完整框架之间自如伸缩
  • 高效:20KB运行大小,超快的虚拟DOM

Vue虚拟DOM

  • 前言:Vue2.0引入了虚拟DOM,比Vue1.0的初始渲染速度提升了2~4倍,并大大降低了内存消耗

  • 为什么要提出虚拟DOM:随着时代的发展,页面上的功能越来越多,我们需要实现的需求也越来越复杂,DOM的操作也越来越频繁,通过js操作DOM的代价很高,因为会引起页面的重排重绘,增加浏览器的性能开销,降低页面渲染速度

  • 为什么虚拟DOM可以提高渲染速度:传统方式用js操作DOM会有很多额外的DOM操作,例如一个ul标签下有很多个li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替代旧的ul,其实除了那个发生变化的li节点之外,其他节点都不需要重新渲染,由于DOM操作比较慢,所以这些DOM操作在性能上会有一定的浪费,避免这些不必要的DOM操作会提升很大一部分性能(减少重排重绘从而节省浏览器的性能开销)
    在这里插入图片描述
    其实虚拟DOM在Vue.js中主要做了两件事:

  • 提供与真实DOM节点所对应的虚拟节点vnode

  • 将虚拟节点vnode和旧虚拟节点oldVnode进行比对,对两个虚拟节点进行比对是虚拟DOM中最核心的算法即patch算法,patch算法的核心是diff算法(借助diff算法,通过对比前后虚拟DOM的差异,进行有针对性的打补丁渲染),它可以判断出哪些节点发生了变化,从而只对发生了变化的节点进行更新操作

Vue实例参数

  • el:元素的挂载位置(值可以是CSS选择器或者DOM元素)
  • data:模型数据(值是一个对象)

Vue模板语法

前端渲染: ajax发起请求对应的后台接口,服务端返回数据给前端,前端把请求回来的数据填充到HTML标签中

前端渲染方式:

  • 原生js拼接字符串
  • 使用前端模板引擎(如art-template)
    • 优点:相比于拼接字符串,代码明显规范很多,代码可读性明显提高了,方便后期的维护
    • 缺点:没有专门提供事件机制
  • 使用Vue特有的模板语法

模板语法概览:

  • 插值表达式
  • 指令
  • 事件绑定
  • 属性绑定
  • 样式绑定
  • 分支循环结构

插值表达式

插值表达式支持基本的计算操作

<div id="container">
	<div>{{msg}}</div>
    <div>{{1+2}}</div>
    <div>{{"Vue——"}+msg}</div>
</div>
<script>
    var vm=new Vue({
        el:'#container',
        data:{
            msg:'Hello World'
        }
    })
</script>

指令

概念:

  • 指令的本质就是自定义属性
  • 指令的格式:以v-开始

v-cloak指令用法

  • 插值表达式存在"闪动"的问题,使用v-cloak指令可以解决该问题
  • 原理:先通过样式隐藏内容,然后在内存中进行值替换,替换好之后再显示最终的结果

数据绑定指令

  • v-text填充纯文本(相比插值表达式更加简洁)
  • v-html填充HTML片段
    • 存在安全问题
    • 本网站内部数据可以使用,来自第三方的数据不可用
  • v-pre填充原始信息(显示原始信息,跳过编译过程)

v-cloak和数据绑定指令使用

<style>
    [v-cloak] {
        display: none;
    }
</style>
<div id="container" v-cloak>
    <div v-text="msg"></div>
    <div v-html="msg1"></div>
    <div v-pre>{{msg}}</div>
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            msg: "Hello World",
            msg1: "<h1>Hello World</h1>"
        }
    })
</script>

数据响应式

htm5和Vue响应式区别:

  • html5中的响应式(屏幕尺寸的变化导致样式的 变化)
  • Vue中的数据响应式(数据的变化导致页面内容的变化)
    • 比如导航菜单,data中的isCollapse为false时导航菜单不折叠收起,为true时导航菜单折叠收起

Vue修改响应式数据

Vue无法检测到数组和对象的property添加或移除,由于Vue会在初始化实例时对property执行 getter/setter 转化,所以 property必须在data对象上存在才能让Vue将它转换为响应式的。对于已经创建的实例,Vue不允许动态添加根级别的响应式 property(如:vm.fruits[2] = "lemon"

Vue提供了vm.$set(vm.items,index,value) 或者 Vue.set(vm.items,index,value)方法向嵌套对象添加响应式property

  • vm.items表示要处理的数组名称或者json对象名称
  • index表示要处理的数组的索引或者json对象的属性名
  • value表示要处理的数组的值或者json对象的属性值
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            fruits: ["apple", "banana", "orange"],
            info: {
                name: "张三",
                age: 18
            }
        }
    })
    // vm.fruits[2] = "lemon" 这样修改的数据不是响应式的(data中数据是改变了,但是页面数据还是没变)
    vm.$set(vm.fruits, 2, "lemon")
    // vm.info.gender = "male" 这样添加的数据不是响应式的(data中数据是改变了,但是页面数据还是没变)
    vm.$set(vm.info, "gender", "male")
</script>

v-once指令

  • v-once指令只编译一次(即显示内容之后不再具有响应式功能)
  • <div v-once></div>如果显示的内容后续不需要再修改,可以使用v-once来提高性能(Vue需要监听响应式的数据是否变化,这样会消耗性能)

MVC设计思想
在这里插入图片描述

  • V(view)视图层:直接面向最终用户,它是提供给用户的操作界面
  • M(model)数据层:程序需要操作的数据或信息
  • C(controller)控制层:它负责根据用户从视图层输入的指令,选取数据层中的数据,然后对其进行相应的操作

MVVM设计思想

Vue是以数据为驱动的,Vue自身将DOM和数据进行绑定,一旦创建绑定,DOM和数据将保持同步,每当数据发生变化,DOM就会跟着变化,每当DOM发生变化,数据也会跟着变化

在这里插入图片描述

  • M(model)数据层
  • V(view)视图层
  • VM(view-model)数据模型层即Vue实例

v-model指令

实现Vue双向数据绑定,input输入框的数据和data中msg数据实现了同步

<div id="container">
    <input type="text" v-model="msg">
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            msg: 123
        }
    })
</script>

v-model低层实现原理:

方法1:

<div id="container">
    <input type="text" v-bind:value="msg" v-on:input="handle">
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            msg: 123
        },
        methods: {
        	handle: function($event){
                //使用input输入框中的最新的数据覆盖原来的数据
                this.msg = $event.target.value;
            }
		}
    })
</script>

方法2:

<div id="container">
    <input v-bind:value="msg" v-on:input="msg=$event.target.value">
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            msg: 123
        }
    })
</script>

Vue事件绑定

data中定义了一个数据msg,vue实例上访问这个数据有两种方式,this. d a t a . m s g ( t h i s . data.msg(this. data.msgthis.data[msg])和 this.msg(vm.msg)

<div id="container">
    <div>{{num}}</div>
    <!-- v-on指令方法 -->
    <button v-on:click="num++">按钮1</button>
    <!-- v-on指令简写方法 -->
    <button @click="num++">按钮2</button>
    <!-- 直接绑定函数名方法(默认第一个参数为$event) -->
    <button @click="fun1">按钮3</button>
    <!-- 调用函数方法 -->
    <button @click="fun2(1,2,$event)">按钮4</button>
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            num: 0
        },
        methods: {
            fun1: function () {
                this.num++;
            },
            fun2: function (a, b, $event) {
                this.num++;
                console.log(a+b);
                //BUTTON(触发事件的对象的标签名)
                console.log($event.target.tagName); 
            }
        }
    })
</script>
  • 事件修饰符

    • .stop 阻止冒泡

      <a v-on:click.stop="handle">跳转</a>
      
    • .prevent 阻止默认行为

      <a v-on:click.prevent="handle">跳转</a>
      
    • .capture 使用事件捕获模式

      <div v-on:click.capture="handle"></div>
      
    • .self 事件不是从内部元素触发的

      <div v-on:click.self="handle"></div>
      
    • .once 事件只会触发一次

      <a v-on:click.once="handle"></a>
      
  • 按键修饰符

    使用<input v-on:keyup.13="submit">也是允许的

    .enter 回车键

    <input v-on:keyup.enter="submit">
    

    Vue提供了绝大多数常用的按键码的别名:

    • .enter
    • .tab
    • .delete (“删除”和“退格”键)
    • .esc
    • .space
    • .up
    • .down
    • .left
    • .right
  • 自定义按键修饰符

    全局config.keyCodes对象

    <input @keyup.aaa="handle">
    <!-- 对应的值必须是按键对应的event.keyCode值(65对应的就是键盘上a这个按键) -->
    Vue.config.keyCodes.aaa=65
    
  • 系统修饰键

    注意修饰键与常规按键不同,在和keyup事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住ctrl的情况下释放其它按键,才能触发 keyup.ctrl,而单单释放ctrl也不会触发事件

    • .ctrl
    • .alt
    • .shift
    • .meta
    <!-- Alt + C -->
    <input v-on:keyup.alt.67="clear">
    
    <!-- Ctrl + Click -->
    <div v-on:click.ctrl="doSomething"></div>
    
  • .exact 修饰符

    .exact 修饰符允许你控制由精确的系统修饰符组合触发的事件

    <!-- 即使Alt或Shift被一同按下时也会触发 -->
    <button v-on:click.ctrl="onClick"></button>
    
    <!-- 有且只有Ctrl被按下的时候才触发 -->
    <button v-on:click.ctrl.exact="onCtrlClick"></button>
    
    <!-- 没有任何系统修饰符被按下的时候才触发 -->
    <button v-on:click.exact="onClick"></button>
    
  • 鼠标按钮修饰符

    • .left
    • .right
    • .middle

Vue属性绑定

每种标签内置属性不同,如img有内置属性width和height,可通过动态绑定内置属性来动态修改img宽高(动态修改样式)

<img :width="isCollapse?'45px':'100px'" :height="isCollapse?'45px':'100px'" src="images/admin.jpg">

  • v-bind指令

    <a v-bind:herf="url">跳转</a>
    
  • 缩写形式

    <a :href="url">跳转</a>
    

Vue样式绑定

  • style样式动态绑定

    凡是有-的属性名都要变成驼峰式,比如font-size要变成fontSize

    属性名的值要用引号,比如fontSize: '18px’而不是fontSize: 18px

    • 对象形式

      :style="{color:activeColor,fontSize:fontSize+'px}"
      
      :style="{display:(activeName=='first'?'flex':'none')}"
      
    • 数组形式

      <div v-bind:style="[objStyle1,objStyle2]"></div>
      
      data: {
          objStyle1: {
              width: "100px",
              height: "100px",
              border: "1px solid red"
          },
          objStyle2: {
              background: "blue"
          }
      }
      

      结果渲染为:

      <div style="width: 100px; height: 100px; border: 1px solid red; background: blue;"></div>
      
    • 三目运算符形式

      :style="{fontSize:(isCollapse?'14px':'16px')}"
      
      :style="[{fontSize:(isCollapse?'14px':'16px')},{color:'#ffffff'}]"
      
    • 多重值形式

      :style="{display:['-webkit-box','-ms-flexbox','flex']}"
      
  • class样式动态绑定

    • 对象形式

      <div v-bind:class="{ active: isActive }"></div>
      
      <div v-bind:class="{ active1: isActive1,active2: isActive2}"></div>
      
      data: {
          isActive1: true,
          isActive2: true,
      }
      

      结果渲染为:

      <div class="active1 active2"></div>
      
    • 数组形式

      <div v-bind:class="[activeClass, errorClass]"></div>
      
      data: {
        activeClass: 'active',
        errorClass: 'text-danger'
      }
      

      结果渲染为:

      <div class="active text-danger"></div>
      
    • 三目运算符形式

      <div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
      

Vue分支循环结构

分支结构:

  • v-if
  • v-else
  • v-else-if
  • v-show
<div id="container">
    <div v-if="score>=90">优秀</div>
    <!-- 只有满足条件的div才会被渲染到页面,score>=80&&score<90为true -->
    <div v-else-if="score>=80&&score<90">良好</div>
    <div v-else>一般</div>
    <!-- v-show为true时元素才会显示 -->
    <div v-show=flag>v-show</div>
</div>
<script>
    var vm = new new Vue({
        el: "#container",
        data: {
            score: 85,
            flag: true
        }
    })
</script>

注意点:

  • v-else元素必须紧跟在带v-if或者v-else-if的元素的后面,否则它将不会被识别

  • v-if控制元素是否渲染到页面

  • v-show控制元素是否显示(已经渲染到了页面),不支持 <template> 元素,也不支持v-else

  • v-if 与 v-show的区别:

    • v-if是“真正”的条件渲染,在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
    • v-if也是惰性的,如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块
    • 使用v-show元素总是会被渲染,并且只是简单地基于CSS的display进行切换
    • v-if有更高的切换开销,而v-show有更高的初始渲染开销
    • 需要非常频繁地切换,则使用v-show较好
    • 在运行时条件很少改变,则使用v-if较好

循环结构:

  • v-for遍历数组

    必须绑定key值,key的作用是帮助Vue区分不同的元素,从而提高性能

    <li :key="item.id" v-for="(item,index) in myfruits">
        {{item.id+"---"+item.ename+"---"+item.cname+"---"+index}}
    </li>
    
    data: {
        myfruits: [{
            id: "a",
            ename: "apple",
            cname: "苹果"
        }, {
            id: "b",
            ename: "banana",
            cname: "香蕉"
        }, {
            id: "o",
            ename: "orange",
            cname: "橘子"
        }]
    }
    
  • v-for遍历对象

    <!-- name是key,"张三"是value,index是索引从0开始 -->
    <div v-for="(key,value,index) in obj"></div>
    
    data: {
        obj: {
            name: "张三",
            age: 18
        }
    }
    

分支结构和循环结构结合使用:

<ul>
    <!-- 只有满足if条件的li才能被渲染到页面 -->
    <li v-if="value==18" v-for="(key,value,index) in obj">{{key+"---"+value+"---"+index}}</li>
</ul>
data: {
    obj: {
        name: "张三",
        age: 18
    }
}

Vue常用特性

  • 表单操作
  • 自定义指令(应用场景:获得表单焦点)
  • 计算属性(应用场景:统计图书数量)
  • 侦听器(应用场景:验证图书的存在性)
  • 过滤器(应用场景:格式化日期)
  • 生命周期(应用场景:图书数据处理)

基于Vue的表单操作

通过value的值来控制显示

<form id="form1" action="XXXXXX" method="post">
    <div>
        <span>姓名:</span>
        <span>
            <input type="text" name="xingming" id="" v-model="username">
        </span>
    </div>
    <div>
        <span>性别:</span>
        <span>
             <input type="radio" name="xingbie" id="male" value="" v-model="gender"><label for="male"></label>
             <input type="radio" name="xingbie" id="female" value="" v-model="gender"><label for="female"></label>
        </span>
    </div>
    <div>
        <span>爱好:</span>
        <span>
            <input type="checkbox" name="aihao" id="ball" value="篮球" v-model="hoddy"><label for="ball">篮球</label>
            <input type="checkbox" name="aihao" id="game" value="游戏" v-model="hoddy"><label for="game">游戏</label>
            <input type="checkbox" name="aihao" id="code" value="写代码" v-model="hoddy"><label for="code">写代码</label>
        </span>
    </div>
    <div>
        <span>职业:</span>
        <!-- 或者multiple=true -->
        <select name="zhiye" id="" v-model="occupation" multiple>
            <option value="教师">教师</option>
            <option value="程序员">程序员</option>
            <option value="职业选手">职业选手</option>
        </select>
    </div>
    <div>
        <span>个人简介:</span>
        <textarea name="gerenjianjie" id="" cols="30" rows="10" v-model="brief"></textarea>
    </div>
    <div>
        <span><input type="submit" value="提交" @click.prevent="fun"></span>
    </div>
</form>
data:{
    username:"小米",
    gender:"男",
    hoddy:["篮球","游戏"],
    occupation:["教师","程序员"],
    brief:"我是中国人"
}

表单域修饰符

  • number:转化为数值

    <input v-model.number="age">
    
  • trim:去掉开始和结尾的空格

    <input v-model.trim="msg">
    
  • lazy:将input事件切换为change事件

    <!-- 默认情况下v-model用的是iput事件 -->
    <input v-model.lazy="msg">
    

Vue自定义指令

  • 全局自定义指令

    • 不带参数的全局自定义指令

      el:表示指令所绑定的元素

      使用autofocus属性与focus()方法功能相同,一个页面上只能有一个autofocus属性,不能滥用

      <div id="container">
          <!-- 实现页面一加载就自动获得焦点 -->
          <input type="text" v-setfocus>
      </div>
      <script>
          Vue.directive("setfocus",{
              inserted: function(el){
                  el.focus();
              }
          })
          var vm=new Vue({
              el:"#container"
          })
      </script>
      
    • 带参数的全局自定义指令

      el:表示指令所绑定的元素

      binding:表示一个对象{ name: “setcolor”, value: { color: “blue”}}

        <div id="container">
          <!-- 实现将input输入框的背景颜色设置为蓝色 -->
            <input type="text" v-setcolor="msg">
        </div>
        <script>
            Vue.directive("setcolor",{
                bind: function (el,binding){
                    el.style.backgroundColor = binding.value.color;        
                }
            })
            var vm=new Vue({
                el:"#container",
                data:{
                    msg:{
                        color: "blue"
                    }
                }
            })
        </script>
      
  • 局部自定义指令

    局部自定义指令只能在本组件中使用

    • 不带参数的局部自定义指令

      <div id="container">
          <input type="text" v-setfocus>
      </div>
      <script>
          var vm = new Vue({
              el: "#container",
              directives: {
                  setfocus: {
                      inserted: function (el) {
                          el.focus();
                      }
                  }
              }
          })
      </script>
      
    • 带参数的局部自定义指令

      <div id="container">
          <input type="text" v-setcolor="msg">
      </div>
      <script>
          var vm = new Vue({
              el: "#container",
              data: {
                  msg: {
                      color: "blue"
                  }
              },
              directives: {
                  setcolor: {
                      bind: function (el, binding) {
                          el.style.backgroundColor = binding.value.color;
                      }
                  }
              }
          })
      </script>
      

计算属性

  • 使模板内容更加简洁
  • 基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值
<div id="container">
    <input type="text" v-model="msg">
    <div>{{reverseString1()}}</div>
    <div>{{reverseString1()}}</div>
    <div>{{reverseString2}}</div>
    <div>{{reverseString2}}</div>
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            msg: "hello"
        },
        methods: {
            reverseString1: function () {
                console.log("reverseString1");
                return this.msg.split("").reverse().join("");
            }
        },
        computed: {
            reverseString2: function () {
                console.log("reverseString2");
                return this.msg.split("").reverse().join("");
            }
        }
    })
</script>

在这里插入图片描述

计算属性和methods方法的区别:

对于任何复杂逻辑,都应当使用计算属性

如果函数本身比较复杂,需要计算很长时间,两者的性能差异就会显现出来

  • 计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,这就意味着只要 msg 还没有发生改变,多次访问 reverseString2 计算属性会立即返回之前的计算结果(返回缓存),而不必再次执行函数

  • methods方法,每当触发重新渲染时,调用方法将总会再次执行函数

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

<div id="container">
    {{ fullName }}
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            firstName: 'Foo',
            lastName: 'Bar'
        },
        computed: {
            fullName: {
                // getter(获取值)
                get: function () {
                    return this.firstName +' '+ this.lastName
                },
                // setter(fullName设置了新的值,firstName和lastName对应设置新的值)
                set: function (newValue) {
                    var names = newValue.split(' ')
                    this.firstName = names[0]
                    this.lastName = names[names.length - 1]
                }
            }
        }
    })
</script>

侦听器

计算属性和侦听器的区别:

使用侦听器实现加法计算器的代码是命令式且重复的,使用计算属性明显简洁了且不重复,虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器,当需要在数据变化时执行异步或开销较大的操作时,使用侦听器是最有用的

  • 使用侦听器实现加法计算器:

    <div id="container">
        <input type="number" v-model="a">
        <span>+</span>
        <input type="number" v-model="b">
        <span>=</span>
        <span>{{sum}}</span>
    </div>
    <script>
        var vm = new Vue({
            el: "#container",
            data: {
                a: 0,
                b: 0,
                sum: 0
            },
            // a或b的数据一旦发生变化就会通知侦听器属性来响应数据的变化
            watch: {
                // val为a的最新值
                a: function (val) {
                    this.sum = parseInt(this.a) + parseInt(this.b)
                },
                // val为b的最新值
                b: function (val) {
                    this.sum = parseInt(this.a) + parseInt(this.b)
                }
            }
        })
    </script>
    
  • 使用计算属性实现加法计算器

    <div id="container">
        <input type="number" v-model="a">
        <span>+</span>
        <input type="number" v-model="b">
        <span>=</span>
        <span>{{sum}}</span>
    </div>
    <script>
        var vm = new Vue({
            el: "#container",
            data: {
                a: 0,
                b: 0,
            },
            computed: {
                sum: function(){
                    return parseInt(this.a) + parseInt(this.b)
                }
            },
        })
    </script>
    

侦听器的立即执行(immediate)与深度监听(deep):

watch最初绑定的时候是不会执行的,只有当侦听的数据发生改变才会执行

var vm = new Vue({
    el: '#app',
    data: {
        obj: {
            id: 1,
            name: "张三",
            age: 18
        }
    },
    watch: {
        // 改成了一个对象,属性值handler固定写法
        obj: {
            // 监视函数
            handler: function (newVal, oldVal) {
                console.log(newVal);
                console.log(oldVal);
                console.log('objChanged')
            },
            // 代表开启深度监视(数据的任何一个属性发生变化,监视函数都会执行)
            deep: true,
            // immediate设置为true(代码一加载,立马执行监视函数)
            immediate: true
        }
    }
})
// 修改obj的age
vm.$set(vm.obj, "age", 20)

深度监听会有性能问题,可以使用字符串形式监听:

'obj': {

}
//或
'obj.id': {
	
}
//或
'obj.age': {

}

过滤器

  • 全局自定义过滤器(使用过滤器格式化日期)

    • 在main.js中添加全局的时间过滤器

      Vue.filter('dateFormat', function (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');
        const hh = (dt.getHours() + '').padStart(2, '0');
        const mm = (dt.getMinutes() + '').padStart(2, '0');
        const ss = (dt.getSeconds() + '').padStart(2, '0');
        return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
      })
      
    • 使用时间过滤器

      <template slot-scope="scope">
      	{{scope.row.date | dateFormat}}
      </template>
      
  • 局部自定义过滤器(使用过滤器实现首字母和尾字母大写并拼接原msg值)

    <div id="container">
        <!-- AbcDabcd -->
        {{msg | filterFirst | filterLast(msg)}}
    </div>
    <script>
        var vm = new Vue({
            el: "#container",
            data: {
                msg: "abcd"
            },
            filters: {
                filterFirst: function (msg) {
                    if (!msg) return "";
                    msg = msg.toString()
                    return msg.charAt(0).toUpperCase() + msg.slice(1)
                },
                filterLast: function (msg,arg1) {
                    if (!msg) return "";
                    msg = msg.toString()
                    lastIndex = msg.length-1
                    return msg.slice(0,lastIndex) + msg.charAt(lastIndex).toUpperCase() + arg1
                }
            }
        })
    </script>
    

Vue实例的生命周期

Vue所有的生命周期钩子自动绑定在this上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法,这是因为箭头函数绑定了父上下文,因此this与你期待的Vue实例不同

**主要阶段:**创建前后,挂接前后,更新前后,显示和隐藏,销毁前后

Vue实例的产生过程:

  • beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用
  • created:在实例创建完成之后被立即调用(数据观测和事件配置已经完成)
  • beforeMount:在挂载开始之前被调用,相关的render函数首次被调用
  • mounted:el被新创建的vm.$el替换,并挂载到实例上去之后被调用
  • beforeUpdate:数据更新时被调用,发生在虚拟DOM打补丁之前
  • updated:由于数据更改导致虚拟DOM重新渲染和打补丁,在这之后会被调用
  • activated:keep-alive组件激活时调用
  • deactivated:keep-alive组件停用时调用
  • beforeDestroy:实例销毁之前被调用(实例仍然完全可用)
  • destroyed:实例销毁后被调用
<div id="container">
    <div>{{msg}}</div>
    <button @click="update">更新</button>
    <button @click="destroy">销毁</button>
</div>
<script>
    var vm = new Vue({
        el: "#container",
        data: {
            msg: "Vue实例的生命周期"
        },
        methods: {
            update: function () {
                this.msg = "更新了"
            },
            destroy: function () {
                this.$destroy()
            }
        },
        beforeCreate: function () {
            console.log("beforeCreate")
        },
        created: function () {
            console.log("create")
        },
        beforeMount: function () {
            console.log("beforeMount")
        },
        mounted: function () {
            console.log("Mount")
        },
        // 当data中的数据发生变化时beforeUpdate和updated就会被调用
        beforeUpdate: function () {
            console.log("beforeUpdate")
        },
        updated: function () {
            console.log("updated")
        },
        // 当this.$destroy()被执行了beforeDestroy和destroyed就会被调用
        beforeDestroy: function () {
            console.log("beforeDestroy")
        },
        destroyed: function () {
            console.log("destroyed")
        }
    })
</script>

Vue组件化开发之组件注册

Vue全局组件注册

全局组件注册之后可以用在任何新创建的Vue根实例的模板中,a和b子组件在各自内部也都可以相互使用

<div id="app">
    <component-a></component-a>
</div>
<script>
    Vue.component("component-a", {
        // data必须是个函数(形成闭包的环境,保证每一个组件拥有一份独立的数据)
        data: function () {
            return {
                msg: "这是组件a"
            }
        },
        template:
        `
        <div>
            <div>this is component-a</div>
            <div>{{msg}}</div>
            <component-b></component-b>
        </div>
        `
    })
    Vue.component("component-b", {
        data: function () {
            return {
                msg: "这是组件b"
            }
        },
        template: 
        `
        <div>
            <div>this is component-b</div>
            <div>{{msg}}</div>
        </div>
        `
    })
    var vm = new Vue({
        el: "#app",
        data: {}
    })
</script>

Vue局部组件注册

<div id="app">
    <component-a></component-a>
    <component-b></component-b>
</div>
<script src="js/vue.js"></script>
<script>
    var ComponentA = {
        data: function () {
            return {
                msg: "ComponentA"
            }
        },
        template: `<div>this is {{msg}}</div>`
    };
    var ComponentB = {
        data: function () {
            return {
                msg: "ComponentB"
            }
        },
        template: `<div>this is {{msg}}</div>`
    };
    var vm = new Vue({
        el: "#app",
        data: {},
        components: {
            "component-a": ComponentA,
            "component-b": ComponentB
        }
    });
</script>

使用场景:如何在App.vue中将我们Users组件进行展示出来

  • 全局组件注册:

    • 在main.js中全局组件注册

      import Users from './components/Users'
      Vue.component("users",Users)
      
    • 就可以在App.vue中直接引入users组件了

      <users></users>
      
  • 局部组件注册:

    <template>
      <div id="app">
        <users></users>
      </div>
    </template>
    <script>
    import Users from './components/Users'
    export default {
      name: 'App',
      components: {
        "users": Users
      }
    }
    </script>
    <style></style>
    

Vue组件间数据交互

Vue父子组件间传值

  • 父组件给子组件传值方法,使用props

    • 自定义属性arry动态绑定来自父组件的值(即 :arry = fruits)
    • 子组件通过props接收父组件传过来的值(即 props: [“array”])
  • 子组件给父组件传值方法,使用$emit

    • 子组件通过使用$emit触发自定义监听事件add,并将子组件的值(即’橙子’)通过参数传给了父组件
    • 父组件通过push方法接收子组件传过来的值
<div id="app">
    <children-com :arry="fruits" @add="push($event)"></children-com>
</div>
<script>
    Vue.component("children-com", {
        props: ["arry"],
        template: 
        `
        <div>
            <ul>
                <li :key="index" v-for="(item,index) in arry">{{item}}</li>
            </ul>
            <button @click="$emit('add','橙子')">添加水果</button>
        </div>
        `
    });
    var vm = new Vue({
        el: "#app",
        data: {
            fruits: ["苹果", "香蕉", "橘子"]
        },
        methods: {
            push: function (val) {
                this.fruits.push(val);
            }
        }
    })
</script>

Vue非父子组件间传值

在这里插入图片描述

  • 事件中心:管理组件间的通信

    var eventHub = new Vue()
    
  • 监听事件(通过事件中心监听a组件)

    eventHub.$on("component-a", (val) => {
    	this.num += val; //val作为形参接收传过来的实参1
    });
    
  • 触发事件(通过事件中心触发监听a组件的事件)

    eventHub.$emit("component-a", 1); //1作为实参传给监听a组件的事件
    
  • 销毁监听事件(将监听a组件的事件销毁)

    eventHub.$off("component-a");
    
<div id="app">
    <button @click="disappear">销毁监听事件</button>
    <component-a></component-a>
    <component-b></component-b>
</div>
<script>
    var eventHub = new Vue();
    Vue.component("component-a", {
        data: function () {
            return {
                num: 0
            }
        },
        template: 
        `
        <div>
            <div>component-a:{{num}}</div>
            <button @click="fun">使b组件加1</button>
        </div>
        `,
        methods: {
            fun: function () {
                eventHub.$emit("component-b", 1);
            }
        },
        mounted: function () {
            eventHub.$on("component-a", (val) => {
                this.num += val;
            });
        }
    });
    Vue.component("component-b", {
        data: function () {
            return {
                num: 0
            }
        },
        template: 
        `
        <div>
            <div>component-b:{{num}}</div>
            <button @click="fun">使a组件加1</button>
        </div>
        `,
        methods: {
            fun: function () {
                eventHub.$emit("component-a", 1);
            }
        },
        mounted: function () {
            eventHub.$on("component-b", (val) => {
                this.num += val;
            });
        }
    });
    var vm = new Vue({
        el: "#app",
        methods: {
            disappear: function () {
                eventHub.$off("component-a");
                eventHub.$off("component-b");
            }
        }
    });
</script>

Vue插槽

在2.6.0中,为具名插槽和作用域插槽引入了一个新的统一的语法(即 v-slot 指令),它取代了 slotslot-scope,这两个目前已被废弃但未被移除

若a组件的template中没有<slot>元素,直接在<component-a></component-a>之间写入任何内容都不会显示出来(任何内容都会被抛弃),<slot>元素作为承载分发内容的出口

若a组件的template中有<slot>元素,<component-a></component-a>之间可以包含任何代码,甚至其他的组件

Vue组件插槽:

<component-a></component-a>之间填充的任何内容都会替换<slot></slot>标签

<div id="app">
    <component-a>
        Your Profile
    </component-a>
</div>
<script>
    Vue.component('component-a', {
        template: 
        `
        <div>
            <slot></slot>
        </div>
        `
    })
    let vm = new Vue({
        el: '#app',
        data: {}
    })
</script>

插槽后备内容:

<component-a></component-a>中没提供内容时,可在<slot></slot>中设置后备内容

<div id="app">
    <component-a></component-a>
</div>
<script>
    Vue.component('component-a', {
        template: 
        `
        <div>
            <slot>Your Profile</slot>
        </div>
        `
    })
    let vm = new Vue({
        el: '#app',
        data: {}
    })
</script>

Vue具名插槽:

<base-layout></base-layout>中的<template>元素中的所有内容都将会被传入相应的插槽

<div id="app">
    <base-layout>
        <template v-slot:header>
            <h1>这是header</h1>
        </template>
        <template v-slot:default>
            <p>这是main</p>
        </template>
        <template v-slot:footer>
            <h1>这是footer</h1>
        </template>
    </base-layout>
</div>
<script>
    Vue.component('base-layout', {
        template: 
        `
        <div class="container">
            <header>
                <slot name="header"></slot>
            </header>
            <main>
                <slot></slot>
            </main>
            <footer>
                <slot name="footer"></slot>
            </footer>
        </div>
        `
    })
    let vm = new Vue({
        el: '#app',
        data: {}
    })
</script>

Vue编译作用域:

父级模板里的所有内容都是在父级作用域中编译的,子模板里的所有内容都是在子作用域中编译的

<div id="app"></div>中的所有内容都是在vm实例作用域中编译的,所以<component-a></component-a>中v-show指令绑定的isShow是vm实例中的,而子组件a里的所有内容都是在子组件a作用域中编译的

<div id="app">
    <component-a v-show="isShow"></component-a>
</div>
<script src="js/vue.js"></script>
<script>
    Vue.component('component-a', {
        data: function () {
            return {
                isShow: false
            }
        },
        template: 
        `
        <div>
            <h1>我是子组件a</h1>
            <h1 v-show="isShow">我是子组件a</h1>
        </div>
        `
    })
    let vm = new Vue({
        el: '#app',
        data: {
            isShow: true
        }
    })
</script>

Vue作用域插槽:

如下代码:a组件已经封装好了,但是a组件没有提供插槽,a组件结构是固定死的,使用<component-a></component-a>永远都是一种结构,扩展性不强

<div id="app">
    <component-a></component-a>
    <component-a></component-a>
</div>
<script src="js/vue.js"></script>
<script>
    Vue.component('component-a', {
        data: function () {
            return {
                language: ['C', 'C++', 'C#', 'Js', 'Java']
            }
        },
        template: 
        `
        <div>
            <ul>
                <li v-for="item in language">{{item}}</li>
            </ul>
        </div>
        `
    })
    let vm = new Vue({
        el: '#app',
        data: {}
    })
</script>

需求:

  • 子组件a有一组数据:language: [‘C’, ‘C++’, ‘C#’, ‘Js’, ‘Java’]
  • 子组件a的数据在页面中以多种形式展示出来:如以列表形式或者以水平方向展示

分析:

  • 这时候就需要子组件a提供插槽了
  • 然后通过<template></template>中的slot-scope="slot"来引用插槽对象,从而拿到子组件a中的数据
  • 然后在<template></template>中将子组件a的数据以span结构形式来展示

一句话总结Vue作用域插槽:父组件替换插槽的标签,但是内容是由子组件来提供

<div id="app">
    <component-a></component-a>
    <component-a>
        <!-- slot-scope="slot"会引用插槽对象(即获取到了插槽对象) -->
        <template slot-scope="slot">
            <span v-for="item in slot.data">{{item}}——</span>
        </template>
    </component-a>
</div>
<script src="js/vue.js"></script>
<script>
    Vue.component('component-a', {
        data: function () {
            return {
                language: ['C', 'C++', 'C#', 'Js', 'Java']
            }
        },
        template: 
        `
        <div>
            <slot :data="language">
                <ul>
                    <li v-for="item in language">{{item}}</li>
                </ul>
            </slot>
        </div>
        `
    })
    let vm = new Vue({
        el: '#app',
        data: {}
    })
</script>

Vue2.6.0 中使用v-slot:default="slotProps"替代了slot-scope="slot"

处理边界情况

在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的

访问根实例

new Vue({
    data: {
        foo: 1
    },
    created: function () {
        // 写入根组件的数据
        this.$root.foo = 2
        // 获取根组件的数据
        console.log(this.$root.foo)
        // 访问根组件的计算属性
        this.$root.bar
        // 调用根组件的方法
        this.$root.baz()
    },
    computed: {
        bar: function () {
            console.log("bar")
        }
    },
    methods: {
        baz: function () {
            console.log("baz")
        }
    }
})

访问父组件实例

父组件通过prop向子组件传值,子组件也可通过**$parent**访问父组件的值

访问子组件或子元素

通过 $refs 访问子组件,注意:$refs 只会在组件渲染完成之后生效

<div id="app">
    <input type="text" ref="input">
</div>
<script>
    new Vue({
        el: "#app",
        data: {},
        mounted() {
            this.$refs.input.focus()
        }
    })
</script>

通过 $el 获取Vue实例关联的DOM元素

如果是html元素直接 this.$refs.input.offsetTop 获取,如果是Element-UI封装的元素或者 el: "#app" 挂载,需要通过 e l 来 获 取 D O M 元 素 ( 如 : ‘ t h i s . r e f s . e l F o r m . el来获取DOM元素(如:`this.refs.elForm. elDOMthis.refs.elForm.el.offsetHeight`)

<div id="app">
    <input type="text" ref="input">
</div>
<script>
    new Vue({
        el: "#app",
        data: {},
        mounted() {
            console.log(this.$refs.input.offsetTop) //10
            console.log(this.$el) //<div id="app">...<div>
        }
    })
</script>

依赖注入

使用props是父组件向子组件共享数据,而使用依赖注入是父组件向所有的子孙组件共享数据(可跨层级的分享数据)

// 父组件中抛出要分享的数据
provide: function () {
  return {
    getMap: this.getMap
  }
}

// 在子组件中注入一下就可以用了
inject: ['getMap']

过渡&动画

Vue在插入、移除或者更新DOM时,提供多种不同方式的应用过渡效果,包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:

  1. 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是则在恰当的时机添加/删除 CSS 类名
  2. 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用
  3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)

单元素/组件的过渡

<div id="app">
    <button @click="isShow = !isShow">toggleShow</button>
    <transition name="move">
        <p v-if="isShow">hello</p>
    </transition>
</div>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                isShow: true
            }
        }
    })
</script>
<style>
    /* 显示的过渡效果 */
    .move-enter-active {
        transition: all 1s
    }
    /* 隐藏的过渡效果 */
    .move-leave-active {
        transition: all 3s
    }
    /* 隐藏时的样式 */
    .move-enter,.move-leave-to {
        opacity: 0
        transform: translateX(20px)
    }
</style>

过渡的类名

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter

在这里插入图片描述

CSS 动画

CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除

<div id="app">
    <button @click="show = !show">toggleShow</button><br>
    <transition name="bounce">
        <p v-if="show" style="display: inline-block;">hello world</p>
    </transition>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            show: true
        }
    })
</script>
<style>
    .bounce-enter-active {
        animation: bounce-in 1s
    }
    .bounce-leave-active {
        animation: bounce-in 1s reverse
    }
    @keyframes bounce-in {
        0% {
            transform: scale(0)
        }
        50% {
            transform: scale(1.5)
        }
        100% {
            transform: scale(1)
        }
    }
</style>

显性的过渡持续时间

<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

列表过渡

<div id="list-demo" class="demo">
    <button v-on:click="add">Add</button>
    <button v-on:click="remove">Remove</button>
    <transition-group name="list" tag="p">
        <span v-for="item in items" v-bind:key="item" class="list-item">
            {{ item }}
        </span>
    </transition-group>
</div>
<script>
    new Vue({
        el: '#list-demo',
        data: {
            items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
            nextNum: 10
        },
        methods: {
            randomIndex: function () {
                return Math.floor(Math.random() * this.items.length)
            },
            add: function () {
                this.items.splice(this.randomIndex(), 0, this.nextNum++)
            },
            remove: function () {
                this.items.splice(this.randomIndex(), 1)
            }
        }
    })
</script>
<style>
    .list-item {
        display: inline-block
        margin-right: 10px
    }
    .list-enter-active,.list-leave-active {
        transition: all 1s
    }
    .list-enter,.list-leave-to {
        opacity: 0
        transform: translateY(30px)
    }
</style>

Vue插件的使用

插件通常用来为Vue添加全局功能。插件的功能范围没有严格的限制,一般有以下几种:

  • 添加全局方法或者属性,如 vue-custom-element(Vue自定义元素插件)
  • 添加全局资源:指令/过滤器/过渡等,如 vue-touch(Vue移动端触摸插件)
  • 通过全局混入来添加一些组件选项,如 vue-router(Vue路由插件)
  • 添加Vue实例方法,通过把它们添加到 Vue.prototype 上实现
  • 一个库,提供自己的API,同时提供上面提到的一个或多个功能,如 vue-router(Vue路由插件)

使用插件

通过全局方法 Vue.use() 使用插件,它需要在调用 new Vue() 启动应用之前完成

Vue.use(MyPlugin)
//或者
Vue.use(MyPlugin,{
	option: xxx,
    option: xxx,
    option: xxx
})

Vue脚手架

Vue CLI用于快速生成Vue项目基础架构

安装3.x版本的Vue CLI:

npm install -g @vue/cli

基于3.x版本的脚手架创建Vue项目:

  • 基于交互式命令行的方式创建:

    vue create my-project
    
  • 基于图形化界面的方式创建:

    vue ui
    

选择模板(选择手动配置)

  • default(默认配置)
  • Manually select features(手动配置)

选择配置
选择:Router,Vuex,CSS Pre-processors,Linter

是否使用路由的 history 模式
选择:yes

选择css预处理器
选择:Less

选择Eslint代码验证规则
选择:ESLint + Standard config // 基本配置

选择什么时候进行代码规则检测
选择:Lint on save // 保存就检测

把babel,postcss,eslint这些配置文件放哪
选择:In dedicated config files // 独立文件放置

是否保存配置
选择:yes

给配置起名字

使用yarn还是npm进行包管理
选择:yarn

打开项目

yarn serve启动项目

基于2.x的旧模板创建旧版Vue项目:

npm install -g @vue/cli-init
vue init webpack my-project

Vue脚手架创建的实际开发项目目录结构

.
├── README.md  ------------------------ 说明文件
├── package.json  ----------------------- 项目配置
├── vue.config.js  ------------------------ webpack配置入口
├── public  --------------------------------- 入口文件
├──   ├── favicon.ico  ---------------- 网页图标
├──   └── index.html  --------------- 入口页面
└── src  ------------------------------------- 源码目录
    ├── apis  --------------------------------- 网络请求与请求的所有接口(分模块)
    ├──  ├── index.js  ----------------- 引入所有api
    ├──  ├── config.js ----------------- 网络请求设置
    ├──  ├── setup.js  ----------------- 封装的所有网络请求
    ├──  └── modules/xx.js  ------- 按模块划分api
    ├── assets  ----------------------------- 项目资源文件目录(图片、字体等)
    ├──  ├── images  ------------------ 所有图片
    ├──  ├── less  ----------------------- 所有样式
    ├──  ├──  ├─ index.less  ------------------- 所有样式引入入口
    ├──  ├──  ├─ common/_base ----------- 公共基础样式
    ├──  ├──  ├─ common/_common ------ 通用公共样式
    ├──  ├──  ├─ common/_components - 公共样式部件
    ├──  ├──  ├─ common/_iconfont -------- 公共icon
    ├──  ├──  ├─ common/_mixins ---------- 公共混入样式
    ├──  ├──  ├─ common/_resetElement- 重置elementUI样式
    ├──  ├──  └─ common/_theme ---------- 主题色配置
    ├──  ├── svgs ---------------------- 所有svg图片
    ├── components  ------------------- 业务模块集合目录(组件)
    ├──  ├── index.js ----------------- 注册所有全局组件
    ├── router  ---------------------------- 路由
    ├── store  ------------------------------ vuex(分模块)
    ├── utils  ------------------------------ 工具函数
    ├──  ├── commonData  ------- 挂载在$common上的数据
    ├──  ├── directive --------------- 所有指令
    ├──  ├── filter  -------------------- 所有过滤器
    ├──  └── veeValidate ----------- 表单校验配置
    ├── plugins  -------------------------- 引入的插件自动生成的配置
    ├── views  ---------------------------- 页面集合目录
    ├── ├── xx.vue  -------------------- 页面
    ├── └── children -----------------拆分出的子页面
    ├── App.less  ------------------------ 主样式
    ├── App.vue  ------------------------ 主组件
    └── main.js  ------------------------- 项目级入口配置文件

Vue项目目录结构

在这里插入图片描述

main.js和index.html是如何关联的?

  • main.js为入口文件
  • 运行npm run dev后,若不做特殊设置,实际index.html页面中仅挂载了app.js一个脚本,所有组件去哪儿了,app.js是如何形成的?
  • Vue实例化在main.js中,但在index.html中并没有引入main.js,main.js和index.html是如何关联的?

vuecli搭建的项目本质是一个集成预设置的webpack项目

  • 入口文件是一个webpack概念,入口文件是webpack构建内部依赖图的起点
  • main.js为入口文件,app.js是由webpack打包生成的输出文件
  • 将app.js挂载到index.html这一过程是由webpack的一个插件html-webpack-plugin完成的,html-webpack-plugin会自动帮你在dist目录下生成一个html文件,并且引用相关的assets文件(如css、js文件)

Vue项目打包后dist文件目录详解

css文件夹:

  • app.css文件是自己写的css样式
  • chunk-vendors.css是ui框架的css样式

js文件夹:

  • app.js是自己写的逻辑代码
  • chunk-vendors.js是通过import导入的第三方依赖包。防止该文件体积过大,可以使用webpack的externals配置,凡是声明在externals中的第三方依赖包都不会被打包,同时可以在index.html文件头部使用CDN链接外部资源
  • 其他的.js文件是使用路由懒加载打包后的文件,按需导入路由,文件的名字是路由懒加载配置中的分组名称
  • .js.map是一个Source map文件,Source map就是一个信息文件,里面存储着位置信息,转换后的代码的每一个位置所对应的转换前的位置。项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知哪里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列报错,方便开发调试使用

render函数的作用

render函数是Vue通过JS渲染DOM结构的函数createElement,约定可以简写为h

render: function (createElement) {
    return createElement(App)
}

简写为:

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

实际渲染为:

import App from './App'
new Vue({
    el: '#app',
    template: '<App></App>',
    components: {
        App
    }
})

Element-UI基于Vue的桌面端组件库

官网 API

Mint UI基于Vue的移动端组件库

官网 API Demo

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值