待整理-vue

vue笔记

一、vue基础

vue基本介绍

官网 https://cn.vuejs.org
API 相当于字典,使用的时候可以查询下

尚硅谷vue技术学习
vue基础、vue-cli、vue-router、vuex、element-ui、vue3

学习vue之前要掌握的知识
ES6语法规范
ES6模块化
包管理器
原型、原型链
数组常用方法
axios
promise

vue的特点

  1. 采用组件化模式,提高代码复用率,更好维护
  2. 声明式编码,无需操作DOM,提高开发效率
    注:区分于js的命令式编码
  3. 使用虚拟DOM+优秀的Diff算法,尽量使用DOM节点
  4. 学习vue之前要掌握的JavaScript基础知识

vue安装

方式1:直接在html中引入

  1. 下载开发版本vue.js
  2. 安装vue开发工具(浏览器插件)
  3. 关闭productionTip提示

<script type="text/javascript" src="../js/vue.js">script>
<script type="text/javascript">
    //设置为 false 以阻止 vue 在启动时生成生产提示
    Vue.config.productionTip = false;
script>

方式2:npm中安装,安装脚手架

初识vue

初识Vue

  1. 想让Vue工作,就必须创建一个vue实例,且要传入一个配置对象
  2. root容器里的代码依然负荷html规范,只不过混入了一些特殊的vue语法
  3. root容器里的代码被称为vue模板
  4. vue实例和容器是一一对应的
  5. 真实开发中只有一个vue实例,并且会配合着组件一起使用
  6. {{xxx}}中xxx要写js表达式,且xxx可以自动读取到data中的所有属性
  7. 一旦data中的数据发生改变,那么页面中用到待数据的地方也会自动更新

注意区分:js表达式 和 js代码(语句)
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方;
如:a,a+b,demo(1),x===y?‘a’:‘b’
2.js代码(语句)
如:if(){},for(){}

vue helloworld小案例


<div id="root">
    <h1>helle,{{name}}h1>
div>

<script type="text/javascript">
    //设置为 false 以阻止 vue 在启动时生成生产提示
    Vue.config.productionTip = false;
    // 创建vue实例
    const x = new Vue({
        el: '#root',  //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
        data: {//data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象
            name: '尹礼胜'
        }
    })
script>

模板语法

vue模板语法有两大类
插值语法
功能:用于解析标签提内容
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性

指令语法
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性
备注:vue中有很多的指令,切形式都是:v-???,此处我们只是拿v-bind举个例子

<div id="root">
    
    <h3>helle,{{name}}h3>
    
    <a v-bind:href="school.url" :x="url">点我去{{school.name}}学习a>
div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    const x = new Vue({
        el: '#root',
        data: {
            name: '尹礼胜',
            school: {
                name: "上海大学",
                url: "https://www.shu.edu.cn/"
            },
            url: "www.baidu.com"
        }
    })
script>

数据绑定 v-bind v-model

DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>数据绑定title>
		
		<script type="text/javascript" src="../js/vue.js">script>
	head>
	<body>
		
		
		<div id="root">
			
			

			
			单向数据绑定:<input type="text" :value="name"><br/>
			双向数据绑定:<input type="text" v-model="name"><br/>

			
			
		div>
	body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		new Vue({
			el:'#root',
			data:{
				name:'尚硅谷'
			}
		})
	script>
html>

MVVM模型

数据代理 {{ }}

1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。

vm.name vm._data.name vm.options.name
options 创建vue实例vm时输入对象

<div id="root">
    <h3>{{username}}h3>
    <h3>{{userId}}h3>
div>
<script>
    Vue.config.productionTip = false;
    const vm = new Vue({
        el: "#root",
        data: {
            username: "ZhangSan",
            userId: "10001000"
        }
    })
    console.log(vm)
script>

事件处理 v-on:/@

事件修饰符,捕获:由外往内,冒泡:由内往外
event 事件对象

事件的基本使用

事件的基本使用:
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click=“demo” 和 @click=“demo($event)” 效果一致,但后者可以传参;

<body>
    <div id="root">
        <h2>欢迎来到{{name}}学习h2>
        <button @click="showInfo1">点我提示信息1(不传参)button>
        <button @click="showInfo2($event,66)">点我提示信息2(传参)button>
    div>
body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
        },
        methods: {
            showInfo1(event) {
                // console.log(event.target.innerText)
                // console.log(this) //此处的this是vm
                alert('同学你好!')
            },
            showInfo2(event, number) {
                console.log(event, number)
                // console.log(event.target.innerText)
                // console.log(this) //此处的this是vm
                alert('同学你好!!')
            }
        }
    })
script>

事件修饰符

