前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)

文章目录

  • 一、Vue常用特性:
    • 1. 表单操作:
      • 1.1 基于Vue的表单操作:
      • 1.2 数据绑定:
      • 1.3 表单域修饰符:
    • 2. 自定义指令:
      • 2.1 Vue.directive 注册全局指令:
      • 2.2 Vue.directive 注册全局指令 带参数:
      • 2.3 自定义指令-局部指令:
    • 3. 计算属性:
      • 3.1 语法:
      • 2.3 示例:
      • 3.3 计算属性与方法的区别:
    • 4. 过滤器:
      • 4.1 什么是过滤器:
      • 4.2 自定义过滤器:
      • 4.3 过滤器的使用:
      • 4.4 局部过滤器:
      • 4.5 过滤器传参:
    • 5. 侦听器:
      • 5.1 什么是侦听器
      • 5.2 侦听器语法:
      • 5.3 侦听器应用场景:
    • 6. 生命周期:
      • 6.1 主要阶段:
  • 二、 案例: 图书管理:
    • 1. 数组变异方法:
    • 2. 替换数组
    • 3. 动态数组响应式数据
    • 4. 实现流程:
      • 4.1 图书列表:
      • 4.2 添加图书:
      • 4.3 修改图书:
      • 4.4 删除图书:
      • 4.5 常用特殊应用场景;
    • 5. 代码实现:

一、Vue常用特性:

1. 表单操作:

1.1 基于Vue的表单操作:

  • input单行文本;
  • textarea多行文本;
  • select下拉多选;
  • radio单选;
  • checkout复选;

1.2 数据绑定:

前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第1张图片

<head>
    <title>表单操作title>
    <style type="text/css">
        form div {
          height: 40px;
          line-height: 40px;
        }
        form div:nth-child(4) {
          height: auto;
        }
        form div span:first-child {
          display: inline-block;
          width: 100px;
        }
    style>
head>
<body>
    <div id="app">
        <form action="http://itcast.cn">
          <div>
            <span>姓名:span>
            <span>
              <input type="text" v-model='uname'>
            span>
          div>
          <div>
            <span>性别:span>
            <span>
              <input type="radio" id="male" value="1" v-model='gender'>
              <label for="male">label>
              <input type="radio" id="female" value="2" v-model='gender'>
              <label for="female">label>
            span>
          div>
          <div>
            <span>爱好:span>
            <input type="checkbox" id="ball" value="1" v-model='hobby'>
            <label for="ball">篮球label>
            <input type="checkbox" id="sing" value="2" v-model='hobby'>
            <label for="sing">唱歌label>
            <input type="checkbox" id="code" value="3" v-model='hobby'>
            <label for="code">写代码label>
          div>
          <div>
            <span>职业:span>
            <select v-model='occupation' multiple>
              <option value="0">请选择职业...option>
              <option value="1">教师option>
              <option value="2">软件工程师option>
              <option value="3">律师option>
            select>
          div>
          <div>
            <span>个人简介:span>
            <textarea v-model='desc'>textarea>
          div>
          <div>
            <input type="submit" value="提交" @click.prevent='handle'>
          div>
        form>
    div>

    <script src="../vue.js">script>
    <script>
        var vm = new Vue({
            el: "#app",
            data: {
                uname: "hello",
                gender: 1,
                hobby: [1,2],
                //occupation: 1,  // 单选
                occupation: [1,2], //多选
                desc: "hello world"
            },
            methods: {
                handle: function() {
                    console.log(this.uname, this.gender, this.hobby, this.occupation, this.desc);
                }
            }
        })
    script>
body>

1.3 表单域修饰符:

官网: https://cn.vuejs.org/v2/guide/forms.html#修饰符

  • .number 转换为数值:
    • 当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值;
    • 所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串;
  • .trim 自动过滤用户输入的首尾空白字符:
    • 只能去掉首尾的 不能去除中间的空格;
  • .lazy 将input事件切换成change事件:
    • .lazy 修饰符延迟了同步更新属性值的时机. 即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上;
  • 在失去焦点 或者 按下回车键时才更新;

<input v-model.number="age" type="number">


<input v-model.trim="msg">


<input v-model.lazy="msg" >

2. 自定义指令:

内置指令: https://cn.vuejs.org/v2/api/#指令

自定义指令: https://cn.vuejs.org/v2/guide/custom-directive.html

2.1 Vue.directive 注册全局指令:

