• vue学习


    本节目录

    一 什么是组件

      首先给大家介绍一下组件(component)的概念

      我们在进行vue开发的时候,还记得我们自己创建的vm对象吗,这个vm对象我们称为一个大组件,根组件(页面上叫Root),在一个网页的开发中,根据网页上的功能区域我们又可以细分成其他组件,或称为子组件,看下面的图解:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>test vue</title>
    </head>
    <body>
    
    <div id="app">
        <div>{{ msg }}</div>
        <div v-text="msg"></div>
        <div v-html="msg"></div>
    
    </div>
    <hr>
    
    
    <script src="vue.js"></script>
    <script>
       //组件
        new Vue({
            el:'#app',
            data(){
                return{
                    msg:'<h2>超</h2>', 
                }
            }
        })
    
    </script>
    </body>
    </html>
    复制代码

      以vue官网来看,vue官网是用vue开发的:

        

        每个组件中都有对应的data(),methods,watch等属性功能,组件是为了功能模块化,为了解耦,每个组件有自己的数据属性,监听自己的数据属性等操作。

        后面我们学习组件会知道组件是可以嵌套的,那么就看看图解组件嵌套的组件树,及数据流向,数据是单项数据流 ,数据从整个项目的入口进来之后,先流向我们的大组件vue,然后再流向其他子组件,看图解:

           

    二 v-model双向数据绑定

      

      v-model的双向数据绑定,v-model只能应用在input、textarea、select等标签中,那v-model怎么用呢,看代码,双向数据绑定又是什么意思呢,看下面的图解。 

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <!--之前我们给input标签加默认值是用的input标签的value属性,但是用vue的时候,vue会默认将这个value属性忽略掉,也就是value={{ msg }}不生效,使用v-model来绑定数据-->
        <!--<input type="text" value="">-->
        <!-- v-model双向数据绑定,打开页面然后在input标签中输入内容,看效果 -->
        <input type="text" v-model="msg">
        <p>{{ msg }}</p>
    </div>
    
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el:'#app',
            data(){
                return{
                    msg:'chao',
                }
            }
    
        })
    
    </script>
    </body>
    </html>
    复制代码

      效果图:

          

      双向数据绑定流程图解:

          

      那么我们自己通过前面学的内容来完成一个类似v-model的input标签的一个双向数据绑定的效果,这里只是模拟了一个双向数据绑定的效果,帮助大家理解其原理的大概实现方式,但实际其原理比下面的代码要复杂的多,注意这里只是模拟,看代码: 

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <!-- 通过v-bind:value属性,及input实时监听输入事件来完成一个双向数据绑定的效果,textarea
    adio等可以这么搞,但是他们绑定的change事件 -->
        <input type="text" :value="msg" @input="valueHandler">
        <p>{{ msg }}</p>
    </div>
    
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        //实际上的原理是通过一个叫做Object.defineProperty(监听哪个对象,给什么事件,回调函数,settergetter方法),比较复杂,用到了观察者、监听者、执行者等好多个对象来完成这个事情,了解一下就行了
        let vm = new Vue({
            el:'#app',
            data(){
                return{
                    msg:'chao',
                }
            },
            methods:{
                valueHandler(e){
                    //这就是setter方法,也就是赋值操作
                    this.msg = e.target.value;
                }
            }
    
        })
    
    </script>
    </body>
    </html>
    复制代码

        模拟的就不用记了,咱们主要记住v-model的用法,实现双向数据绑定。

      

      textarea的v-model

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <p style="white-space: pre-line;">{{ message }}</p>
        <br>
        <!--<textarea placeholder="内容">{{ message }}</textarea> 不能这样写-->
        <textarea v-model="message" placeholder="内容"></textarea>
    </div>
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    message: 'chao',
                }
            }
        })
    </script>
    </body>
    </html>
    复制代码

       单个复选框的v-model:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <!--单选框v-model绑定了这个checked属性,下面给了默认值为false,选中这个单选框,那么checked属性的值自动变为true-->
        <input type="checkbox" id="checkbox" v-model="checked">
        <label for="checkbox">{{ checked }}</label>
    </div>
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    // checked: '',//也可以给其他的默认值,但是选中值为true,取消选中值为false
                    checked: false,
                }
            }
        })
    </script>
    </body>
    </html>
    复制代码

      多个复选框的v-model

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <div id='example-3'>
            <!-- 注意,这里选中之后,每个复选框的value属性的值会添加到v-model绑定的后面这个 checkedNames数组中,如果没有value属性,那么选中它时,添加的是null-->
            <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
            <label for="jack">姓名Jack</label>
            <input type="checkbox" id="john" value="John" v-model="checkedNames">
            <label for="john">姓名John</label>
            <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
            <label for="mike">姓名Mike</label>
            <br>
            <span>选择的名称: {{ checkedNames }}</span>
        </div>
    </div>
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    checkedNames: []
                }
            }
        })
    </script>
    </body>
    </html>
    复制代码

      单选框raido的v-model

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <div id="example-4">
            <!-- 选中之后picked的值为选中的单选框的value属性的值,如果没有这是value属性,那么选中值为空 -->
          <input type="radio" id="one" value="One" v-model="picked">
          <label for="one">1</label>
          <br>
          <input type="radio" id="two" value="Two" v-model="picked">
          <label for="two">2</label>
          <br>
          <span>Picked: {{ picked }}</span>
        </div>
    </div>
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    picked: '',
                }
            }
        })
    </script>
    </body>
    </html>
    复制代码

      单选下拉框的v-model

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <div id="example-5">
            <!-- 单选下拉框,v-model写在select标签中,选中某个option标签时,如果option标签有value属性,那么v-model绑定的selected的值是value属性对应的值,如果option标签中没有设置value属性,那么选中option标签时,selected值为option标签的文本内容 -->
            <select v-model="selected">
                <option disabled value="">请选择</option>
                <option value="xx1">A</option>
                <option>B</option>
                <option>C</option>
            </select>
            <span>Selected: {{ selected }}</span>
        </div>
    </div>
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    selected: '',
                }
            }
        })
    </script>
    </body>
    </html>
    复制代码

        注意:如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

       多选下拉框的v-model

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
    
        <!-- 多选下拉框,v-model写在select标签中,选中某个option标签时,如果option标签有value属性,那么value属性对应的值会添加到v-model绑定的selected数组中,如果option标签中没有设置value属性,那么选中option标签时,option标签的文本内容添加到v-model绑定的selected数组中 -->
        <div id="example-6">
            <select v-model="selected" multiple style=" 50px;">
                <option value="1">A</option>
                <option>B</option>
                <option>C</option>
            </select>
            <br>
            <span>Selected: {{ selected }}</span>
        </div>
    </div>
    <script src="vue.js"></script>
    <script src="jquery.js"></script>
    <script>
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    // selected: '', //可以写别的值,但是最终都会变为数组
                    selected: [],
                }
            }
        })
    </script>
    </body>
    </html>
    复制代码

       

       值的绑定

        关于值的绑定大家看看下面的写法就可以,这里不多说了

        对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值):

    复制代码
    <!-- 当选中时,`picked` 为字符串 "a" -->
    <input type="radio" v-model="picked" value="a">
    
    <!-- `toggle` 为 true 或 false -->
    <input type="checkbox" v-model="toggle">
    
    <!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
    <select v-model="selected">
      <option value="abc">ABC</option>
    </select>
    复制代码

        但是有时我们可能想把值绑定到 Vue 实例的一个动态属性上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串。

        复选框:

    复制代码
    <input
      type="checkbox"
      v-model="toggle"
      true-value="yes"
      false-value="no"
    >
    // 当选中时 vm.toggle === 'yes'
    // 当没有选中时 vm.toggle === 'no'
    复制代码

          这里的 true-value 和 false-value 特性并不会影响输入控件的 value 特性,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(比如“yes”或“no”),请换用单选按钮。

         单选按钮:

    <input type="radio" v-model="pick" v-bind:value="a">
    
    // 当选中时
    vm.pick === vm.a

         选择框的选项

    复制代码
    <select v-model="selected">
        <!-- 内联对象字面量 -->
      <option v-bind:value="{ number: 123 }">123</option>
    </select>
    
    // 当选中时
    typeof vm.selected // => 'object'
    vm.selected.number // => 123
    复制代码

      修饰符

         .lazy  懒监听

          在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change事件进行同步:

    <!-- 在“change”时而非“input”时更新,意思就是输入完按下回车键或者光标移走时才触发数据的更新 -->
    <input v-model.lazy="msg" >

        .number

           如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符,意思就是让用户只能输入数字:

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

          这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

        .trim

          如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

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

       

      以后用vue开发的话,基本也就放弃jQuery了,因为jQuery里面有的功能,vue里面基本都有,vue没有ajax,但是我们有别的办法。

      另外给大家说一个vue社区,vue中文社区,这里面有vue的很多项目,你可以来这里找项目来学习,这里的项目基本都只有前端的代码,后端需要我们自己写,其中 vue awesome,是vue高星项目,也就是很多人都喜欢,比较nb的项目,记着FQ玩,不然有些东西你搜不了。

         

           

          这里面有很多vue的高级应用,我们学的都是基础,想玩高级的,就来这里学。

       给大家推荐一些高星的vue-ui组件:后面我们的学习的项目,用Element UI。

        Vue 是一个轻巧、高性能、可组件化的MVVM库,API简洁明了,上手快。从Vue推出以来,得到众多Web开发者的认可。在公司的Web前端项目开发中,多个项目采用基于Vue的UI组件框架开发,并投入正式使用。开发团队在使用Vue.js框架和UI组件库以后,开发效率大大提高,自己写的代码也少了,很多界面效果组件已经封装好了。在选择Vue UI组件库的过程中,通过GitHub上根据star数量、文档丰富程度、更新的频率以及维护等因素,也收集整理了一些优秀的Vue UI组件库。下面介绍一下给大家强烈推荐优秀的的Vue UI组件库。

        1、 iView UI组件库iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品。iView的组件还是比较齐全的,更新也很快,文档写得很详细。有公司团队维护,比较可靠的Vue UI组件框架。iView生态也做得很好,还有开源了一个iView Admin,做后台非常方便。官网上介绍,iView已经应用在TalkingData、阿里巴巴、百度、腾讯、今日头条、京东、滴滴出行、美团、新浪、联想等大型公司的产品中。iView官网:https://www.iviewui.com/

        2、Vux UI组件库Vux是基于WeUI和Vue2.x开发的移动端UI组件库,主要服务于微信页面。Vux的定位已经很明确了,一是:Vue移动端UI组件库,二是:WeUI的基础样式库。Vux的组件涵盖了所有的WeUI的内容,还扩展了一些常用的组件。比如:Sticky、timeline、v-chart、XCircle。Vux是个人维护的。但是GitHub上star还是很高的,达到13k。在GitHub上看到对issue的关闭还是很迅速的。Vux文档基本的组件用法和效果都讲解到位了。在vux官网上也展示了很多Vux的使用案例。在微信页面开发中,基本没有太多的bug,开发还是比较顺手的。Vux官网:https://vux.li/

        3、Element UI组件库Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。Element是饿了么前端开源维护的Vue UI组件库,更新频率还是很高的,基本一周到半个月都会发布一个新版本。组件齐全,基本涵盖后台所需的所有组件,文档讲解详细,例子也很丰富。没有实际使用过,网上的Element教程和文章比较多。Element应该是一个质量比较高的Vue UI组件库。Element官网:http://element.eleme.io/#/zh-CN

        4、Mint UI组件库Mint UI基于 Vue.js 的移动端组件库,同样出自饿了么前端的项目。Mint UI是真正意义上的按需加载组件。可以只加载声明过的组件及其样式文件。Mint UI 采用 CSS3 处理各种动效,避免浏览器进行不必要的重绘和重排,从而使用户获得流畅顺滑的体验。网上的视频教程很多都是基于Mint UI来讲的,开发移动端web项目还是很方便,文档也很简介明了。很多页面Mint UI组件都已经封装好,基本可以照着例子写,简单的调整一下就可以实现。不过,在GitHub上看最后一次代码提交在2018年1月16日,截止到目前已经过去半年了。不知道是项目比较稳定没有更新,还是项目有被废弃的可能。我们会持续关注Mint UI的动态。Mint UI官网:http://mint-ui.github.io/#!/zh-cn

        5、Bootstrap-Vue UI组件库Bootstrap-VUE提供了基于vue2的Bootstrap V4组件和网格系统的实现,完成了广泛和自动化的WAI ARA可访问性标记。Bootstrap 4是最新发布的版本,与 Bootstrap3 相比拥有了更多的具体的类以及把一些有关的部分变成了相关的组件。同时 Bootstrap.min.css 的体积减少了40%以上。Bootstrap4 放弃了对 IE8 以及 iOS 6 的支持,现在仅仅支持 IE9 以上 以及 iOS 7 以上版本的浏览器。想当初刚流行响应式网站的时候,Bootstrap是世界上最受欢迎的建立移动优先网站的框架,Bootstrap可以说风靡全球。就算放在现在很多企业网站都是采用Bootstrap做的响应式。Bootstrap-Vue可以让你在Vue中也实现Bootstrap的效果。Bootstrap-Vue官网:https://bootstrap-vue.js.org/

        6、Ant Design Vue UI组件库Ant Design Vue是 Ant Design 3.X 的 Vue 实现,开发和服务于企业级后台产品。在GitHub上可以找到几个Ant Design的Vue组件。不过相比较而言,Ant Design Vue更胜一筹。Ant Design Vue共享Ant Design of React设计工具体系,实现了所有Ant Design of React的组件,支持现代浏览器和 IE9 及以上(需要 polyfills)。可以让熟悉Ant Design的在使用Vue时,很容易的上手。Ant Design Vue官网:https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/

        7、AT-UI UI组件库AT-UI 是一款基于 Vue.js 2.0 的前端 UI 组件库,主要用于快速开发 PC 网站中后台产品,支持现代浏览器和 IE9 及以上。AT-UI 更加精简,实现了后台常用的组件。AT_UI官网:https://at-ui.github.io/at-ui/#/zh

        8、Vant UI组件库Vant是一个轻量、可靠的移动端 Vue 组件库。Vant是有赞团队开源的,主要维护也是有赞团队。Vant Weapp 是有赞移动端组件库 Vant 的小程序版本,两者基于相同的视觉规范,提供一致的 API 接口,助力开发者快速搭建小程序应用。截止到目前,Vant已经开源了50+ 个经过有赞线上业务检验的组件。比如:、AddressEdit 地址编辑、AddressList 地址列表、Area 省市区选择、Card 卡片、Contact 联系人、Coupon 优惠券、GoodsAction 商品页行动点、SubmitBar 提交订单栏、Sku 商品规格弹层。如果做商城的,不太在意界面,实现业务逻辑的话,用Vant组件库开发还是很快的。Vant官网:https://youzan.github.io/vant/#/zh-CN/intro

        9、cube-ui UI组件库cube-ui 是基于 Vue.js 实现的精致移动端组件库。由滴滴内部组件库精简提炼而来,经历了业务一年多的考验,并且每个组件都有充分单元测试,为后续集成提供保障。在交互体验方面追求极致。遵循统一的设计交互标准,高度还原设计效果;接口标准化,统一规范使用方式,开发更加简单高效。支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。cube-ui官网:https://didi.github.io/cube-ui/#/zh-CN

        10、Muse-UI UI组件库Muse-UI基于 Vue 2.0 优雅的 Material Design UI 组件库。Muse UI 拥有40多个UI 组件,用于适应不同业务环境。Muse UI 仅需少量代码即可完成主题样式替换。Muse UI 可用于开发的复杂单页应用Muse-UI官网:https://muse-ui.org/#/zh-CN

        11、N3-components UI组件库N3组件库是基于Vue.js构建的,让前端工程师和全栈工程师能快速构建页面和应用。N3-components超过60个组件 组件列表、自定义样式、支持多种模化范式(UMD)、使用ES6进行开发。N3官网:https://n3-components.github.io/N3-components/component.html

        12、Mand MobileMand Mobile是面向金融场景的Vue移动端UI组件库,丰富、灵活、实用,快速搭建优质的金融类产品,让复杂的金融场景变简单。Mand Mobile含有丰富的组件30+的基础组件,覆盖金融场景,极高的易用性组件均有详细说明文档、案例演示,汲取最前沿技术,组件化轻量化实现,兼顾稳定和品质,努力实现金融场景的全覆盖。Mand Mobile官网:https://didi.github.io/mand-mobile/#/zh-CN/home

        13、we-vue UI组件库we-vue 是一套基于 Vue.js 的移动关组件库,结合 weui.css 样式库,封装了一系列组件,非常适于微信公众号等移动端开发。we-vue 包含35+ 个组件,单元测试覆盖率超 98%,支持 babel-plugin-import,完善的在线文档,详细的在线示例。we-vue官网:https://wevue.org/

        14、veui UI组件库veui是一个由百度EFE team开发的Vue企业级UI组件库。目前文档还没有,只有demo。GitHub上说是正在进行的一项工作。那我们就耐心等待吧。veui官网:https://ecomfe.github.io/veui/components/#/

        15、Semantic-UI-Vue UI组件库Semantic-UI-Vue是基于 Vue.js对Semantic-UI 框架的实现。Semantic作为一款开发框架,帮助开发者使用对人类友好的HTML语言构建优雅的响应式布局。Semantic-UI-Vue提供了一个类似于 Semantic-UI 的 API 以及一组可定制的主题。Semantic-UI-Vue官网:https://semantic-ui-vue.github.io/#/

         16 vueAdmin 基于vuejs2和element的简单的管理员模板

      以后我们做vue开发,基本都是基于组件开发的,下面我们就来学学组件基础。

        

    三 组件基础

    通常一个应用会以一棵嵌套的组件树的形式来组织:

      

        每个组件都有自己的数据属性、方法、监听、钩子函数等自己相应的功能,一个组件就可以称为一个模块,组件化开发就是咱们说的模块化开发了。

    下面我们来学一下组件怎么玩。

    1 局部组件 

       使用局部组件遵循三步:打油诗:声子(声明子组件)、挂子(挂在子组件)、用子(使用子组件)。

       再说局部组件之前,我先给大家说一个东西,叫做template模板,这个不是组件昂,只是我们后面组件要用这个东西,看代码

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div id="app">
        {{ msg }}
    </div>
    <div id="app2">
    
    </div>
    
    <script src="vue.js"></script>
    <script>
        //仅仅在实例化vue对象中如果既有el又有template属性,并且template中定义模板的内容,那么template模板的优先级大于el
        let vm = new Vue({
            el:'#app',  //注意,这个id为app的标签还必须在html中写上,而且不写的话会报错
            data(){
                return{
                    msg:'chao'
                }
            },
            //template中的模板内容会将el指向的那个id为app的标签给替换掉,替换成template里面的模板内容,为什么要写这个东西呢,因为我们后面学习组件会用,至于做什么用,大家往后面学
            template:`  //用反引号的原因是里面有写标签,属性值用的双引号
                <div class="xxx">
                    <h1>{{ msg }}</h1>
                </div>
            `
        })
    
    </script>
    </body>
    </html>
    复制代码

       看效果:这个回头我们学到生命周期,你再回来看这个就会很清楚了。

        

      好,接下来学我们的局部组件,看代码:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
    
    </div>
    
    <script src="vue.js"></script>
    <script>
        //整个这个vue对象可以理解为我们的根组件,大的root组件,里面有其他几个组件:比如说app组件(父组件)、header导航栏组件、aside侧边栏组件、content内容组件
        //1 声子,首先我们先声明一个父组件,vue中的组件的名字首字母要大写,为了跟标签区分
        let App = {  //是一个自定义对象,这个对象里面除了el没有,其他的Vue对象里面的内容都有,并且组件中的data必须是个函数,一定要有返回值。
            data(){ // 绑定当前app组件的数据属性
                return{
                    text:'我是Jaden'
                }
            },
            //子组件里面加上template模板
            template: //当前的模板里面使用当前组件的数据属性,和下面的Vue对象里面的数据属性没关系昂
                `
                    <div id="dd1">
                        <h2>{{ text }}</h2>
                    </div>
                `
        };
        let vm = new Vue({
            el:'#app',
            data(){
                return{
                    msg:'chao'
                }
            },
            //template中的模板内容会将el指向的那个id为app的标签给替换掉,替换成template里面的模板内容,为什么要写这个东西呢,因为我们后面学习组件会用,至于做什么用,大家往后面学
            template:  //3 用子,template可以不用,如果不用那就是挂载到上面的el中去
            `
                <div class="xxx">
                    <App /> //这样写,类似标签的写法,但是我们叫组件,还可以<App><App/>这样写,但是这样写的话,这个标签后面的所有内容都不会显示了,因为它封闭了,并且首字母都是要大写的,为了和html中的标签区分,并且要闭合标签,就把App组件使用上了,将声明的App组件里面的内容全部挂载上了,注意,想要显示内容,需要在我们上面声明的App组件中写template模板了,(拿我的代码测试的时候,别忘了把我注释的这些内容删除了,我写在反引号里面了)
                    <a href="">aaa</a>
                </div>
            `,
            //2 挂子
            components:{ //组件们,可以在这里挂载多个组件
                // App:App  //写法key:value,key是我们自己起的,value就是上面我们声明的app组件名称,并且如果key和value一样,可以用下面的简写方式
                App
            }
        })
    
    </script>
    </body>
    </html>
    复制代码

      

      打开页面看效果:

        

      看图解:

        

      上面代码的另外一种写法,不在vue对象里面写template了,并且除了App组件外,我们在给App组件加一个子组件,大家看代码,一个组件挂到另外一个组件上,那这个组件称为子组件,另外一个组件称为父组件。

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <!--也是两种写法,下面两种都可以-->
        <!--<App></App>-->
        <App/>
    </div>
    
    <script src="vue.js"></script>
    <script>
        //除了下面的App组件外,我们再定义一个App组件的子组件,叫Vheader,别写header,因为在H5中新出了个header标签,以防有冲突
        let Vheader = {
            data() {
                return {
    
                }
            },
            //子组件里面加上template模板,当然也可以不写,但是一般都写上,因为我们要通过它来定义内容,去替换我们挂载的组件内容,而且我们在组件里面要写很多的内容,还有一点大家要记着,不管是组件还是我们的vue对象里面的template中写标签或者内容的时候,必须有个外层标签包裹,这里我们用的是div标签,其他所有内容都写在这个标签里面才行
            // template: ` 这种写法只能显示第一个标签的内容,另外一个标签就报错了,错误信息是:Component template should contain exactly one root element. 意思是说必须有个根元素(根标签)包裹,保证内容全部闭合,也就是下面的那种写法。
            //     <h2>chao</h2>
            //     <h2>Jaden</h2>
            // `
            template: `
                <div>
                    <h2>chao</h2>
                    <h2>Jaden</h2>
                </div>
            `
        };
    
    
        let App = {
            data() {
                return {
                    text: '我是Jaden'
                }
            },
            //子组件里面加上template模板,当然也可以不写,但是一般都写上,因为我们要通过它来定义内容,去替换我们挂载的组件内容,而且我们在组件里面要写很多的内容
            template: //现在我们将子组件Vheader在App组件的template中使用一下,
                `
                    <div id="dd1">
                        <h2>{{ text }}</h2>
                        <Vheader></Vheader>
                    </div>
                `
            ,
            components:{
                Vheader,  //将子组件挂载到App组件的里面,别忘了,除了el属性,vue对象里面的所有属性或者方法在组件中都有
            }
        };
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    msg: 'chao'
                }
            },
            //template中的模板内容会将el指向的那个id为app的标签给替换掉,替换成template里面的模板内容,为什么要写这个东西呢,因为我们后面学习组件会用,至于做什么用,大家往后面学
            //此时我就把template去掉了,那么将App组件写到了上面id为app的div标签中,大家看看,说了,不写template,那么就会挂载到el对应的那个标签中
            //2 挂子
            components: {
                App
            }
        })
        //现在我们写的组件都放到这一个文件里面了,比较乱是不是,将来我们是模块化开发的,这些组件都会分文件来存写的,然后以import的形式引入,然后再挂载,再使用,现在先忍着吧,哈哈
    </script>
    </body>
    </html>
    复制代码

        上面代码的简单流程图解:

          

     简单总结一下

      1.声明子   

    复制代码
    let App = {
            data() {
                return {
                    text: '我是Jaden'
                }
            },
          
            template: 
                `
                    <div id="dd1">
                       
                    </div>
                `
            ,
        };
    复制代码

       2.挂子

    复制代码
    {
            如果有template,用template也是可以挂子的,并且template优先级比el高
            template:`<App />`
            components: {
                App //子组件
            }
    }
    复制代码

         3 用子 

    复制代码
    父组件的template中,或者el对应的标签中来使用
    <App></App>
    <App/>
    在template中使用的时候可以这样写
    template:`
    <div>
        <App />
    </div>
    `,
    或者直接写
    template:`<App />`,
    复制代码

        上面我们学了一下组件的基础,我们提到了一个模块化开发,有些朋友可能不太理解,那么我们在GitHub上下载一个项目,来看看:

        第一步:到GitHub上下载这个项目

        

        下载到本地之后,按照人家的说明来运行项目

    复制代码
    # 克隆到本地,或者下载zip压缩包
    git clone https://github.com/bailicangdu/vue2-happyfri-master.git
    
    # 进入文件夹
    cd vue2-happyfri-master
    
    # 安装依赖
    npm install 或 yarn(推荐)
    
    # 开启本地服务器localhost:8088
    npm run dev
    
    # 发布环境
    npm run build
    复制代码

         一个组件里面包含了HTML、CSS、JS等内容,我们就看一下这个项目的src文件夹里面的main.js和page文件夹里面的内容,大致看看就清晰一些了。

      2 全局组件

      直接看代码,局部组件使用时需要挂载,全局组件使用时不需要挂载。那么他们两个什么时候用呢,局部组件就在某个局部使用的时候,全局组件是大家公用的,或者说每个页面都有这么一个功能的时候,在哪里可能都会用到的时候。

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <App/>
    </div>
    
    <script src="vue.js"></script>
    <script>
    
        // 全局组件,Vue.component(参数1,参数2),第一个参数是起的全局组件的名字,第二个参数是组件的options,这个组件是全局的,在任意组件中都可以用,使用的时候不需要挂载了,局部组件才需要挂载
        //下面的操作,我们将VBtn这个全局组件用到了App组件和Vheader组件中,那么这个VBtn组件称为App组件和Vheader组件的子组件
        Vue.component('VBtn',{
           data(){
               return{
                    btnName:'按钮',
               }
           },
           // template:``
           template:`<button>{{ btnName }}</button>`
        });
    
        //下面是声明局部组件
        let Vheader = {
            data() {
                return {
                    message:'chao'
                }
            },
            //使用全局组件
            template: `
                <div>
                    <h2>{{ message }}</h2>
                    <h2>Jaden</h2>
                    <VBtn></VBtn>  
                </div>
            `
        };
        let App = {
            data() {
                return {
                    text: '我是Jaden'
                }
            },
            //使用全局组件
            template:
                `
                    <div id="dd1">
                        <h2>{{ text }}</h2>
                        <Vheader></Vheader>
                        <VBtn></VBtn>
                    </div>
                `
            ,
            components:{
                Vheader,
            }
        };
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    msg: 'chao'
                }
            },
            components: {
                App
            }
        })
    </script>
    </body>
    </html>
    复制代码

          

      我们发现这个全局组件应用到哪个组件上都可以,就比如这个button组件一样,我们可能需要很多的button按钮,每个button按钮里面的文字还不一样,这样我们就需要按照自己组件中需求来改button按钮里面的文字,这个怎么玩呢,看代码:slot(插槽)内容分发组件

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <App/>
    </div>
    
    <script src="vue.js"></script>
    <script>
    
        Vue.component('VBtn',{
           data(){
               return{
                    btnName:'按钮',
               }
           },
           // template:``
           // template:`<button>{{ btnName }}</button>`
           //button按钮中写slot组件<slot>{{ btnName }}</slot> ,那么下面其他组件中使用这个全局组件的时候,就可以动态的更改button按钮的文字了,写法<VBtn>删除</VBtn>,那么删除两个字就替换了按钮两个字
           template:`<button>
                        <slot>{{ btnName }}</slot>
                    </button>`
        });
    
        let Vheader = {
            data() {
                return {
                    message:'chao'
                }
            },
            template: `
                <div>
                    <h2>{{ message }}</h2>
                    <h2>Jaden</h2>
                    <VBtn>编辑</VBtn>
                </div>
            `
        };
        let App = {
            data() {
                return {
                    text: '我是Jaden'
                }
            },
            //注意<VBtn>你好</VBtn>,这样写直接来改button的文字是不行的,我们需要将文字'你好',映射给上面我们定义全局组件时的template中的button按钮中的文字,这时候我们就需要使用Vue内置的slot组件,叫做内容分发组件,看写法
            template:
                `
                    <div id="dd1">
                        <h2>{{ text }}</h2>
                        <Vheader></Vheader>
                        <VBtn>删除</VBtn>
                        <VBtn></VBtn>
                    </div>
                `
            ,
            components:{
                Vheader,
            }
        };
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    msg: 'chao'
                }
            },
            components: {
                App
            }
        })
    </script>
    </body>
    </html>
    复制代码

    四 父子组件传值

    通过prop属性进行传值

    1 首先说父组件往子组件传值  :两步

      1.在子组件中使用props属性声明,然后可以直接在子组件中任意使用

      2.父组件要定义自定义的属性

      看代码:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <App/>
    </div>
    
    <script src="vue.js"></script>
    <script>
    
        let Vheader = {
            data() {
                return {
                    message:'chao'
                }
            },
            //挂载父组件的属性,此时在子组件的任意位置就可以使用这个父组件的text对应的数据了,但是需要父组件将值传给子组件
            props:['msg','msg2','msg3','msg4','msg5'],
            template: `
                <div class="c1">
                    <h2>{{ message }}</h2>
                    <h2>Jaden</h2>
                    <h3>{{ msg }}</h3>
                    <h3>{{ msg2 }}</h3>
                    <h3>{{ msg3.id }}</h3>
                    <h3>{{ msg3.name }}</h3>
                    <h3>{{ msg4 }}</h3>
                    <h3>{{ msg5 }}</h3>
                </div>
            `
        };
        let App = {
            data() {
                return {
                    text: '我是父组件的数据1',//字符串
                    text2: 22,  //数字
                    text3: true,  //布尔值
                    post:{  //自定义对象
                        id:1,
                        name:'jj'
                    },
                    l1:[11,22,33],  //数组
                }
            },
            //<Vheader :msg="text"></Vheader>这就是将父组件的text属性的值,给了Vheader标签的msg属性,这个msg属性就是上面子组件的props里面的msg,props['msg'],如果想绑定多个值呢?可以搞一个自定义对象(其实可以传列表什么的其他数据),存放所有的数据,但是<Vheader :msg="text" :msg2="text2" v-bind="post"></Vheader>这样的写法是将post这个自定义对象里面的键值对作为属性放到了上面子组件的class='c1'的div标签里面,作为了这个div标签的属性了,并不是我们想要的,我们想要的是在div标签里面的h标签里面用这些数据作为文本内容,所以我们的写法应该是这样的<Vheader :msg="text" :msg2="text2" v-bind:msg3="post"></Vheader>,不然我们在子组件的div标签里面这样<h3>{{ msg3.id }}</h3>使用的时候就会报错,所以注意写法,并且可以简写<Vheader :msg="text" :msg2="text2" :msg3="post"></Vheader>
            template:
                `
                    <div id="dd1">
                        <h1>{{ text }}</h1>
                        <Vheader :msg="text" :msg2="text2" :msg3="post" :msg4="l1" :msg5="text3"></Vheader>
    
                    </div>
                `
            ,
            components:{
                Vheader,
            }
        };
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    msg: 'chao'
                }
            },
            components: {
                App
            }
        })
    </script>
    </body>
    </html>
    复制代码

       子组件还可以给子组件的子组件传值,将父组件的值传递给孙子组件的意思,看代码:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <App/>
    </div>
    
    <script src="vue.js"></script>
    <script>
    
        Vue.component('VBtn',{
           data(){
               return{
    
               }
           } ,
           template:`
                <button>
                   {{ id }}
                </button>
           `,
            props:['id']  //孙子组件使用父组件的数据
        });
        let Vheader = {
            data() {
                return {
                    message:'chao'
                }
            },
            props:['msg','msg2','msg3','msg4','msg5'],
            template: `
                <div class="c1">
                    <h2>{{ message }}</h2>
                    <h2>Jaden</h2>
                    <h3>{{ msg }}</h3>
                    <h3>{{ msg2 }}</h3>
                    <h3>{{ msg3.id }}</h3>
                    <h3>{{ msg3.name }}</h3>
                    <h3>{{ msg4 }}</h3>
                    <h3>{{ msg5 }}</h3>
                    <br>
                    //看写法
                    <!--<VBtn :id="msg"></VBtn>-->
                    <VBtn :id="msg3.id"></VBtn>
                </div>
            `
        };
        let App = {
            data() {
                return {
                    text: '我是父组件的数据1',//字符串
                    text2: 22,  //数字
                    text3: true,  //布尔值
                    post:{  //自定义对象
                        id:1,
                        name:'jj'
                    },
                    l1:[11,22,33],  //数组
                }
            },
            template:
                `
                    <div id="dd1">
                        <h1>{{ text }}</h1>
                        <Vheader :msg="text" :msg2="text2" :msg3="post" :msg4="l1" :msg5="text3"></Vheader>
    
                    </div>
                `
            ,
            components:{
                Vheader,
            }
        };
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    msg: 'chao'
                }
            },
            components: {
                App
            }
        })
    </script>
    </body>
    </html>
    复制代码

      2 子组件父组件传值  :两步

      a.子组件中使用this.$emit('fatherHandler',val);fatherHandler是父组件中使用子组件的地方添加的绑定自定义事件<Vheader  @fatherHandler="appFatherHandler"></Vheader> 

      b.父组件中的methods中写一个自定义的事件函数:appFatherHandler(val){},在函数里面使用这个val,这个val就是上面子组件传过来的数据

       看代码:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div id="app">
        <App/>
    </div>
    
    <script src="vue.js"></script>
    <script>
        //通过点击,将孙子组件的button中的id值改掉,然后父组件和爷爷组件的id数据值都跟着改,这时候比较复杂,需要一个自定义事件,并且记住每个组件的事件函数中的this,都是当前事件调用者的组件,前提是你用的普通函数来写的事件执行函数,从孙子组件传递给爷爷组件的传递顺序是这样的  孙子-->父亲-->爷爷
        Vue.component('VBtn',{
           data(){
               return{
    
               }
           } ,
    
           template:`
                <button @click="clickHandler">
                   {{ id }}
                </button>
           `,
            props:['id'], //别忘了我说的,这个东西只要一声明,这里面的数据在哪里都可以用,相当于在上面的data(){return{id:}}方法里面添加了这个数据属性
            methods:{
               clickHandler(){
                   //这个clickHandler事件函数是个普通函数,那么这个this就是事件的调用者,就是我们的VBtn组件
                   console.log('4444',this);//_uid: 3
                   //VueComponent {_uid: 3, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
                   // 你需要使用this的$emit方法,vue提供的方法, this.$emit('父组件中声明的自定义事件','传的值'),点击事件传值,此时我们现在组件的父组件是下面的Vheader组件,this.$emit('vheaderClick')的意思就是触发父组件的这个自定义的vheaderClick事件
                   //  this.$emit('vheaderClick');
                   //那么我就可以通过this.id++来将id值改变,并且传递给父组件
                   this.id++;
                   this.$emit('vheaderClick',this.id); //将this.id的值传递给了父组件的vheaderClick事件,所以下面的事件需要写个形参来接收这个数据
                    //然后往Vheader的父组件app传值,将孙子组件的值传递给爷爷组件的意思
    
               }
            }
        });
        let Vheader = {
            data() {
                return {
                    message:'chao'
                }
            },
            created(){
                console.log('@@@@',this)//_uid: 2,
                //VueComponent {_uid: 2, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
            },
    
            props:['msg','msg2','msg3','msg4','msg5'],
            //我们想把孙子组件的数据先传递给父组件Vheader,然后写法是:Vheader组件中使用上面子组件的地方(标签)添加一个绑定事件,这个绑定的事件的值要等于上面我们子组件中的methods里面的事件名称相同,注意下面我们要在使用子组件的地方写一个自定义事件了,这个自定义事件的名称不能和js原生事件的名称冲突(clickinput等等)
            template: `
                <div class="c1">
                    <h1>我是Vheader组件</h1>
                    <h2>{{ message }}</h2>
                    <h2>Jaden</h2>
                    <h3>{{ msg }}</h3>
                    <h3>{{ msg2 }}</h3>
                    <h3>{{ msg3.id }}</h3>
    
                    <VBtn :id="msg3.id" @vheaderClick="vheaderClickHandler"></VBtn>
                </div>
            `,
            //需要写methods来写我们在template中使用子组件的地方绑定的事件
            methods:{
                vheaderClickHandler(val){
                    // alert(1);
                    alert(val);
                    this.$emit('fatherHandler',val);
    
                }
            },
        };
        let App = {
            data() {
                return {
                    text: '我是父组件的数据1',//字符串
                    text2: 22,  //数字
                    text3: true,  //布尔值
                    post:{  //自定义对象
                        id:1,
                        name:'jj'
                    },
                    l1:[11,22,33],  //数组
                }
            },
    
            created(){
                console.log('!!!!',this)//_uid: 1
                //VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
            },
            methods:{
              appFatherHandler(val){
                  // alert('app组件的id',val);
                  this.post.id=val;//将app里面的id改掉,那么下面使用了post.id的值就变了
              }
            },
            template:
                `
                    <div id="dd1">
                        <h1>{{ text }}</h1>
                        <h1>我是父组件的id值:{{ post.id }}</h1>
                        <Vheader :msg="text" :msg2="text2" :msg3="post" :msg4="l1" :msg5="text3" @fatherHandler="appFatherHandler"></Vheader>
    
                    </div>
                `
            ,
            components:{
                Vheader,
            }
        };
        let vm = new Vue({
            el: '#app',
            data() {
                return {
                    msg: 'chao'
                }
            },
            created(){
                console.log('>>>>',this) //_uid:0
                //Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
            },
            components: {
                App
            }
        })
    </script>
    </body>
    </html>
    复制代码

    五 平行组件传值

     先看一下什么是平行组件,看图:

      

      平行组件的传值,假如说我们将组件1的数据传递给组件2,那么就需要在组件2中声明一个方法,通过$on来声明,而组件1中要触发一个方法,通过$emit来触发。并且前提是这两个方法要挂载到一个公用的方法上,比较懵逼是不是,你想,在组件1中声明的方法,在组件2中能用吗,是不是不能用啊,所以我们需要一个公用的方法,两个组件将$on和$emit都放到这个公用的方法上,而不是绑定给某个组件的this对象上,说了半天都是废话,直接看代码吧:   

    复制代码
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
    
    <div class="app">
        <App />
    
    </div>
    
    <script src="vue.js"></script>
    <script>
        //第二步:做一个全局的vue对象,这个对象来调用$on和$emit方法,注意:这个vue对象和下面的那个vue对象不是一个对象昂,两个的内存地址是不同的,你现在相当于实例化了两个vue对象,但是这个vue对象只是单纯的作为平行组件传值的一个公交车
        let bus = new Vue();
    
    
        //下面的组件,我们通过平行组件传值的方式来搞,下面写了两个全局组件来演示平行组件Test和Test2,我想将Test组件中的数据传递给Test2,Test-->Test2,那么Test2中要通过$on来声明事件$on('事件的名字',function(val){}),Test组件中要触发事件$emit('Test组件中声明的事件',值),前提是,这两个方法$on和$emit必须绑定在同一个实例化对象中,一般称这个实例化对象为bus对象,公交车对象
        Vue.component('Test2',{
           data(){
               return{
                   msg:'Test2数据',
                   text:'',
               }
           } ,
            //使用一下Test组件传递过来的数据
            template:`
               <h1>{{ text }}</h1>
            `,
            methods:{
               clickHandler(){
    
               }
            },
            //可以在created方法中接收Test组件传过来的数据
            created(){
               //声明事件,现在并没有调用,只有下面的那个Test组件里面的按钮点击事件触发才会调用这个事件
               // this.$on('TestData',function (val) { 通过this绑定是不行的,两个组件之间没有关系
               // bus.$on('TestData',function (val) {
               //      alert(val);
               //      this.text = val; //现在想给Test2组件里面的text数据属性传值,直接这样写是不行的因为this现在指向的是bus那个vue对象,所以this的指向需要变化,所以我们需要用箭头函数来改变this的指向
               // })
                bus.$on('TestData', (val) => {
                    alert(val);
                    this.text = val; //this现在只的是bus那个vue对象,所以this的指向需要变化,所以我们需要用箭头函数来改变this的指向
               })
            }
        });
        Vue.component('Test',{
           data(){
               return{
                   msg:'我是子组件Test的数据',
    
               }
           } ,
            props:['txt'],//下面<Test txt="chao"></Test>这种写法的静态传值
            //通过点击这个按钮,把子组件的数据传递给下面的Vheader组件
            template:`
                <!--<button @click="clickHandler">{{ txt }}</button>-->
                <button @click="clickHandler">{{ txt }}</button>
            `,
            methods:{
               clickHandler(){
                    // this.$emit('TestData',this.msg);通过this绑定是不行的,两个组件之间没有关系
                    bus.$emit('TestData',this.msg)
               }
            },
    
        });
    
        let Vheader = {
            data(){
                return{
                    txt:'Jaden', //动态的给下面的Test组件传值,注意<Test txt="chao"></Test>txt前面没有:的是静态传值的方式
                }
            },
            template:`
                <div class="header">
                    <!--<Test txt="chao"></Test>-->
                    <Test :txt="txt"></Test>
    
                    <!--<Test></Test>-->
                    <Test2></Test2>
    
                </div>
            `
        };
    
        let App={
            data(){
                return{
    
                }
            },
            template:`
                <div class="xxx">
                    <Vheader></Vheader>
    
                </div>
    
            `,
            components:{
                Vheader
            }
        };
        new Vue({
            el:'.app',
            data(){
                return{
    
                }
            },
            //挂载
            components:{
                App
            }
    
        })
    
    
    </script>
    
    </body>
    </html>
    代码改变了我们,也改变了世界
  • 相关阅读:
    关于集合中的实现细节
    数组与内存控制笔记
    python进阶------进程线程(五)
    python进阶------进程线程(四)
    python进阶------进程线程(三)
    python进阶-------进程线程(二)
    python进阶------进程线程(一)
    python进阶---Python中的socket编程
    Python基础---python中的异常处理
    Python进阶---面向对象第三弹(进阶篇)
  • 原文地址:https://www.cnblogs.com/wencaiguagua/p/13663795.html
Copyright © 2020-2023  润新知