Vue中的事件修饰符:
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式;
5.self:只有event.target是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>事件修饰符title>
    <script type="text/javascript" src="../js/vue.js">script>
    <style>
        * {
            margin-top: 20px;
        }

        .demo1 {
            height: 50px;
            background-color: skyblue;
        }

        .box1 {
            padding: 5px;
            background-color: skyblue;
        }

        .box2 {
            padding: 5px;
            background-color: orange;
        }

        .list {
            width: 200px;
            height: 200px;
            background-color: peru;
            overflow: auto;
        }

        li {
            height: 100px;
        }
    style>
head>

<body>

    <div id="root">
        <h2>欢迎来到{{name}}学习h2>
        
        <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息a>

        
        <div class="demo1" @click="showInfo">
            <button @click.stop="showInfo">点我提示信息button>
            
            
        div>

        
        <button @click.once="showInfo">点我提示信息button>

        
        <div class="box1" @click.capture="showMsg(1)">
            div1
            <div class="box2" @click="showMsg(2)">
                div2
            div>
        div>

        
        <div class="demo1" @click.self="showInfo">
            <button @click="showInfo">点我提示信息button>
        div>

        
        <ul @wheel.passive="demo" class="list">
            <li>1li>
            <li>2li>
            <li>3li>
            <li>4li>
        ul>

    div>
body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el: '#root',
        data: {
            name: '尚硅谷'
        },
        methods: {
            showInfo(e) {
                alert('同学你好!')
                // console.log(e.target)
            },
            showMsg(msg) {
                console.log(msg)
            },
            demo() {
                for (let i = 0; i < 100000; i++) {
                    console.log('#')
                }
                console.log('累坏了')
            }
        }
    })
script>

html>

计算属性 computed

计算属性:
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

<div id="root">
    姓:<input type="text" v-model="firstName"> <br /><br />
    名:<input type="text" v-model="lastName"> <br /><br />
    全名:<span>{{fullName}}span> <br /><br />
div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三',
        },
        computed: {
            //完整写法
            /* fullName:{
                get(){
                    console.log('get被调用了')
                    return this.firstName + '-' + this.lastName
                },
                set(value){
                    console.log('set',value)
                    const arr = value.split('-')
                    this.firstName = arr[0]
                    this.lastName = arr[1]
                }
            } */
            //简写
            fullName() {
                console.log('get被调用了')
                return this.firstName + '-' + this.lastName
            }
        }
    })
script>

监视(侦听)属性 watch

immediate 初始化调用
deep 深度监视

几种写法

<body>
    <div id="root">
        <h2>今天天气很{{info}}h2>
        <button @click="changeWeather">切换天气button>
    div>
body>
<script type="text/javascript">
    const vm = new Vue({
        el: '#root',
        data: {
            isHot: true,
        },
        computed: {
            info() {
                return this.isHot ? '炎热' : '凉爽'
            }
        },
        methods: {
            changeWeather() {
                this.isHot = !this.isHot
            }
        },
        watch: {
            //正常写法
            /* isHot:{
                // immediate:true, //初始化时让handler调用一下
                // deep:true,//深度监视
                handler(newValue,oldValue){
                    console.log('isHot被修改了',newValue,oldValue)
                }
            }, */
            //简写
            /* isHot(newValue,oldValue){
                console.log('isHot被修改了',newValue,oldValue,this)
            } */
        }
    })

		//正常写法
    /* vm.$watch('isHot',{
        immediate:true, //初始化时让handler调用一下
        deep:true,//深度监视
        handler(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue)
        }
    }) */

		//简写
    /* vm.$watch('isHot',(newValue,oldValue)=>{
        console.log('isHot被修改了',newValue,oldValue,this)
    }) */

script>

绑定样式 class与style

绑定样式:

  1. class样式
    写法:class=“xxx” xxx可以是字符串、对象、数组。
    字符串写法适用于:类名不确定,要动态获取。
    对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
    数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
  2. style样式
    :style="{fontSize: xxx}“其中xxx是动态值。
    :style=”[a,b]"其中a、b是样式对象。
<style>
    .basic {
        width: 400px;
        height: 100px;
        border: 1px solid black;
    }

    .happy {
        border: 4px solid red;
        ;
        background-color: rgba(255, 255, 0, 0.644);
        background: linear-gradient(30deg, yellow, pink, orange, yellow);
    }

    .sad {
        border: 4px dashed rgb(2, 197, 2);
        background-color: gray;
    }

    .normal {
        background-color: skyblue;
    }

    .atguigu1 {
        background-color: yellowgreen;
    }

    .atguigu2 {
        font-size: 30px;
        text-shadow: 2px 2px 10px red;
    }

    .atguigu3 {
        border-radius: 20px;
    }
style>

<div id="root">
    
    <div class="basic" :class="mood" @click="changeMood">{{name}}div> <br /><br />
    
    <div class="basic" :class="classArr">{{name}}div> <br /><br />
    
    <div class="basic" :class="classObj">{{name}}div> <br /><br />
    
    <div class="basic" :style="styleObj">{{name}}div> <br /><br />
    
    <div class="basic" :style="styleArr">{{name}}div>
