Vue

笔记:vue基础 · 语雀

代码:vue基础阶段代码

官网:Vue.js(作者: 尤雨溪)

组件库

Vant2 

      Vant 2 - Mobile UI Components built on Vue

      Vant 3 - Mobile UI Components built on Vue

elementui  Element - The world's most popular Vue UI framework

一、Vue介绍

1、学习前的工具安装

插件名称作用

chrome插件

vue_devtools

可以检测网站是不是Vue开发,如果是图标会亮,如果不是图标是灰暗; 

可以在开发阶段帮助我们调试数据;

安装方法:三点 → 更多工具 → 扩展程序 → 打开'开发者模式' → 将插件拖入其中(安装完成) → 工具栏中的'扩展程序',将其固定

vscode插件

vetur

让vscode能够识别 .vue文件;

还提供了一些vue代码的格式化功能,以及一些简单的代码段;

vue 3  snippets

(选装)

提供更多的vue的代码段

2、Vue及其作用

概念:Vue是一套渐进式的JavaScript框架;

作用:开发快捷;

什么是框架?

内部做了高度封装,并提供了自己的一套语法和开发体系,用框架可能会改变原本的开发模式;

3、MVVM设计模式

二、Vue的使用方法

1、举例

<body>
    <div class="box">
        <p>shy</p>
3. 插值语法, 将数据放入页面中
        <p>{{msg}}</p>
    </div>

1. 引入vue.js文件
    <script src="./vue-2.6.12.js"></script>
    <script>
2. 实例化Vue对象
        new Vue({
      2.1 把.box的元素交给vue来管理,可以传任意的css选择器 
          或 new Vue 最后加上 .$mount('.box')
            el: '.box',
      2.2 data是vue里放数据的地方,html中这样写,vue文件中的data是函数
            data: {
                属性名: 属性值,
                msg: '中国',
            }
      2.3 methods是vue里放方法的地方
            methods: {
                函数名() {函数体},
            },
      2.4 过滤器
            filters: { 
                过滤器名字 (原数据) {
                    代码对数据进行操作
                    return 新结果
                },
            },
      2.5 计算属性
            computed: {      
                计算属性名 () {                // 函数的写法
                    经过一系列计算,得到结果
                    return 结果
                }
            },
      2.6 侦听器
            watch: {
                要监测的数据 () {
                    监测的数据变换了,进行的操作
                }
            }
        })
    </script>
</body>

2、插值语法

语法:{{vue的数据}}

注意:括号里可以写vue里的数据或任意的表达式,但不能写语句
例:
    <p>{{ msg }}</p>
    <p>{{ msg + ',998' }}</p>
    <p>{{ age >= 18 ? '成年' : '未成年' }}</p>

三、指令

指令是Vue提供的一套具有特殊功能的行内属性,一般是 v- 开头;

内置指令

1、v-text和v-html

v-text

不会解析标签,只是当纯文本(类似于 innerText)

v-html

会解析标签,会解析成dom元素(类似于 innerHTML)

共同特点:都是用来设置双标签里显示的内容,都覆盖原本的内容
v-text和插值语法的区别:
    v-text 不会解析标签,但会覆盖原来的内容;
    插值语法 不会解析标签,并且会保留原本的内容,它只会把写{{}}的地方替换成最终数据;
        <div v-text="msg">原本的内容1</div>
        <div>原本的内容2--{{msg}}</div>

2、v-on(绑定事件)

(1)v-on的使用方法

作用:vue里面绑定元素的事件,用 v-on 指令 
语法:
    <标签 v-on:事件名="函数名/函数体"/>  如果事件函数的代码只有一句话,那么可以把这句话直接写到行内
    <标签 v-on:事件名="一句代码" />
简写语法:将 v-on 换成 @
    <标签 @事件名="函数"/>
    <标签 @事件名="一句代码"/>

举例:<button v-on:click="fn1">内容</button>          点击,调用fn1函数
     <button @click="msg = '鸡杀马'">内容</button>    点击,给数据中的msg设置值
    注意:在JS中访问Vue里的数据,前面要加this,在HTML中Vue里的数据,前面不用加this

 (2)Vue里的this

Vue里的this,指的是当前它所在的Vue实例,只不过Vue实例在创建后的某个阶段里,会把所有data中的数据和methods中的所有方法,全部挂载到这个实例的最外层,所以我们可以直接通过this来调用;

this.数据名
this.方法名()

fn() {
    this.msg='SHY',
    this.fn1()
}

(3) vue里事件对象($event)

总结:

  • $event 在原生标签中,代表事件对象;
  • $event 在组件中,代表组件传递过来的数据;
  • 可以直接把子组件传递过来的数据进行赋值;
  • <son @getMsg="msg = $event"/>    将子组件传过来的数据赋值给父组件的msg变量
原生代码(APP.vue本身):
1. 在Vue中,如果事件绑定的函数没加小括号,这个参数就是事件对象
    <button @click="fn1">按钮</button>
    fn1 (e) {
           console.log(e)    // 事件对象
    }

2. 在Vue中,如果事件绑定时加了小括号,就代表自己要指定传参数,此时你传什么,参数就是什么
    2.1 如果加了小括号,但并没传实参,那么fn2里函数的参数的返回值是 undefined
        <button @click="fn2()">按钮</button>
        fn2 (e) {
           console.log(e)    // undefined
        }

    2.2 如果加了小括号,参数里传的是hello,那么fn3里函数的参数,就是一个 hello
        <button @click="fn3('hello')">按钮</button>
        fn3 (e) {
           console.log(e)    // hello
        }

    2.3 既想自己传参,又想拿到事件对象,就用 $event 代表事件对象
        <button @click="fn4('hello', $event)">加了小括号拿事件对象</button>
        fn4 (a, b) {
           console.log(a)    // hello
           console.log(b)    // 事件对象e
        }
使用组件时(写在APP.vue中):
1. 在Vue中,如果事件绑定的函数没加小括号,这个参数就是子组件传递过来的数据
    <son @changeMsg="fn1" />
    fn1 (e) {
           console.log(e)    // 子组件传递过来的数据
    }