获取元素焦点:

<body>
    <div id="app">
        <input type="text"  v-focus>
    </div>
    <script src="../vue.js"></script>
    <script>
        Vue.directive('focus', {
            // 当绑定元素插入到 DOM 中。 其中 el为dom元素
            inserted: function (el) {
                // 聚焦元素
                el.focus();
            }
        })
        var vm = new Vue({
            el: "#app"
        })
    </script>
</body>

2.2 Vue.directive 注册全局指令 带参数:

自定义指令-带参数:

  • bind - 只调用一次,在指令第一次绑定到元素上时候调用
  • inserted:
  • update:
  • componentUpdated:
  • unbind:
    在这里插入图片描述
<body>
    <div id="app">
        <input type="text" v-color='msg'>
    div>
    <script src="../vue.js">script>
    <script type="text/javascript">
    /*
        自定义指令-带参数
        bind - 只调用一次,在指令第一次绑定到元素上时候调用
        inserted:
        update:
        componentUpdated:
        unbind:
    */
    Vue.directive('color', {
        // bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
        // el 为当前自定义指令的DOM元素  
        // binding 为自定义的函数形参   通过自定义属性传递过来的值 存在 binding.value 里面
        bind: function(el, binding){
        // 根据指令的参数设置背景色
        // console.log(binding.value.color)
        el.style.backgroundColor = binding.value.color;
        }
    });
    var vm = new Vue({
        el: '#app',
        data: {
            msg: {
                color: 'blue'
            }
        }
    });
    script>
body>

2.3 自定义指令-局部指令:

  • 局部指令,需要定义在 directives 的选项 用法和全局用法一样;
  • 局部指令只能在当前组件里面使用;
  • 当全局指令和局部指令同名时以局部指令为准;
<input type="text" v-color='msg'>
<input type="text" v-focus>
<script type="text/javascript">
  /*
    自定义指令-局部指令
  */
  var vm = new Vue({
    el: '#app',
    data: {
      msg: {
        color: 'red'
      }
    },
 	  //局部指令,需要定义在  directives 的选项
    directives: {
      color: {
        bind: function(el, binding){
          el.style.backgroundColor = binding.value.color;
        }
      },
      focus: {
        inserted: function(el) {
          el.focus();
        }
      }
    }
  });
script>

3. 计算属性:

  • 模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁;
  • 计算属性是基于它们的响应式依赖进行缓存的 ;
  • computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化;

3.1 语法:

//computed  属性 定义 和 data 已经 methods 平级 
computed: {
	//  reverseString   这个是我们自己定义的名字 
	reverseString: function(){
	  console.log('computed')
	  var total = 0;
	  //  当data 中的 num 的值改变的时候  reverseString  会自动发生计算  
	  for(var i=0;i<=this.num;i++){
	    total += i;
	  }
	  // 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果    
	  return total;
	}
}

2.3 示例:

<div id="app">
   
  <div>{{reverseString}}div>
  <div>{{reverseString}}div>
   
  <div>{{reverseMessage()}}div>
  <div>{{reverseMessage()}}div>
div>
<script type="text/javascript">
  /*
    计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
  */
  var vm = new Vue({
    el: '#app',
    data: {
      msg: 'Nihao',
      num: 100
    },
    methods: {
      reverseMessage: function(){
        console.log('methods')
        return this.msg.split('').reverse().join('');
      }
    },
    //computed  属性 定义 和 data 已经 methods 平级 
    computed: {
      //  reverseString   这个是我们自己定义的名字 
      reverseString: function(){
        console.log('computed')
        var total = 0;
        //  当data 中的 num 的值改变的时候  reverseString  会自动发生计算  
        for(var i=0;i<=this.num;i++){
          total += i;
        }
        // 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果    
        return total;
      }
    }
  });
script>

3.3 计算属性与方法的区别:

  • 计算属性是基于它们的依赖进行缓存的;
  • 方法不存在缓存;

4. 过滤器:

4.1 什么是过滤器:

  • Vue.js允许自定义过滤器,可被用于一些常见的文本格式化;
  • 过滤器可以用在两个地方:双花括号插值和v-bind表达式;
  • 过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
  • 支持级联操作;
  • 过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本;
  • 全局注册时是filter,没有s的。而局部过滤器是filters,是有s的;
    在这里插入图片描述

4.2 自定义过滤器:

Vue.filter('过滤器名称', function(value) {
	// 过滤器业务逻辑
})