div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            mood: 'normal',
            classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
            classObj: {
                atguigu1: false,
                atguigu2: false,
            },
            styleObj: {
                fontSize: '40px',
                color: 'red',
            },
            styleObj2: {
                backgroundColor: 'orange'
            },
            styleArr: [
                {
                    fontSize: '40px',
                    color: 'blue',
                },
                {
                    backgroundColor: 'gray'
                }
            ]
        },
        methods: {
            changeMood() {
                const arr = ['happy', 'sad', 'normal']
                const index = Math.floor(Math.random() * 3)
                this.mood = arr[index]
            }
        },
    })
script>

条件渲染 v-if 与 v-show

v-if 会销毁 v-show 只是隐藏

条件渲染:
1.v-if
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。

2.v-show
写法:v-show=“表达式”
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。

<div id="root">
    <h2>当前的n值是:{{n}}h2>
    <button @click="n++">点我n+1button>
    
    
    

    
    
    

    
    
            
    
    <template v-if="n === 1">
        <h2>你好h2>
        <h2>尚硅谷h2>
        <h2>北京h2>
    template>

div>


<script type="text/javascript">
    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            n: 0
        }
    })
script>

列表渲染 v-for

v-for指令:
1.用于展示列表数据
2.语法:v-for=“(item, index) in xxx” :key=“yyy”
3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

基本列表

<div id="root">
    
    <h2>人员列表(遍历数组)h2>
    <ul>
        <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}
        li>
    ul>

    
    <h2>汽车信息(遍历对象)h2>
    <ul>
        <li v-for="(value,k) of car" :key="k">
            {{k}}-{{value}}
        li>
    ul>

    
    <h2>测试遍历字符串(用得少)h2>
    <ul>
        <li v-for="(char,index) of str" :key="index">
            {{char}}-{{index}}
        li>
    ul>

    
    <h2>测试遍历指定次数(用得少)h2>
    <ul>
        <li v-for="(number,index) of 5" :key="index">
            {{index}}-{{number}}
        li>
    ul>
div>

<script type="text/javascript">
    new Vue({
        el: '#root',
        data: {
            persons: [
                { id: '001', name: '张三', age: 18 },
                { id: '002', name: '李四', age: 19 },
                { id: '003', name: '王五', age: 20 }
            ],
            car: {
                name: '奥迪A8',
                price: '70万',
                color: '黑色'
            },
            str: 'hello'
        }
    })
script>

key的原理(这块不太懂)

面试题:react、vue中的key有什么作用?(key的内部原理)

  1. 虚拟DOM中key的作用:
    key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
    随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

  2. 对比规则:
    (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
    ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
    ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

  1. 用index作为key可能会引发的问题:
    (1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:
    会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
    (2)如果结构中还包含输入类的DOM:
    会产生错误DOM更新 ==> 界面有问题。

  2. 开发中如何选择key?:
    (1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
    (2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
    使用index作为key是没有问题的。

DOCTYPE html>
<div id="root">
    
    <h2>人员列表(遍历数组)h2>
    <button @click.once="add">添加一个老刘button>
    <ul>
        <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}
            <input type="text">
        li>
    ul>
div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el: '#root',
        data: {
            persons: [
                { id: '001', name: '张三', age: 18 },
                { id: '002', name: '李四', age: 19 },
                { id: '003', name: '王五', age: 20 }
            ]
        },
        methods: {
            add() {
                const p = { id: '004', name: '老刘', age: 40 }
                this.persons.unshift(p)
            }
        },
    })
script>

列表过滤

<div id="root">
    <h2>人员列表h2>
    <input type="text" placeholder="请输入名字" v-model="keyWord">
    <ul>
        <li v-for="(p,index) of filPerons" :key="index">
            {{p.name}}-{{p.age}}-{{p.sex}}
        li>
    ul>
div>

<script type="text/javascript">
    Vue.config.productionTip = false

    //用watch实现
    //#region 
    /* new Vue({
        el:'#root',
        data:{
            keyWord:'',
            persons:[
                {id:'001',name:'马冬梅',age:19,sex:'女'},
                {id:'002',name:'周冬雨',age:20,sex:'女'},
                {id:'003',name:'周杰伦',age:21,sex:'男'},
                {id:'004',name:'温兆伦',age:22,sex:'男'}
            ],
            filPerons:[]
        },
        watch:{
            keyWord:{
                immediate:true,
                handler(val){
                    this.filPerons = this.persons.filter((p)=>{
                        return p.name.indexOf(val) !== -1
                    })
                }
            }
        }
    }) */
    //#endregion

    //用computed实现
    new Vue({
        el: '#root',
        data: {
            keyWord: '',
            persons: [
                { id: '001', name: '马冬梅', age: 19, sex: '女' },
                { id: '002', name: '周冬雨', age: 20, sex: '女' },
                { id: '003', name: '周杰伦', age: 21, sex: '男' },
                { id: '004', name: '温兆伦', age: 22, sex: '男' }
            ]
        },
        computed: {
            filPerons() {
                return this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1
                })
            }
        }
    }) 