2. 在Vue中,如果事件绑定时加了小括号,就代表自己要指定传参数,此时你传什么,参数就是什么
    2.1 如果加了小括号,但并没传实参,那么fn2里函数的参数的返回值是 undefined
        <son @changeMsg="fn1()" />
        fn2 (e) {
           console.log(e)    // undefined
        }

    2.2 如果加了小括号,参数里传的是hello,那么fn3里函数的参数,就是一个 hello
        <son @changeMsg="fn1(hello)" />
        fn3 (e) {
           console.log(e)    // hello
        }

    2.3 $event 代表 子组件传递过来的数据
        <son @changeMsg="fn1($event)" />
        fn4 (e) {
           console.log(e)    // 子组件传递过来的数据
        }

        可以直接把子组件传递过来的数据进行赋值 
        <son @getMsg="msg = $event"/>

(4)事件修饰符

在vue中很多事件对象的功能,可通过事件修饰符就能实现;

写在事件类型后面即可,并且这些修饰符可以连在一起用,没有顺序之分

事件修饰符作用
.prevent

阻止默认行为

.stop

阻止冒泡

.once

代表绑定的事件只触发一次

.self

代表事件只能由自身触发,不能由冒泡触发

若加在父元素身上,其子元素的事件都会有.self效果

例:<button @click.stop="fn2">点我</button>         写在事件类型后面
    <button @click.stop.once="fn3">点我</button>    链接写法,once代表这个事件绑定只绑定了一次,那么后面再点会连同stop的效果也会没了

3、v-bind(动态绑定行内属性)

作用: 想让标签内哪个属性不写死,就可以用v-bind进行绑定
语法  <标签 v-bind:属性名="vue里的数据"/>
简写  <标签 :属性名="vue里的数据"/>   // 省略 v-bind

例:a标签中的href或img中的src 随数据中的 url/pic 改变而改变
    <a v-bind:href="url" target="_blank">点我跳转</a>  
    <img v-bind:src="pic">

4、v-model(双向绑定)

作用:v-model是用来获取/设置表单元素(input/select/textarea)的内容,除了获取也可以设置;
     (界面变了,数据会跟着变; 数据变了,界面也能跟着变)
语法:<表单标签 v-model="vue里的数据">

例:<input type="text" v-model="msg">  input里的内容修改时,数据中的msg也会改变;
                                       数据中的msg修改时,input里的内容也会改变

(1)v-model的修饰符

语法:<表单标签 v-model.修饰符="vue里的数据">
 v-model的修饰符作用
.number

输入框默认输入的内容,拿到的都是字符串;

用了.number 把输入的内容自动转成数字类型,如果不能转,输入的是什么就拿到什么;

.trim

去除输入内容的两边空格

.lazy

输入完成后,才会让数据改变

可以同时使用多个修饰符

5、v-for(循环生成标签)

作用:循环/遍历(数组、对象)生成标签;
语法:
    <标签 v-for="v in 数字"></标签>
    <标签 v-for="(元素,下标) in 数组"></标签>      只取下标时,需要占位
    <标签 v-for="(属性值,属性名) in 对象"></标签>   只有1个值时不用加小括号
例:
    <div v-for="v in 9">haha -- {{ v }}</div>                
        会循环生成9个div, v就是从1到9的数字, 标签中的内容除了v 其它的都一样
    <li v-for="(item, index) in list">{{ item }} -- {{ index }}</li>   
        遍历list数组,这个数组有多少个元素就会产生多少个li;
        item:就是被遍历到的每个元素,index:就是被遍历到的下标(名字不一定要叫item和index)
    <li v-for="(val,key) in obj"> {{ val }} -- {{ key }}</li>
        遍历obj这个对象,对象有多少个属性,就会生成多少个li;
        val就是每一个被遍历到的属性值,key就是每一个被遍历到的属性名
        (不一定要叫val和key,也可以叫别的名字,但是第一个一定是属性值,第二个一定是属性名)

6、v-show与v-if

语法:
    <标签 v-show="数据"></标签>
    <标签 v-if="数据"></标签>
例:
    <p v-show="isShow">我是v-show控制</p>
    <p v-if="isShow">我是v-if控制</p>
作用(相同点)不同点
v-show

都是用来控制一个元素是显示还是隐藏

1. v-show 是通过控制css中的 display 属性来达到看的到或者看不到的效果,所以要频繁控制显示和隐藏,用 v-show

2. v-show没有双分支或多分支;

v-if

1. v-if 是通过直接操作dom树的添加或移除来达到看到或者看不到的效果,所以如果要一次性决定有或者没有某个元素,就可以用 v-if;(如,VIP权力)

2. v-if有双分支和多分支(v-if / v-else-if / v-else)

 v-if的双分支和多分支

举例:(根据输入的内容显示对应的div)
1. 双分支
    <input type="text" placeholder="请输入年龄" v-model.number="age">  // 双向绑定
    <div v-if="age >= 18">去网吧偷耳机</div>
    <div v-else>去公园捡垃圾</div>
2. 多分支
    <input type="text" placeholder="请输入班长有多少钱" v-model.number="money">
    <div v-if="money >= 3000">按背</div>
    <div v-else-if="money >= 2000">洗脚</div>
    <div v-else-if="money >= 1000">吃饭</div>
    <div v-else-if="money >= 500">网吧</div>
    <div v-else>扫地</div>

自定义指令

1、全局自定义指令

写在 main.js 中,在 new Vue() 之前定义全局自定义指令
写法:Vue.directive('自定义指令名称', { 配置 })
例:
Vue.directive('color', {
  bind(el,binding) {                     // 只执行一次
        el.style.color = 'red'   或
        el.style.color = binding.value
  },
  update(el,binding) {                   // 当数据变化后,更新DOM,会调用update函数
        el.style.color = 'red'   或
        el.style.color = binding.value
  },
})

当bind和update里要执行的代码一模一样,可以简写
Vue.directive('color', (el, obj) => { el.style.color = obj.value; })

2、 私有自定义指令

directives: {    // 用来声明自定义指令,与data同级
    指令名: {
        bind (el, obj) {    // 当绑定指令的dom渲染出来时就会立即执行的函数,就执行1次
            在函数内设置该指令的效果
            参数1:绑定指令的元素
            参数2:是一个对象,里面有指令的相关信息,其中这个对象的value属性可以拿到指令绑定的数据
        },
          
        update(el, obj) {    // 本指令绑定的数据发生改变就来调用,绑定的数据指的是<h1 v-color="color">中国</h1>中的变量color
            当绑定的数据发生变化后执行的代码
        },
    }
}