4.3 过滤器的使用:

{{msg | upper}}
{{msg | upper | lower}}

4.4 局部过滤器:

var vm = new Vue({
	el: "#app"",
	filter: {
		upper: function(val) {
			// 逻辑
		}
	}
})

4.5 过滤器传参:

Vue.filter('format', function(value, arg1){
	// 过滤器参数
})

{{date | format('yyyy-MM-dd')}}
<div id="box">
    
    {{ message | filterA('arg1', 'arg2') }}
div>
<script>
    // 在过滤器中 第一个参数 对应的是  管道符前面的数据   n  此时对应 message
    // 第2个参数  a 对应 实参  arg1 字符串
    // 第3个参数  b 对应 实参  arg2 字符串
    Vue.filter('filterA',function(n,a,b){
        if(n<10){
            return n+a;
        }else{
            return n+b;
        }
    });
    
    new Vue({
        el:"#box",
        data:{
            message: "哈哈哈"
        }
    })

script>

5. 侦听器:

5.1 什么是侦听器

  • 使用watch来响应数据的变化;
  • 一般用于异步或者开销较大的操作;
  • watch 中的属性 一定是data 中 已经存在的数据;
  • 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听;
  • 前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第2张图片

5.2 侦听器语法:

watch: {
	firstName: function(val) {
		this.fullName = val + ' ' + this.lastName;
	},
    //   注意:  这里 lastName 对应着data 中的 lastName 
     lastName: function(val) {
         this.fullName = this.firstName + ' ' + val;
     }
}

用法:

<div id="app">
    <div>
        <span>名:span>
        <span>
    <input type="text" v-model='firstName'>
  span>
    div>
    <div>
        <span>姓:span>
        <span>
    <input type="text" v-model='lastName'>
  span>
    div>
    <div>{{fullName}}div>
div>
<script type="text/javascript">
    /* 侦听器 */
    var vm = new Vue({
        el: '#app',
        data: {
            firstName: 'Jim',
            lastName: 'Green',
            // fullName: 'Jim Green'
        },
         //watch  属性 定义 和 data 已经 methods 平级 
        watch: {
            //   注意:  这里firstName  对应着data 中的 firstName 
            //   当 firstName 值 改变的时候  会自动触发 watch
            firstName: function(val) {
                this.fullName = val + ' ' + this.lastName;
            },
            //   注意:  这里 lastName 对应着data 中的 lastName 
            lastName: function(val) {
                this.fullName = this.firstName + ' ' + val;
            }
        }
    });
script>

5.3 侦听器应用场景:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 数据变化时执行异步或开销较大的操作;

案例: 用户名检测

  • 需求分析:

    • 通过v-model实现数据绑定;
    • 需要提供提示信息;
    • 侦听器真挺输入信息的变化;
    • 需要修改触发事件;
  • 实现;

<head>
    <title>用户名检测title>
head>
<body>
    <div id="app">
        <span>用户名: span>
        <span>
            <input type="text" v-model.lazy='uname'>
        span>
        <span>{{tip}}span>
    div>
    <script src="../vue.js">script>
    <script>
        /*
        侦听器:
        1. 采用侦听器监听用户名变化;
        2. 调用后台接口进行验证;
        3. 根据验证的结果吊证提示信息;
        */
        var vm = new Vue({
            el: '#app',
            data: {
                uname: '',
                tip: ''
            },
            methods: {
                checkName: function(uname) {
                    // 调用接口, 但是可以使用定时任务的方式模拟接口调用
                    var that = this;
                    setTimeout(function() {
                        // 模拟接口调用
                        if(uname == 'admin') {
                            that.tip = '用户名已经存在, 请更换...'
                        } else {
                            that.tip = '用户名可用...'
                        }
                    },2000)
                }
            },
            watch: {
                uname: function(val) {
                    // 调用后台接口, 验证用户名合法性;
                    this.checkName(val)
                    this.tip = '正在验证...'
                }
            }
        })
    script>
body>

6. 生命周期:

  • 事物从出生到死亡的过程;
  • Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用. 这些函数称为钩子函数;

6.1 主要阶段:

  • 挂载(初始化相关属性):
钩子函数 说明
beforeCreate 在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created 在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来
beforeMount 在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已
mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件
  • 更新(元素或组件的变更操作)
钩子函数 说明
beforeUpdate 数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的
updated 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的
  • 销毁:
钩子函数 说明
beforeDestroy 实例销毁之前调用
destroyed 实例销毁后调用

前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第3张图片

<head>
  <meta charset="UTF-8">
  <title>Documenttitle>
head>
<body>
  <div id="app">
    <div>{{msg}}div>
    <button @click='update'>更新button>
    <button @click='destroy'>销毁button>
  div>
  <script type="text/javascript" src="js/vue.js">script>
  <script type="text/javascript">
    /*
      Vue实例的生命周期
      
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: '生命周期'
      },
      methods: {
        update: function(){
          this.msg = 'hello';
        },
        destroy: function(){
          this.$destroy();
        }
      },
      beforeCreate: function(){
        console.log('beforeCreate');
      },
      created: function(){
        console.log('created');
      },
      beforeMount: function(){
        console.log('beforeMount');
      },
      mounted: function(){
        console.log('mounted');
      },
      beforeUpdate: function(){
        console.log('beforeUpdate');
      },
      updated: function(){
        console.log('updated');
      },
      beforeDestroy: function(){
        console.log('beforeDestroy');
      },
      destroyed: function(){
        console.log('destroyed');
      }
    });
  script>
body>

初始:
前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第4张图片

更新:
前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第5张图片

销毁:
前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第6张图片

二、 案例: 图书管理:

前端学习之VUE基础二:Vue常用特性(表单操作, 自定义指令,计算属性, 过滤器,侦听器,生命周期)_第7张图片

1. 数组变异方法:

  • 在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变
  • 变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展
方法 说明
push() 往数组最后面添加一个元素,成功返回当前数组的长度
pop() 删除数组的最后一个元素,成功返回删除元素的值
shift() 删除数组的第一个元素,成功返回删除元素的值
unshift() 往数组最前面添加一个元素,成功返回当前数组的长度
splice() 有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值
sort() sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组
reverse() reverse() 将数组倒序,成功返回倒序后的数组

2. 替换数组

  • 不会改变原始数组,但总是返回一个新数组
方法 说明
filter filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
concat concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组
slice slice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组

3. 动态数组响应式数据

Vue.set(vm.item, indexOfltem, newValue)

vm.$set(vm.item, indexOfltem, newValue)
  • 参数一:表示要处理的数组名称;
  • 参数二: 表示要处理的数组索引;
  • 参数三: 表示要处理的数组的值;

4. 实现流程:

4.1 图书列表:

  • 实现静态列表效果:
  • 基于数据实现模板效果;
  • 处理每行的操作按钮;

4.2 添加图书:

  • 实现表单的静态效果;
  • 添加图书表单域数据绑定;
  • 添加按钮事件绑定;
  • 实现添加业务逻辑;

4.3 修改图书:

  • 修改信息填充到表单;
  • 修改后重新提交表单;
  • 重新添加和修改的方法;

4.4 删除图书:

  • 删除按钮绑定事件处理;
  • 实现删除逻辑;

4.5 常用特殊应用场景;

  • 过滤器: 格式化日期;
  • 自动以指令: 获取表单焦点
  • 计算属性: 统计图书数量;
  • 侦听器: 验证图书存在性;
  • 生命周期: 图书数据处理;

5. 代码实现:

<head>
    <title>图书管理title>
    <style>
        .grid {
            margin: auto;
            width: 500px;
            text-align: center;
        }
        .grid table {
            width: 100%;
            border-collapse: collapse;
        }
        .grid th,td {
            padding: 10px;
            border: 1px dashed orange;
            height: 35px;
            line-height: 35px;
        }
        .grid th {
            background-color: orange;
        }
        .grid .book {
            padding-bottom: 10px;
            padding-top: 5px;
            background-color: orange;
            border-bottom: 1px dashed #000;
        }
        .grid .total {
            height: 30px;
            line-height: 30px;
            background-color: #F3DCAB;
            border-top: 1px solid #C2D89A;
            }
    style>
head>
<body>
    <div id="app">
        <div class="grid">
            <div>
                <h1>图书管理h1>
                <div class="book">
                    <div>
                        <label for="id">编号: label>
                        <input type="text" name="id" v-model='id' :disabled='flag' v-focus>
                        <label for="name">名称: label>
                        <input type="text" name="anme" v-model='name'>
                        <button @click="handle" :disabled='submitFlag'>提交button>
                    div>
                div>
            div>
            <div class="total">
                <span>图书总数:span>
                <span>{{total}}span>
              div>
            <table>
                <thead>
                    <tr>
                        <th>编号th>
                        <th>名称th>
                        <th>时间th>
                        <th>操作th>
                    tr>
                thead>
                <tbody>
                    <tr :key='item.id' v-for='item in bookList'>
                        <td>{{item.id}}td>
                        <td>{{item.bName}}td>
                        <td>{{item.createTime | format('yyyy-MM-dd hh:mm:ss')}}td>
                        <td>
                            <a href="#" @click.prevent="toEdit(item.id)">修改a>
                            <span>|span>
                            <a href="#" @click.prevent="deleteBook(item.id)">删除a>
                        td>
                    tr>
                tbody>
            table>
        div>
    div>

    <script src="../../vue.js">script>
    <script>
        // 获取焦点
        Vue.directive('focus', {
            inserted: function (el) {
                el.focus();
            }
            });

        // 过滤器
        Vue.filter('format', function(value, arg) {
            function dateFormat(date, format) {
                if (typeof date === "string") {
                var mts = date.match(/(\/Date\((\d+)\)\/)/);
                if (mts && mts.length >= 3) {
                    date = parseInt(mts[2]);
                }
                }
                date = new Date(date);
                if (!date || date.toUTCString() == "Invalid Date") {
                return "";
                }
                var map = {
                "M": date.getMonth() + 1, //月份 
                "d": date.getDate(), //日 
                "h": date.getHours(), //小时 
                "m": date.getMinutes(), //分 
                "s": date.getSeconds(), //秒 
                "q": Math.floor((date.getMonth() + 3) / 3), //季度 
                "S": date.getMilliseconds() //毫秒 
                };
                format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
                var v = map[t];
                if (v !== undefined) {
                    if (all.length > 1) {
                    v = '0' + v;
                    v = v.substr(v.length - 2);
                    }
                    return v;
                } else if (t === 'y') {
                    return (date.getFullYear() + '').substr(4 - all.length);
                }
                return all;
                });
                return format;
            }
            return dateFormat(value, arg);
            })
        var vm = new Vue({
            el: "#app",
            data: {
                flag: false,
                id: "",
                name: "",
                submitFlag: false, 
                bookList: []
            },
            methods: {
                handle: function() {
                    if(this.flag) {
                        // 编辑
                        this.bookList.some((item) => {
                            // 箭头函数的this指定义这个函数的父级作用域, 即handle作用域
                            if(item.id == this.id) {
                                console.log(item, this);
                                item.bName = this.name;
                                return true;
                            }
                        });
                        this.flag = false;
                    } else {
                        // 添加
                        this.bookList.push({
                            id: this.id,
                            bName: this.name,
                            createTime: new Date().getTime()
                        })
                    }
                    this.id = "";
                    this.name = "";
                },
                toEdit: function(id) {
                    // 根据id查询出要编辑的数据
                    var book = this.bookList.filter(function(item) {
                        return item.id == id;
                    })
                    this.flag = true;
                    this.id = book[0].id;
                    this.name = book[0].bName;
                },
                deleteBook: function(id) {
                    // 方法1:
                    // var index = this.bookList.findIndex(function(item) {
                    //     return item.id == id;
                    // });
                    // // 根据索引删除
                    // this.bookList.splice(index, 1);

                    // 方法2:
                    this.bookList = this.bookList.filter(function(item){
                        return item.id != id
                    })
                }
            },
            computed: {
                total: function(){
                    // 计算图书总数
                    return this.bookList.length;
                }
            },
            watch: {
                name: function(val){
                    // 验证书名是否存在
                    var flag = this.bookList.some(function(item) {
                        return item.bName == val;
                    })
                    console.log(flag)
                    if(flag) {
                        this.submitFlag = true;
                    } else {
                        this.submitFlag = false;
                    }
                }
            },
            mounted: function() {
                // 该生命周期钩子函数被触发时, 模板已经可以使用
                // 一般此时用于获取后台数据, 然后把数据填充打模板
                var data = [
                    {
                        id: 1,
                        bName: "java技术基础",
                        createTime: 2525609975000
                    },
                    {
                        id: 2,
                        bName: "python技术基础",
                        createTime: 2525609975000
                    },
                    {
                        id: 3,
                        bName: "人工智能",
                        createTime: 2525609975000
                    }
                ]
                this.bookList = data;
            }
        })
    script>
    
body>

你可能感兴趣的:(前端学习,#,Vue)