script>

列表排序

<div id="root">
    <h2>人员列表h2>
    <input type="text" placeholder="请输入名字" v-model="keyWord">
    <button @click="sortType = 2">年龄升序button>
    <button @click="sortType = 1">年龄降序button>
    <button @click="sortType = 0">原顺序button>
    <ul>
        <li v-for="(p,index) of filPerons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
            <input type="text">
        li>
    ul>
div>

<script type="text/javascript">
    new Vue({
        el: '#root',
        data: {
            keyWord: '',
            sortType: 0, //0原顺序 1降序 2升序
            persons: [
                { id: '001', name: '马冬梅', age: 30, sex: '女' },
                { id: '002', name: '周冬雨', age: 31, sex: '女' },
                { id: '003', name: '周杰伦', age: 18, sex: '男' },
                { id: '004', name: '温兆伦', age: 19, sex: '男' }
            ]
        },
        computed: {
            filPerons() {
                const arr = this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1
                })
                //判断一下是否需要排序
                if (this.sortType) {
                    arr.sort((p1, p2) => {
                        return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
                    })
                }
                return arr
            }
        }
    })
script>

数据监测

Vue监视数据的原理:

  1. vue会监视data中所有层次的数据。

  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue时就传入要监测的数据。
    (1).对象中后追加的属性,Vue默认不做响应式处理
    (2).如需给后添加的属性做响应式,请使用如下API:
    Vue.set(target,propertyName/index,value) 或
    vm.$set(target,propertyName/index,value)

  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事:
    (1).调用原生对应的方法对数组进行更新。
    (2).重新解析模板,进而更新页面。

  4. 在Vue修改数组中的某个元素一定要用如下方法:
    1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    2.Vue.set() 或 vm.$set()

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

<div id="root">
    <h1>学生信息h1>
    <button @click="student.age++">年龄+1岁button> <br />
    <button @click="addSex">添加性别属性,默认值:男button> <br />
    <button @click="student.sex = '未知' ">修改性别button> <br />
    <button @click="addFriend">在列表首位添加一个朋友button> <br />
    <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三button> <br />
    <button @click="addHobby">添加一个爱好button> <br />
    <button @click="updateHobby">修改第一个爱好为:开车button> <br />
    <button @click="removeSmoke">过滤掉爱好中的抽烟button> <br />
    <h3>姓名:{{student.name}}h3>
    <h3>年龄:{{student.age}}h3>
    <h3 v-if="student.sex">性别:{{student.sex}}h3>
    <h3>爱好:h3>
    <ul>
        <li v-for="(h,index) in student.hobby" :key="index">
            {{h}}
        li>
    ul>
    <h3>朋友们:h3>
    <ul>
        <li v-for="(f,index) in student.friends" :key="index">
            {{f.name}}--{{f.age}}
        li>
    ul>
div>

<script type="text/javascript">
    const vm = new Vue({
        el: '#root',
        data: {
            student: {
                name: 'tom',
                age: 18,
                hobby: ['抽烟', '喝酒', '烫头'],
                friends: [
                    { name: 'jerry', age: 35 },
                    { name: 'tony', age: 36 }
                ]
            }
        },
        methods: {
            addSex() {
                // Vue.set(this.student,'sex','男')
                this.$set(this.student, 'sex', '男')
            },
            addFriend() {
                this.student.friends.unshift({ name: 'jack', age: 70 })
            },
            updateFirstFriendName() {
                this.student.friends[0].name = '张三'
            },
            addHobby() {
                this.student.hobby.push('学习')
            },
            updateHobby() {
                // this.student.hobby.splice(0,1,'开车')
                // Vue.set(this.student.hobby,0,'开车')
                this.$set(this.student.hobby, 0, '开车')
            },
            removeSmoke() {
                this.student.hobby = this.student.hobby.filter((h) => {
                    return h !== '抽烟'
                })
            }
        }
    })
script>

收集表单数据

表单:form 作为元素的呈现,一个结构,通过ajax把数据发给后端
ajax优势:页面不刷新获取数据,页面不刷新把数据送走

收集表单数据:
若:,则v-model收集的是value值,用户输入的就是value值。
若:,则v-model收集的是value值,且要给标签配置value值。
若:
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤

<div id="root">
    <form @submit.prevent="demo">
        账号:<input type="text" v-model.trim="userInfo.account"> <br /><br />
        密码:<input type="password" v-model="userInfo.password"> <br /><br />
        年龄:<input type="number" v-model.number="userInfo.age"> <br /><br />
        性别:
        男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br /><br />
        爱好:
        学习<input type="checkbox" v-model="userInfo.hobby" value="study">
        打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
        吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
        <br /><br />
        所属校区
        <select v-model="userInfo.city">
            <option value="">请选择校区option>
            <option value="beijing">北京option>
            <option value="shanghai">上海option>
            <option value="shenzhen">深圳option>
            <option value="wuhan">武汉option>
        select>
        <br /><br />
        其他信息:
        <textarea v-model.lazy="userInfo.other">textarea> <br /><br />
        <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》a>
        <button>提交button>
    form>