当bind和update里要执行的代码一模一样,可以简写
directives: { 
    指令名 (el, obj) {    // 就相当于不管是bind的时机还是update时机,都是调用此函数
        设置的指令效果的代码
    }
}
举例:自定义属性 v-color(作用:给标签更改颜色;
使用方法:就给某个标签加 v-color 即可使用这个指令)<div v-color="msg">

directives: {
    color: {
        bind (el, obj) {
            console.log(el,obj)    // el为 <h1 style="color: red;">中国</h1>
                                   // obj为 {name: 'color', rawName: 'v-color', value: 'red', expression: 'color', modifiers: {…}, …}
            el.style.color = obj.value    // 把obj.value赋值给标签的样式,obj.value拿到的是msg的值
        },
        update(el, obj) {
            console.log(el, obj)
            el.style.color = obj.value;    // 把修改后的值重新赋值给标签的样式
        },
    }
}

因为bind和update里要执行的代码一模一样,所以可以简写
就相当于不管是bind的时机还是update时机,都是调用此函数
directives: {
    color (el, obj) {
      el.style.color = obj.value 
    }
}

三、动态样式及类

1、动态style

作用:让行内样式不写死,根据数据来得到最终效果;

利用 v-bind 绑定 style 属性 
    <标签 :style="{样式名1: 数据1,样式名2: 数据2}"></标签>
    <div :style="{backgroundColor: red, color}">我是div</div>

 注意:

  • 如果样式名带 -,那么去掉 -并把-后面的首字母大写;
  • 如果要绑定多个样式,用逗号隔开;
  • 若样式名和数据值一样时,根据es6简写,可以简写为样式名(color:color)

2、动态class

作用:动态绑定类名(class),让类名不写死,来实现样式的切换;

<标签 :class="{类名1:布尔值1,类名2:布尔值2}"></标签>
<div class="box" :class="{ red: isRed, op: true }">我是div</div>
<td :class="{red: item.price >= 100}">{{ item.price }}</td>

    1. 当布尔值为true就有这个类,为false就没有这个类;
    2. 布尔值写成比较表达式

注意:

  •  可以写死一些类,然后可以再动态绑定一些类,他们并不冲突,可以同时使用;
  • 如果有多个动态类,那么逗号隔开;

四、Vue过滤器和计算属性

过滤器(filters)、计算属性(computed)跟data、methods这些是平级的

(1)Vue过滤器

作用:对某个数据做处理,得到一个新的结果;

1. 声明一个过滤器
    filters: { 
        过滤器名字 (参数) {        // 参数用来接收使用过滤器是的原数据
            代码对 参数(原数据) 进行处理
            return 新结果
        },
    }

2. 使用过滤器   含义:将原数据交给过滤器处理,会从左往右依次调用过滤器,但是最终显示的结果,一定是最后一个过滤器的结果
    <标签> {{ 原数据 | 过滤器名字1 | 过滤器名字2 }} </标签>
例子:
<body>
    <div id="app">
        <!-- 把hobby先交给sing去处理得到一个结果,
            再把结果又交给jump去处理,得到最终结果,
            最终显示的结果一定是最后一个过滤器的结果 -->
        <p>{{ hobby | sing | jump | rap }}</p>   结果:我的兴趣是:唱,跳,rap
    </div>

    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                hobby: '我的兴趣是:'
            },
            // 写过滤器的地方
            filters: {
                sing (val) {
                    return val + '唱'    // 返回 原数据val和唱拼接成的字符串
                },
                jump (val) {
                    return val + ',跳'
                },
                rap (val) {
                    return val + ',rap'
                }
            }
        })
    </script>
</body>

(2)计算属性

作用:当某个数据是依赖别的数据计算得到的,就可以用计算属性;

1. 声明一个计算属性
    computed: {      
        计算属性名 () {                // 函数的写法
            经过一系列计算,得到结果
            return 结果
        }
    }
2. 使用计算属性(和普通属性的使用方法一样)
    <标签>{{计算属性名}}</标签>
例子:
// js中
computed: {
    total () {
        let sum = 0
        this.list.forEach(v => sum += v.price)
        return sum
    }
}
// html中
<span>{{ total }}</span>

计算属性有缓存

a)计算属性本质上是函数,但是它默认情况下只会调用一次,得到结果后把结果缓存起来了,后面读取计算属性,都是从缓存里取出来的;

b)当计算属性里的依赖项(计算属性里用到的 别的属性)发生变化时,就会重新调用,得到一个新结果,再把新结果缓存起来;

(3)Vue过滤器和计算属性的区别

应用场景是否有缓存
Vue过滤器 (filters)

如果界面上展示的数据不是想要的效果,就可以用过滤器来处理,它一般用来处理时间等

没缓存
计算属性 (computed)

计算属性主要用在一些需要计算得到的数据(总价、平均价)

有缓存

五、侦听器

作用:侦听某个数据的变化(如果数据变化了,就执行一些操作);

1、普通侦听

语法:(可用来侦听data里的数据或某些数组变化)
    watch: {
        要监测的数据 (变化后的值,变化前的值) {
            监测的数据变换了,进行的操作
        }
    }
例:
    watch: {
        监测msg(msg是data里的数据)是否有变化,一旦有变化就调用这个函数
        msg (newVal, oldVal) {
            console.log('msg变化了', newVal, oldVal)  // 打印出 变化后的值 和 变化前的值
        }
    }

obj.name 对象的属性
要监测的数据是数据、对象(指向,地址不同)

2、侦听对象