div>

<script type="text/javascript">
    new Vue({
        el: '#root',
        data: {
            userInfo: {
                account: '',
                password: '',
                age: 18,
                sex: 'female',
                hobby: [],
                city: 'beijing',
                other: '',
                agree: ''
            }
        },
        methods: {
            demo() {
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
script>

过滤器

过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据

<div id="root">
    <h2>显示格式化后的时间h2>
    
    <h3>现在是:{{fmtTime}}h3>
    
    <h3>现在是:{{getFmtTime()}}h3>
    
    <h3>现在是:{{time | timeFormater}}h3>
    
    <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}h3>
    <h3 :x="msg | mySlice">尚硅谷h3>
div>

<div id="root2">
    <h2>{{msg | mySlice}}h2>
div>

<script type="text/javascript">
    Vue.config.productionTip = false
    //全局过滤器
    Vue.filter('mySlice', function (value) {
        return value.slice(0, 4)
    })

    new Vue({
        el: '#root',
        data: {
            time: 1621561377603, //时间戳
            msg: '你好,尚硅谷'
        },
        computed: {
            fmtTime() {
                return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
        },
        methods: {
            getFmtTime() {
                return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
        },
        //局部过滤器
        filters: {
            timeFormater(value, str = 'YYYY年MM月DD日 HH:mm:ss') {
                // console.log('@',value)
                return dayjs(value).format(str)
            }
        }
    })

    new Vue({
        el: '#root2',
        data: {
            msg: 'hello,atguigu!'
        }
    })
script>

内置指令

<style>
    [v-cloak] {
        display: none;
    }
style>

<div id="root">
    <h2>ZTEh2>
    <h2>{{name}}h2>
    <h2 v-text="name">helloh2>

    <h2>"<h3>shanghaih3>"h2>
    <h2>{{city}}h2>
    <h2 v-html="city">h2>

    <h2 v-cloak>{{name}}h2>

    <h2 v-once>初始化的n值是:{{n}}h2>
    <h2>当前的n值是:{{n}}h2>
    <button @click="n++">点我n+1button>

    <h2>{{name}}h2>
    <h2 v-pre>{{name}}h2>
div>
<script>
    new Vue({
        el: '#root',
        data: {
            name: "ZTE",
            city: "

shanghai

"
, n: 1 }, })
script>

自定义指令

需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。

自定义指令总结:
一、定义语法:
(1).局部指令:
new Vue({directives:{指令名:配置对象})
new Vue({directives{指令名:回调函数}})
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。

三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

//定义全局指令
/* Vue.directive('fbind',{
    //指令与元素成功绑定时(一上来)
    bind(element,binding){
        element.value = binding.value
    },
    //指令所在元素被插入页面时
    inserted(element,binding){
        element.focus()
    },
    //指令所在的模板被重新解析时
    update(element,binding){
        element.value = binding.value
    }
}) */

<div id="root">
    <h2>{{name}}h2>
    <h2>当前的n值是:<span v-text="n">span> h2>
    
    <h2>放大10倍后的n值是:<span v-big="n">span> h2>
    <button @click="n++">点我n+1button>
    <input type="text" v-fbind:value="n">
div>


<script type="text/javascript">
    //定义全局指令
    /* Vue.directive('fbind',{
        //指令与元素成功绑定时(一上来)
        bind(element,binding){
            element.value = binding.value
        },
        //指令所在元素被插入页面时
        inserted(element,binding){
            element.focus()
        },
        //指令所在的模板被重新解析时
        update(element,binding){
            element.value = binding.value
        }
    }) */

    new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            n: 1
        },
        directives: {
            //big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
            /* 'big-number'(element,binding){
                // console.log('big')
                element.innerText = binding.value * 10
            }, */
            big(element, binding) {
                console.log(element, binding);
                console.log('big', this) //注意此处的this是window
                // console.log('big')
                element.innerText = binding.value * 10
            },
            fbind: {
                //指令与元素成功绑定时(一上来)
                bind(element, binding) {
                    element.value = binding.value
                },
                //指令所在元素被插入页面时
                inserted(element, binding) {
                    element.focus()
                },
                //指令所在的模板被重新解析时
                update(element, binding) {
                    element.value = binding.value
                }
            }
        }
    })
script>

生命周期

8个

beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlLJcATI-1693138193624)(vx_images/430224174948648.jpg)]

组件化编程

对应视频教程:P53

存在的问题:
1.依赖关系混乱,不好维护
2.代码复用率不高

2.1 模块与组件、模块化与组件化

组件的基本使用

Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)

		一、如何定义一个组件?
					使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
					区别如下:
							1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
							2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
					备注:使用template可以配置组件结构。s

		二、如何注册组件?
						1.局部注册:靠new Vue的时候传入components选项
						2.全局注册:靠Vue.component('组件名',组件)

		三、编写组件标签:
						

2.2 非单文件组件

一个文件中包含有n个组件

2.3 单文件组件

一个文件中只包含有1个组件 a.vue

一个重要的内置关系

二、vue脚手架

vue-cli vite

脚手架常用配置

main.js


lintOnSave:false, //关闭语法检查

3.1 初始化脚手架

3.1.1说明

vue脚手架是vue官方提供的标准化开发工具(开发平台)

新建一个项目

cd [指定目录]
vue create [项目名字]
cd [项目名字]
npm run serve

关于不同版本的vue:

  1. vue.js与vue.runtime.xxx.js的区别

    vue.js是完整版的vue,包含:核心功能+模板解析器

    vue.runtime.xxx.js是运行版的vue,只包含核心功能,没有模板解析器

  2. 以为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容

类比装修铺瓷砖

第一种:买瓷砖(vue核心)+买工人(模板解析器)====>铺好的瓷砖+工人

第二种:买瓷砖(vue核心)+雇工人(模板解析器)====>铺好的瓷砖

创建vue实例对象

修改默认配置

脚手架默认配置

vue.config.js

3.2 ref与props

ref属性
1.被用来给元素或子组件注册引用信息(id的替代者)
2.应用在html标签上获取的是真实dom元素,应用在组件标签上是组件实例对象(vc)
3.使用方式
打标识:

...


获取:this.$refs.xxx

props

组合式api访问参数

    props: ['addStep'],
    setup(props) {
        // console.log("props", props);
        // console.log("props", props.addStep);
        const addStep = ref(props.addStep)

3.3 混入

3.4 插件

3.5 Todo-list案例

3.6 vue中的自定义事件

3.7 全局事件总线

3.8消息订阅与发布

3.9过渡与动画

三、vuex

vuex基础知识

安装vuex
npm install vuex@3 vue2只能用vuex3
npm install vuex 或者 npm install vuex@4 vue3只能用vuex4

官网
vuex

概念
专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

什么时候用vuex
多个组件依赖于同一状态
来自不同组件的行为需要变更同一状态

vuex原理图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C88h91KM-1693138193626)(vx_images/84333353744099.png)]

核心概念和api

state

vuex管理的状态对象
它应该是唯一的

const state = {
    xxx: initValue
}

actions

  1. 值为一个对象,包含多个响应用户动作的回调函数
  2. 通过 commit( )来触发 mutation 中函数的调用, 间接更新 state
  3. 如何触发 actions 中的回调?
    在组件中使用: $store.dispatch(‘对应的 action 回调名’) 触发
  4. 可以包含异步代码(定时器, ajax 等等)
const actions = {
    zzz({commit, state}, data1){
            commit('yyy', {data1})
        }
}

mutations

  1. 值是一个对象,包含多个直接更新 state 的方法
  2. 谁能调用 mutations 中的方法?如何调用?
    在 action 中使用:commit(‘对应的 mutations 方法名’) 触发
  3. mutations 中方法的特点:不能写异步代码、只能单纯的操作 state
const mutations = {
    yyy(state, data1){
        // 更新state的某个属性
    }
}

getters

值为一个对象,包含多个用于返回数据的函数
2. 如何使用?—— $store.getters.xxx

const getters = {
	bigSum(state){
		return state.sum*10
	}
}

modules

一个store包含多个子模块

命名空间

默认情况下
state是局部命名空间
mutation、action、getter 是全局命名空间

命名空间的作用是为了减少模块之间定义的方法命名冲突的问题
比如说在a模块getter 中定义一个getter1,在b模块getter 中也定义一个getter1 ,在同一空间内,拥有两个相同的名称,程序会报错。

语法 namespaced:true 在模块定义时使用

模块的调用

state 默认就是命名空间

命名空间

调用的时候需要加上模块名
state
{{ $store.state.模块名.sum }} 或者 this.$store.state.模块名.sum
action(使用dispatch调用)
this.$store.dispatch('模块名/方法名',参数)
mutations(使用commit调用)
this.$store.commit('模块名/方法名', '参数')
getters
{{$store.getters['personAbout/firstPersonName']}}
或者
this.$store.getters['personAbout/firstPersonName']

无命名空间