new Vue({
    el: '#app',
    data: {
        obj: {
            name: 'jack',
            age: 16
         }
    },
    watch: {
     1. 侦听对象本身时,是侦听不到他里面属性(obj.name)的变化(因为对象是复杂类型,指向的是地址)
        当对象的指向发生改变时,才会侦听到对象的变化
        obj (newVal, oldVal) {
            console.log('obj被改了', newVal, oldVal)
        },
     2. 直接写对象.属性名就能侦听这个对象的某个属性的变化
        这个是侦听的obj的name属性,不能侦听obj.age的变化
        'obj.name' (newVal, oldVal) {
            console.log('obj里的name变化了', newVal, oldVal)
        },
     3. 深度侦听,可以侦听对象(obj)里任何属性的变化
        obj: {
            deep: true,         // 开启深度侦听
            immediate: true,    // 是否页面一打开就立即调用一次handler(选写),不写默认为flase
            // 当侦听到obj里任何属性发生改变都会触发这个handler函数
            // newVal, oldVal得到的是obj的指向,返回的值是一样的
            handler (newVal, oldVal) {
            console.log('obj里有属性变化了', newVal, oldVal)
            }
         }
    }
})

3、深度侦听

作用:深度侦听可以侦听对象(obj)里任何属性的变化;

对象名: {
    deep: true,         // 开启深度侦听
    immediate: true,    // 是否页面一打开就立即调用一次handler(选写),不写默认为flase
    // 参数newVal, oldVal得到的是obj的指向,返回的值是一样的(都是修改后的指向),所以参数可以省略
    handler () {
        当侦听到obj里任何属性发生改变都会调用这个handler函数
    }
}

4、侦听数组

如果调用的是以下方法(修改原数组的方法),那么即是用一般侦听去侦听数组,那也能捕捉到变化(因为Vue对这些方法重新封装了),所以大部分情况下,对于数组而言,直接不用开启深度侦听也可侦听到。

push() / pop() / shift() / unshift() / splice() / sort() / sort() / reverse()

// html
    <div id="app">
        <button @click="add">添加一个元素</button>
        <button @click="del">删除一个元素</button>
        <ul>
            <li v-for="item in list"> {{ item }}</li>
        </ul>
    </div>
// JS
        new Vue({
            el: '#app',
            data: { list: [10, 20, 30] },
            methods: {
                add () {    // 添加一个随机数
                    this.list.push( Math.random() )
                },
                del () {    // 删除最后一个元素
                    this.list.pop()
                }
            },
            watch: {
                // 普通侦听数组list——因为数组的push、pop、unshift、shift、splice等方法都被Vue重新包装过,所以才能侦听到变化
                list () {
                    console.log('list有变化')
                }
            }
        })

六、Vue文件(组件)

1、概念

以 .vue结尾的文件,称之为vue文件,也叫组件;

组成网页的一部分就叫组件,一个网页就是一个一个组件组成的;

2、vue文件包含的三大部分

html、js、css

正是因为有这三大部分,所以才能代表界面中的一个小部分

3、将vue文件进行webpack打包

因为浏览器认识的是html文件,不认识vue文件,所以该用 webpack 进行打包,打包生成为html文件,而且 webpack 是一个模块化管理项目的工具,而组件可以理解为就是整个项目中的一个模块,所以更应该用 webpack 管理起来。

七、脚手架(vue-cli)

官网:4.40

1、安装脚手架

安装全局模块
    1. 安装命令
        npm i -g @vue/cli
    2. 检测是否安装成功(这是大写的V,如果出现版本号,就代表安装成功)
        vue -V
    3. 更新
        npm update -g @vue/cli

2、创建新项目和运行项目

a)找一个放自己项目的文件夹(文件夹中不能有vue.js文件),打开小黑窗,执行创建项目的命令

创建项目
vue create 项目名
    注意:项目名不能大写、不能有特殊符号、不能用关键字、不能叫vue

b)选择版本配置

c)出现如下界面代表创建成功

d)可执行最后两行命令开启开发服务器

e)创建成功后,文件夹中会出现创建的项目文件夹

项目文件介绍

  • App.vue,称之为 主组件,因为项目运行后看到的效果,就是在这里写的
  • main.js,称之为 入口文件,打包运行的第一个文件就是它
  • 以后写代码,都是写在 src 里的

3、脚手架Vue项目启动流程

 

4、组件开发的注意点

每个 .vue文件有三大部分:

  • template:写html结构的地方(必须有);
  • script:写js代码的地方;
  • style:写样式的地方;

这三大部分可以用 <vue> 来快速生成

(1)每个组件的template里要有根标签

template里是放html结构的地方,但是必须把所有写的结构 包到一个根标签里;

根标签一般都是div;

(2)组件化开发里,data必须是个函数,在return的对象中声明vue的数据

export default {    // 默认导出
    data () {       // 1.es6的简写形式
        return { 
            // 在此处声明vue的数据
            msg:'123',
        }
    },
    data: function () {       // 2.原生形式
        return {
            msg:'123',
        }
    }
};

5、eslint (强制遵守规范)

eslint工具在创建项目时已经默认安装了,默认是开启的(代码不规范的话会报错);

作用:让程序员写代码时能够强制遵守规范,如果代码不符合规范就直接报错,运行不了;

如何关闭ESLint (关的是项目的规范)?

来到 vue.config.js 这个配置文件,写如下命令

lintOnSave: false

注意:以后如果小黑窗里报错,但凡出现ESLint字样,代表代码不规范

6、v-for必须加key才符合VScode规范

:key只能给字符串或数值类型,所以建议:有id给id,没id给下标

<li v-for="(item,index) in list" :key="index">{{ item }}</li>      // item为字符串或数字
<li v-for="(item,index) in list" :key="item.id">{{ item }}</li>    // item为对象且有id属性时

注意:在VScode标准语法规范里,写 v-for 必须要加 :key 属性;如果不写,语法检查通不过会标红报错(不是真正的报错)

八、组件

包含另外一个组件的叫父组件;被包含的组件叫子组件

Vue组件是对UI结构的封装,提高了复用性。

1、导入与注册组件

(1)在文件夹components 中新建vue组件文件,相当于封装了一个组件;

(2)导入组件

import 组件名 from '组件的路径'    // 写在父组件的<script>中,组件名可以和

例:
import mySon from './components/mySon.vue'
import mySon from './components/mySon'    可以省略后缀,意为先寻找这个的.js文件,在找.vue文件

(3)注册组件

components: {     写在与data平级的位置,注册的所有组件都写在这个components中
    组件名1,
    组件名2, ...
}
// 例
components: {
    mySon,       // 注册组件mySon,是 mySon: mySon 的简写
    xx:mySon,    // 注册组件mySon,并改名为xx,所以后面用要用 <xx /> 来使用
}