调用时,state加上模块名,其他不需要加上模块名
state
{{ $store.state.模块名.sum }} 或者 this.$store.state.模块名.sum
getters
{{$store.getters['参数]}}
或者
this.$store.getters['模块名/firstPersonName']
action(使用dispatch调用)
this.$store.dispatch('方法名',参数)
mutations(使用commit调用)
this.$store.commit('方法名', '参数')

vuex使用【vue2】

创建 store/index.js

// @/store/index.js
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions——用于响应组件中的动作
const actions = {
    jiaOdd(context, value) {
        context.commit('JIA', value)
    }
}
//准备mutations——用于操作数据(state)
const mutations = {
    JIA(state, value) {
        state.sum += value
    }
}
//准备state——用于存储数据
const state = {
    sum: 0 //当前的和
}
//准备getters——用于将state中的数据进行加工
const getters = {
    bigSum(state) {
        return state.sum * 10
    }
}
//创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

main.js中应用vuex

import Vue from 'vue'
import App from './App.vue'
//引入store
import store from './store'
new Vue({
	el:'#app',
	render: h => h(App),
	store
})

模块化编码

index.js

import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引入模块
import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)
//创建并暴露store
export default new Vuex.Store({
	modules:{
		countAbout:countOptions,
		personAbout:personOptions
	}
})

count模块

// count模块
export default {
	actions: {},
	mutations: {},
	state: {},
	getters: {},
}

person模块

// person模块
export default {
	actions: {},
	mutations: {},
	state: {},
	getters: {},
}

组件中使用vuex

非模块

state
{{ $store.state.sum }} 或者 this.$store.state.sum
action(使用dispatch调用)
this.$store.dispatch('方法名字', '参数')
mutations(使用commit调用)
this.$store.commit('方法名字', '参数')
getters
{{$store.getters.bigSum}} 或者 this.$store.getters.bigSum

示例

<template>
	<div>
		<h1>当前求和为:{{ $store.state.sum }}h1>
		<h3>当前求和放大10倍为:{{ $store.getters.bigSum }}h3>
	div>
template>

<script>
export default {
	name: 'Count',
	data() {
		return { n: 1, }
	},
	methods: {
		increment() {
			this.$store.commit('JIA', this.n)
		},
		incrementOdd() {
			this.$store.dispatch('jiaOdd', this.n)
		},
	},
}
script>

模块化

state
{{ $store.state.countAbout.sum }} 或者 this.$store.state.countAbout.sum
action(使用dispatch调用)
this.$store.dispatch('模块名/方法名',参数)
mutations(使用commit调用)
this.$store.commit('模块名/方法名', '参数')
getters
{{$store.getters['personAbout/firstPersonName']}}
或者
this.$store.getters['personAbout/firstPersonName']

示例

<template>
	<div>
		<h1>人员列表h1>
		<h3 style="color:red">Count组件求和为:{{sum}}h3>
		<h3>列表中第一个人的名字是:{{firstPersonName}}h3>
		<input type="text" placeholder="请输入名字" v-model="name">
		<button @click="add">添加button>
		<button @click="addWang">添加一个姓王的人button>
		<button @click="addPersonServer">添加一个人,名字随机button>
		<ul>
			<li v-for="p in personList" :key="p.id">{{p.name}}li>
		ul>
	div>
template>

<script>
	import {nanoid} from 'nanoid'
	export default {
		name:'Person',
		data() {
			return {
				name:''
			}
		},
		computed:{
			personList(){
				return this.$store.state.personAbout.personList
			},
			sum(){
				return this.$store.state.countAbout.sum
			},
			firstPersonName(){
				return this.$store.getters['personAbout/firstPersonName']
			}
		},
		methods: {
			add(){
				const personObj = {id:nanoid(),name:this.name}
				this.$store.commit('personAbout/ADD_PERSON',personObj)
				this.name = ''
			},
			addWang(){
				const personObj = {id:nanoid(),name:this.name}
				this.$store.dispatch('personAbout/addPersonWang',personObj)
				this.name = ''
			},
			addPersonServer(){
				this.$store.dispatch('personAbout/addPersonServer')
			}
		},
	}
script>

vuex使用【vue3】

创建 store/index.js

import { createStore } from 'vuex';
export default createStore({
    actions: {},
	mutations: {},
	state: {},
	getters: {},
});

main.js中应用vuex

import { createApp } from 'vue'
import { createStore } from 'vuex'
//引入store
import store from './store'

const app = createApp({ /* 根组件 */ })
// 将 store 实例作为插件安装
app.use(store)

模块化编码

index.js

import { createStore } from 'vuex';
//引入模块
import countOptions from './count'
import personOptions from './person'

//创建并暴露store
export default createStore({
	modules:{
		countAbout:countOptions,
		personAbout:personOptions
	}
})

count模块

// count模块
export default {
	actions: {},
	mutations: {},
	state: {},
	getters: {},
}

person模块

// person模块
export default {
	actions: {},
	mutations: {},
	state: {},
	getters: {},
}

组件中使用vuex

非组合式API访问与vue2一致,这里展示的是组合API访问

可以通过调用 useStore 函数,来在 setup 钩子函数中访问 store。这与在组件中使用选项式 API 访问 this.$store 是等效的

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()
  }
}

非模块