(4)使用组件 (在想展示的位置,写一个这个组件的标签即可)

<组件名 />
例:
<mySon />
<my-son />    使用时支持把驼峰拆开
<xx />        使用改名的组件

2、组件的style的属性

(1)scoped (样式只作用于当前文件)

默认情况下(没使用scoped时),某个组件里写的样式,都是全局样式,别的组件也能匹配到此样式

(1.1)scoped使用方法

style 标签上加一个 scoped,就代表当前 style 里的样式,只生效于当前 组件,不会影响到别的组件;

<style scoped>
    该vue文件的样式
</style>

建议:以后在组件里写样式时,记得要加 scoped,这样不会造成样式冲突;

(1.2)scoped原理

(1.2.1)为什么默认情况下某个组件里的样式是全局样式?

因为在app.vue里导入某个组件,那就相当于把这个组件的内容全部放到app.vue里了,就相当于网页里整体内容都是在app.vue里的,所以某个组件的样式也是放到app.vue里了,那就相当于是全局样式了;

(1.2.2)为什么加scoped就能让它不是全局的样式了?

原理:用到了 属性选择器

如果某个组件加了 scoped,它会把组件里所有的标签加上 data-v-hash值的自定义属性(图一),并且把样式都变成[data-v-hash]属性选择器 的样式(图二),既然是属性选择器,就代表一定要有这个属性,才能匹配到样式,没有这个属性就匹配不到,那么谁有这些属性呢?就只有加了 scoped的组件才有这个属性,所以通过这样的操作,保证了加了scoped的样式只给当前组件使用。

(1.2.3)如果另外一个组件也加了 scoped能用到这个组件里的样式吗?

不能,因为hash值不一样,这个hash值是根据组件名产生的(每一个都不一样),

这样就保证了,谁加scoped,就只是给谁自己用,别人用不到。

(2)深度作用选择器(将样式穿透给子组件)

使用情况:若一个组件加了 scoped,那么它的样式默认只能给自己用,无法给子组件用,如果希望某个样式能穿透给子组件用(即父组件的某个样式要做用于子组件),就用 深度作用选择器

深度作用选择器

注意
>>>
vscode不认识,会有波浪线(不是真的报错)
/deep/
::v-deep(推荐)不会有波浪线
语法:
::v-deep 选择器{ 这里的样式就能给子组件用了 }
>>> 选择器{ 这里的样式就能给子组件用了 }
/deep/ 选择器{ 这里的样式就能给子组件用了 }

(3)vue的style标签支持less语法

步骤:(1)安装 less:npm install less less-loader@7.3.0 -D

           (2)在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式

例:
<style lang='less'>
h1{
  color: red;
  span{
    color: blue;
  }
}
</style>

3、prop

prop 是组件的 自定义只读属性
作用: 封装通用组件的时候,合理地使用 prop 可以极大的提高组件的复用性。可以理解为 prop 是在使用组件 的时候, 从外面传递给组件的一个参数
使用举例:

prop 的配置选项

3.30          配置时要用对象

(1)type—给 prop 设置数据类型

(2)default—给 prop 设置默认值

(3)required—给 prop 设置是否必填

 

4、组件通信

(1)父传子

把父的数据传递给子组件

 单向数据流

  • 父可以直接给子传递数据,并且传递时父的数据变了,子的数据会跟着变
  • 单向数据流只允许 父数据改动能自动流入到子,但是子的数据不能流入到父
  • 所以如果直接在 子里修改父传递来的数据,当修改的是  上的数据(即不能修改简单类型的数据,能修改复杂类型的数据),会报错。所以如果传递的是对象,在子里改了对象的属性,那是不会报错的,也允许的;

(2)子传父

 子里的数据给父 或 父传给子的数据再给父

 使用步骤:

a.需要改父数据的时候,使用 $emit 发送一条通知

this.$emit('自定义的通知名', 数据)
例
this.$emit('changeMsg', '喵喵喵')

b.父里监听这个通知

<子组件 @自定义的通知名="函数"/>
例:
<son @changeMsg='fn'/>    // fn写在父组件中

fn (val) {    
  val就是子传递过来的数据,就可将val进行处理
}

(3)兄弟(非父子关系)组件传值-eventBus

不一定是有一个共同的父组件的2个组件之间传值,可能是更复杂的关系。

原理:在vue中,实例化一个新的vue对象,把它作为bus,将bus作为转换平台,实现非父子关系的传值。

方法一:用了一个独立的js文件来准备 bus这个对象

准备文件的好处是:谁要用谁导入即可;  

缺点:得多准备这么个文件,然后要写导入导出的语法

步骤: 

a. 来到 src目录,新建 eventBus的文件夹,里面放一个 index.js

此index.js文件中写:
    // 导入vue
    import Vue from "vue";
    // 实例化一个vue对象,并暴露出去(因为要通过它传值,所以别的组件要用)
    export default new Vue()

b. 发送、接收数据的组件都要导入index.js(bus文件)

c. 使用 bus.$emit 发送数据;使用 bus.$on 接收数据;先点关注再传递数据,才能成功

发送数据的组件:
    import bus from 'bus的路径'
    bus.$emit('自定义的事件名', 数据)    // 导入的对象不要加this,如果是访问data中的数据加this
例:
    bus.$emit('son1Send', "haha")
    bus.$emit('son1Send', this.list)
接收数据的组件:
    import bus from 'bus路径'
    bus.$on('自定义事件', (data) => {
        // 当别的组件传过来值时会自动调用这个回调函数
        // 参数data就是传递过来的数据
    })

例:

方法二:将eventBus挂载到原型对象上

a. 挂载Vue的原型对象一般是放在 main.js

因为 main.js 是入口文件,相当于项目一运行就执行的代码,所以如果写到这里面,就代表项目一打开,vue的原型里就有bus对象了

Vue.prototype.$bus = new Vue()

注意:挂载到Vue原型上的属性,前面名字要加 $,方便区分是组件自己的数据还是原型上的数据
    this.emit    // 组件data里的变量
    this.$emit   // 原型上的$emit

b. 后面组件之间的传值,用...

this.$bus.$on('自定义事件', (data) => { 参数data是传递过来的数据 }) // 订阅
this.$bus.$emit('自定义的事件名', 数据) // 发布

补充:vue-cli脚手架里的路径说明

(1)如果文件后缀是.js或者.vue,可以省略后缀

import bus from '../eventBus/index'

(2)如果一个文件夹里的文件叫package.json、index.js 或 index.vue,文件名都可以省略

import bus from '../eventBus'

(3)在脚手架中,用@代表src目录

import bus from '@/eventBus'

5、ref与$refs(可非官方实现子传父)

作用:ref配合$refs可以方便在vue中找到 dom元素以及 组件对象

用法:
1. 对着标签添加 ref 属性,指定一个名字 
    <标签 ref="名字" />
    <组件 ref="名字" />
2. 在JS中,就可以通过 this.$refs.名字 就可以到这个标签对象

例:获取标签
        this.$refs.h1.style
    如果获取的是组件,那么拿到的是组件对象,相当于组件内部的this,所以可快速父传子
        this.$refs.son1                   // 找到son1组件对象
        this.$refs.son1.list.push(999)    // 给son1子组件里的数组list添加元素
        this.$refs.son1.sayHi()           // 调用子组件里的方法

九、插槽

1、插槽分类

作用:让封装的组件内某个区域不写死(放自己相放的内容),增加组件的复用性。

使用步骤:在不想写死的地方写一个 <slot />,就相当于在这个位置挖了个坑占了个位,外界使用组件时传递了什么。slot这个位置就显示什么

注意:给插槽传递内容时,规范是建议把所有内容包在 template标签里(方便具名插槽操作)

(1)默认插槽

<slot>默认内容</slot>

插槽的双标签内可写默认内容;
如果外界如果没传内容,那么就以默认内容来显示;
如果以后外界传了内容,那么就以传递的内容在slot里显示;

(2)具名插槽

使用情况:当组件有多个地方不想写死是,所以就要准备多个具名插槽,使每处插入的内容不同。

具名插槽语法:
    <slot name="名字"> 默认内容 </slot>

传递给特定插槽的内容:
    <template v-slot:插槽名字>
    	要传递的内容
    </template>

(3)作用域插槽

使用情况:默认情况下,组件内部的数据是无法给外界使用的。作用域插槽可以把当前作用域里的数据暴露出去,给别的组件使用。

语法:
1. 暴露(向外传递)数据
    <slot name="插槽名字" :数据名1="数据1" :数据名2="数据2" />
2. 接收数据    若插槽无名字,则 v-slot="对象名"
    <template v-slot:插槽名字="对象名">
        对象名表示的对象接收传递过来的所有数据
    </template>

 2、插槽简写

总结:   v-slot:简写成 #

(1)默认插槽

<template>
    不写名字就是默认插槽
</template>

<template v-slot:default>
    默认插槽的名字叫 default,简写 #default
</template>

<template v-slot="接收数据的对象名">
    如果要拿到作用域里的数据,这时候不建议写#,写#要写 #default="接收数据的对象名"
</template>

(2)具名插槽

1、具名插槽接收数据
    完整写法
    <template v-slot:名字="obj">
    </template>

    简写写法
    <template #名字="obj">
    </template>

2、不需要接收数据的具名插槽
    完整写法
    <template v-slot:名字>
    </template>

    简写
    <template #名字>
    </template>

3、已废弃插槽的语法

已废弃的插槽语法 — Vue.js

废弃语法是在vue2.6里开始废弃,vue2.6时还能用,在vue3以后彻底不能用;

废弃语法有两个语法:
    1.用来指定给哪个插槽用
        slot="插槽名字"    等于    v-slot:"插槽名字"
    2.用来拿到插槽内部的数据,都放到obj里
        slot-scope="obj"  等于    v-slot="接收数据的对象名"

例:<template #body="obj">
    废弃语法是  <template slot="body" slot-scope="obj">

十、Vue的生命周期

Vue生命周期图示 — Vue.js

Vue的生命周期指的是vue对象(组件)从创建到销毁的一系列阶段。

 生命周期钩子总结

4个阶段8个钩子

组件缓存会产生两个钩子(activated、deactivated),所以最终生命周期钩子共10个

阶段钩子特点使用场景

创建阶段

beforeCreate

创建前,是最早的钩子,但是还无法访问data中的数据和methods的方法

created

(常用)

创建后,是最早能访问到数据的钩子

场景1:开发中,要页面一打开就要发请求拿数据,所以越早越好,但是发请求拿到数据后,还要把数据保存到vue的变量里,所以写到 created

场景2: eventBus要订阅兄弟组件传值

渲染阶段

beforeMount

渲染前,还无法访问到真实DOM

mounted

(常用)

渲染后,可以访问到真实dom,是最早能访问到真实dom的钩子

有些插件例如 echarts,希望页面一打开就有图表,那就意味着页面一打开就要创建echarts对象,但是创建 echarts对象时,需要传入一个dom元素作为图表的容器。所以用这个最早能访问到dom的钩子

更新阶段

beforeUpdate

更新前,指数据发生改变立即调用的钩子,但是此时dom还没重新更新

updated

更新后,指数据发生改变并且dom已经更新调用的钩子

销毁阶段

beforeDestroy

销毁前,还能访问到子组件

两个钩子任选其一

可以做一些回收工作:

1、停止定时器

2、如果销毁了,应该把监听兄弟组件传值给关了(取关)

this.$bus.$off("自定义的事件名")

destroyed

销毁后,访问不到子组件了

销毁钩子停止定时器

<template>
  <div class="son2">
    <h3>我是son2.vue</h3>
    <button >我要拿son1的数据 -- 相当于点了关注</button>
  </div>
</template>

<script>
export default {

  data() {
    return {
      timer: null,    // 声明变量,用于开启关闭定时器
    };
  },

  created() {
    // 当这个组件一诞生就自动订阅了组件传值(相当于关注了公众号)
    this.$bus.$on("son1Send", (data) => {
      console.log("son1给数据了,数据是:", data);
    });
    // 开启定时器
    this.timer = setInterval(() => {
      console.log('我是son2里开的定时器')
    }, 1000);
  },

  destroyed () {    或 beforeDestroy
    console.log('son2已销毁')
    // 移除定时器
    clearInterval(this.timer)
    // 在组件销毁时取关(避免内存泄露)
    this.$bus.$off("son1Send")
  }
};
</script>