state 和 getter
为了访问 state 和 getter,需要创建 computed 引用以保留响应性,这与在选项式 API 中创建计算属性等效

import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 在 computed 函数中访问 state
      count: computed(() => store.state.count),

      // 在 computed 函数中访问 getter
      double: computed(() => store.getters.double)
    }
  }
}

Mutation 和 Action
要使用 mutation 和 action 时,只需要在 setup 钩子函数中调用 commit 和 dispatch 函数

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 使用 mutation
      increment: () => store.commit('increment'),

      // 使用 action
      asyncIncrement: () => store.dispatch('asyncIncrement')
    }
  }
}

模块化

action方法调用,不用写子模块名字 store.dispatch(“方法名”)
state参数调用,需要写子模块名字 store.$state.模块名.参数

四、vue中的ajax

解决开发环境ajax跨域问题
使用代理服务器

vue项目中常用的2个ajax库
axios
通用的 Ajax 请求库, 官方推荐,使用广泛

vue-resource
一个非常轻量的用于处理HTTP请求的插件。Vue2.0之后,就不再对vue-resource更新,而是推荐使用axios

axios

https://github.com/axios/axios#axios-api

安装
npm i axios

使用

示例

// Send a POST request
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

axios({
  method: 'get',
  url: 'https://bit.ly/2mTM3nY'
})

axios.get(url).then(
    response => {
        console.log('请求成功了', response)
    },
    error => {
        console.log('请求失败了', error)
    }
)

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

五、slot插槽

六、vue-router

安装

npm install vue-router@3
npm install vue-router@4
注:vue-router4只能在vue3中使用

配置

6.1相关理解

vue 的一个插件库,专门用来实现 SPA 的

SPA的理解

  1. 单页 Web 应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面。
  3. 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
  4. 数据需要通过 ajax 请求获

6.2基本路由

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由器
import router from './router'

//关闭Vue的生产提示
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	router:router
})


About
Home
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'

//创建并暴露一个路由器
export default new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home,
			children:[
				{
					path:'news',
					component:News,
				},
				{
					path:'message',
					component:Message,
				}
			]
		}
	]
})

6.3嵌套(多级路由)

6.4路由传参

6.5编程式路由导航

相关 API:

  1. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
  2. this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
  3. this.$router.back(): 请求(返回)上一个记录路由
  4. this.$router.go(-1): 请求(返回)上一个记录路由
  5. this.$router.go(1): 请求下一个记录路由

七、UI组件库

掌握:
安装部署、完整引入和部分引入、本地搭建

注:如果访问网络不给力,可以下载到本地,借助Nginx本地搭建服务

常用UI组件库

移动端常用UI组件库

Vant
https://youzan.github.io/vant
Cube UI
https://didi.github.io/cube-ui
Mint UI
http://mint-ui.github.io

PC端常用UI组件库

ElementPlus 基于vue3
https://element-plus.gitee.io/zh-CN/
ElementUI 基于vue2
https://element.eleme.cn/#/zh-CN
IViewUI
https://www.iviewui.com/

ElementUI vue2.0

安装和引入

安装

npm i element-ui -S

完整引入

在 main.js 中写入以下内容:

import Vue from 'vue';
import App from './App.vue';
//完整引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
//完整引入应用插件
Vue.use(ElementUI);

new Vue({
  el: '#app',
  render: h => h(App)
});

按需引入

注意事项:

  1. 如果后续运行过程中报错,出现少包的情况,直接执行npm install [包名] 安装所需要的包

  2. “presets”: [[“es2015”, { “modules”: false }]] 修改为 “presets”: [[“@babel/preset-env”, { “modules”: false }]]

安装

npm install babel-plugin-component -D

修改配置文件babel.config.js

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
import Vue from 'vue'
import App from './App.vue'
// 按需引入
import { Button, Row, DatePicker } from 'element-ui';
// 按需引入应用插件
Vue.component(Button.name, Button);
Vue.component(Row.name, Row);
Vue.component(DatePicker.name, DatePicker);
Vue.config.productionTip = false

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

ElementPlus vue3.0

安装和引入

npm install element-plus --save

引入element-plus

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

常见问题

表格样式不生效问题

不要使用scoped样式

数据量过大的问题

数据量过大,导致前端组件渲染时间过长,卡死

其他

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": false,
    "jsx": "preserve",
    "moduleResolution": "node"
  }
}

问题记录

less-loader 安装失败问题
原因:版本匹配问题
解决:npm install [email protected] [email protected]

elementplus样式调整问题

表头固定问题

表格固定表头,需要设置高度,但是表格内容会随着每页显示条目而变化,如何动态计算表格高度

表格选择每页条数弹出框的样式调整问题

这个弹出框的样式不好调整,怎么处理和解决

Vue3组合式API之getCurrentInstance

参考:https://www.jb51.net/article/263720.htm

getCurrentInstance只能在setup或生命周期钩子中使用

你可能感兴趣的:(vue.js,前端,javascript)