进阶面试题:当一个组件里也有子组件时,那么父组件和子组件穿插的的生命周期是怎样的?

 父beforeCreate->父created->父beforeMount->子的beforeCreate->子的created->子beforeMount->子的mounted->父的mounted->谁数据改变就调用谁的beforeUpdate和updated -> 如果此时销毁父组件,会调用父的 beforeDestroy -> 子的beforeDestroy->子destroyed->父destroyed

十二、

1、注册全局

全局组件、全局过滤器、全局指令都是在 main.js 里注册(写在new Vue上边)。

(1)全局组件

注册全局组件
    Vue.component('组件的注册名称', 导入的组件名称)

例:全局组件panel
    import panel from './components/panel'
    Vue.component('panel', panel)

(2)全局过滤器

注册全局过滤器
    Vue.filter('过滤器名', (原数据) => {
        return 处理后的结果
    })

例:全局过滤器formatTime
    Vue.filter('formatTime', (val) => {
        return '2022-03-30'
    })

(3)全局指令

注册全局指令
    Vue.directive('指令名', {
        bind (el, obj) { },
        update (el, obj) { }
    })

简写:写成函数的形式
    Vue.directive('指令名', (el, obj) => { })

例:全局指令v-color
// Vue.directive('color', {
//   bind (el, obj) {
//     el.style.color = obj.value 
//   },
//   update (el, obj) {
//     el.style.color = obj.value 
//   }
// })

全局指令v-color简写
Vue.directive('color', (el, obj) => {
  el.style.color = obj.value 
})

2、动态组件

作用:让某个区域里的组件可来回切换,你指定哪个组件,它就显示那个组件;

实现方法:

在需要切换组件的位置,写一个 component标签 ,数据变量等于哪个组件名,那么这个位置就会显示什么组件
    <component :is="数据变量"/>

3、组件缓存

<keep-alive>
    // 被缓存的组件
</keep-alive>

使用 v-if 隐藏组件时,会将组件销毁,因为v-if 是通过直接操作dom树的添加或移除来达到看到或者看不到的效果

组件缓存的作用:不想让组件销毁,让其依然保留在内存中(即当组件被缓存时,它就不会被销毁);

注意:如果组件被缓存起来了,它的 销毁钩子就不会被调用了,取而代之会多两个钩子,所以最终生命周期钩子10个

  • activated: 当组件显示时调用(激活时)
  • deactivated:当组件隐藏时调用

登入切换案例—动态组件、组件缓存练习

<template>
  <div>
    <button @click="comName = 'qrcode'">二维码登入</button>
    <button @click="comName = 'sologinFomen'">表单登入</button>
    <!-- 动态切换方法1: -->
    <keep-alive>
      <component :is="comName" />
      <!-- 当点击第一个按钮时,comName = 'qrcode',就显示qrcode组件;第二个按钮也是如此 -->
    </keep-alive>
    <!-- <keep-alive> 组件缓存,当来回切换时,表单input的内容不会销毁 -->

    <!-- 动态切换方法2:v-if和v-else-if -->
    <!-- <qrcode v-if="comName == 'qrcode'" />
    <sologinFomen v-else-if="comName == 'sologinFomen'" /> -->
  </div>
</template>

<script>
// 导入切换时显示不同的两个组件
import sologinFomen from "./components/loginFome.vue";
import qrcode from "./components/qrcode.vue";

export default {
  components: { sologinFomen, qrcode },
  data() {
    return { comName: qrcode }; // 设置comName的默认值
  },
};
</script>

4、单页面应用

单页面应用(Single Page Application):整个网站的项目其实只有一个页面(整个网站只有一个 index.html,用页面里内容的切换 实现 看起来的页面跳转效果,实际上页面并没有跳转,不会有刷新)

优点:

  • 切换页面时速度快
  • 可以减少服务器的请求

缺点:

  • 默认情况下,第一次加载会慢一点
  • 不利于SEO(搜索引擎优化)
单页面应用实现原理—Hash:
  • # 号后面的部分,专业术语为“Hash 地址”
  • Hash 地址发生变化,不会导致浏览器页面的刷新
  • Hash 地址发生变化,会形成浏览器历史记录
  • vue 就是基于 hash 的这些特性来做路由的跳转,实现页面的切换的

十二、路由 (vue-router)

官方文档地址(给vue2用的路由文档):Vue Router-路由文档

路由:可以实现用一个路径对应显示一个组件;路由(核心插件)是实现单页面应用的关键所在。

1、路由的基本使用步骤

方法一:

(1)下载路由插件

npm i vue-router@3.x        // 路由的3.x版本对应vue2,路由的4.x版本对应vue3

(2)在 main.js 中粘贴以下代码

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

Vue.config.productionTip = false

// 1. 使用模块化机制编程时,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
import VueRouter from 'vue-router'
// 给Vue安装路由(给vue对象提供路由支持)
Vue.use(VueRouter)

// 2. 导入路由要显示的组件    注意:以后路由对应的组件一般都是放到 `src/views`文件夹里
// import 组件对象 from '组件路径'
import discover from './views/discover'

// 3. 准备路由规则:设置什么路径对应什么组件
const routes = [
    // 路由重定向
    // { path: '/a路径', redirect: '/b路径'},

    // path这里就是路径;component这里就是对应的组件
    { path: '/a', component: discover },

    // 访问了一个没有设置过的路径,都会跳转到 404 页面(notFound组件)
    // { path: '*', component: notFound },
]

// 4. 创建 路由router 实例
const router = new VueRouter({
    routes, // (缩写) 相当于 routes: routes
    // mode: 'history'    // 将hash模式转换成history模式
})

// 5. 把路由对象挂载到vue的根实例
// 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载
new Vue({
    router,
    render:h=>h(App)
}).$mount('#app')

(3)在组件中放置一个路由出口        注:路由出口就是路由要显示到的位置

<router-view />        // 用了路由,vue文件中就不必导入/注册组件了

方法二(规范):

(1)安装 vue-router

(2)创建路由模块

在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:

// 1.导入Vue及VueRouter
import Vue from "vue";
import VueRouter from "vue-router";

// 2.将VueRouter安装为Vue的插件,给vue对象提供路由支持
Vue.use(VueRouter)

// 3.创建路由的实例对象
const router = new VueRouter()

// 4.导出路由实例对象
export default router

(3)将路由模块挂载到 vue 的根组件中

在 src/main.js 入口文件中,导入并挂载路由模块

 

(4)配置路由规则

在 src/router/index.js 路由模块中,在router的实例对象中通过 routes 数组声明路由的匹配规则

routes: [
    { path: 'hash地址', component:组件 }
    { path: '/home', component: home },
]

 

(5)声明路由链接路由占位符

在组件vue文件中使用
1.路由链接
    <router-link to="路径">导航名</router-link>
    <router-link to="/about">关于</router-link>
2.路由占位符,将组件渲染到这里
    <router-view></router-view>

2、使用a标签做路由跳转

<a href="#/a">组件discover</a>    // 点击a标签显示地址对应的组件
    注意:使用路由后,默认的路径前面都有 # ,所以路径应该这样写: #/路径

3、路由的模式

路由的模式特点

hash模式

(默认)

网址上的路径前面要包含 # ,如 域名/#/my

history模式

像正常网址一样,没有 # 号,如 域名/my ;

如果项目正式上线,想用history模式还需要服务器做一些配置上的支持;

将hash模式转换成history模式

main.js文件中在实例化 router 对象时,加一个mode,写明模式名字 
    const router = new VueRouter({
        routes,
        mode: 'history'
    })

4、router-link 路由链接,实现跳转(使用)

语法:
    <router-link to="路径">文字</router-link>
例:
    <router-link to="/discover">发现音乐</router-link>

router-link 的本质就是 a标签;

优点:

  • 既支持hash模式,也支持history模式;如果是hash模式,它最终会自动在路径前面加 #
  • 谁被点,谁会自动加两个类:router-link-exact-active 和 router-link-active,方便制作点谁谁高亮的效果(只要对 router-link-active 写一个高亮样式即可);

5、路由重定向

路由重定向:访问a路径,自动帮你跳转到b路经;可用于自动跳转到默认主页面;

语法:在 main.js的路由规则 里加如下一条
    { path: '/a路径', redirect: '/b路径'}    // 访问a路径,自动帮你跳转到b路经
    { path: '', redirect: '/b路径'}         // 空字符串就代表什么路径都没输入时,自动跳转到b路经
    { path: '/', redirect: '/b路径'}        // 空字符串和'/' 表示的含义一样,即访问域名自动跳转到默认的主页面
如:
    { path: '/d', redirect: '/discover'}    // 如果路径上输入的是 /d, 它会自动帮你跳转到 /discover

6、路由404页面

要实现的效果:访问了一个没有设置过的路径,都会跳转到 404 页面

在 main.js的路由规则 的最后添加一条规则
    const routes = [
        .......
        .......
        { path: '*', component: notFound },
    ]

注意: 1. 一般情况下404页面都是放在最后;
      2. *代表除了上面以外的其他路径,就访问notFound组件(404页面);
      3. 需要提前准备好 notFound组件并导入;

7、动态路由

作用、优点:提高路由规则的复用性。

使用场景:一般都是在从列表页,跳转到详情页的时候,使用动态路由。
语法:
    { path: '/路径/:动态参数名', component: 组件对象 }
例:
    { path: '/movie/:id', component: movie },
    // 这里的:id就代表需要路径上在movie后面添加一个id参数;
    // 可以访问/movie/1,/movie/2,/movie/3,...
    // 访问这个路由时,必须携带id作为参数,不携带就访问不了;

8、获取动态参数

动态路由可以传参:访问一个路由的同时也把参数携带过去。

(1)$route.params.参数名

传递动态参数后,在这个组件内部拿到这个动态参数值    ($route记录了当前路由的信息)
    语法:$route.params.参数名    
    注意:如果这段代码不是写在html,而是写在 js,那记得前面要加 this

如:
    {{ $route.params.id }}    // 参数名如果叫id,那就是 .id

(2)props: ['动态参数名']

使用 $route.params 获取动态参数比较麻烦,所以提供了通过 props来接收参数。

步骤:
1. 在要访问的组件里,声明 props,动态参数名要跟规则里的动态参数名保持一致
    props: ['动态参数名']
2.  在main.js的路由规则里,开启props接收参数的功能
    { path: '/路径/:参数名', component: movie, props: true },

9、路由嵌套

路由嵌套:一个路由里又包含了另一个路由。

  • 包含另外路由的叫父路由,被包含的叫子路由;
  • 包含所有的路由叫一级路由;一级路由里的子路由叫二级路由;

使用步骤:

a. 准备二级路由要对应的页面组件(也就是.vue文件);

b. 来到 main.js路由规则的地方,先做组件的导入;

c. 将二级路由的规则写到父路由的 children

const routes = [
  .....
  { 
    path: '/discover', 
    component: discover,                          // discover是一级路由的组件
    children: [
      { path: 'topList', component: topList },    // topList、songList是二级路由的组件
      { path: 'songList', component: songList },
    ]
  },
 ]
注意:1. 子路由的路径不加/
      2. 不加/就代表需要先拼接父路由的路径再加自己的路径
      3. 虽然加了/它还是二级路由,只不过地址上就不能拼接父路由路径了,不会访问到二级路由的组件

d. 在父路由对应的组件里,写路由链接和路由占位符

1.路由链接    注:路径要从父组件开始
    <router-link to="/about/tab1">tab1</router-link>
2.路由占位符,二级导航要放在父组件哪里就写在哪里
    <router-view></router-view>

10、声明式导航 & 编程式导航

(1)声明式导

在浏览器中, 点击标签链接 实现路由跳转的方式,叫做 声明式导航
例如: 普通网页中点击 <a> 链接 、vue 项目中点击 <router-link> 都属于声明式导航

(2)编程式导航

在浏览器中, 调用 JS API 方法 实现路由跳转的方式,叫做 编程式导航
例如:普通网页中调用 location.href 跳转到新页面的方式、vue 项目中vue-router 提供的 导航API属于编程式导航

(2.1)this.$router.push('hash地址')

作用:跳转到指定 hash 地址,并增加一条历史记录;

(2.2)this.$router.go(数值n)

作用:当数值为正、负数时,实现导航历史前进、后退

 

Vue人力资源中台项目:vue全系列课程/人力资源项目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值