Vue组件(一):评分、搜索、菜单、轮播、分页等

Vue组件实践:评分、搜索、菜单、轮播与分页
本文介绍了如何在Vue中创建组件,包括评分、搜索、菜单、轮播和分页组件。通过实例展示了组件化的优势,如避免代码重复,提高代码复用率。详细讲解了各个组件的实现过程,如评分组件的鼠标交互逻辑,搜索组件的v-model绑定,菜单组件的内容切换,轮播组件的自动切换与手动控制,以及分页组件的数据切割和页码控制。并强调了v-model在简化组件内外数据传递中的作用。

Vue组件

在做项目的时候经常会写功能一样,只是写入的数据不一样的元素,为了避免重复书写,会将这些元素做成组件,例如菜单、分页、轮播图等。
由于目前还是用的是VScode软件,写组件的模板没有提示,需要手动书写,所以对于刚刚尝试写组件的新手,可以现在html文件中先写出组件的元素以及需要实现的功能。

评分组件

以评分作为例子:
做评分组件的几个元素:
1、总分有几颗星?
2、初始时有没有默认星?
3、怎么实现鼠标往后移动的时候星星被点亮,向前移开的时候星星不亮。鼠标离开评分,恢复默认值
4、鼠标点击的时候,实现评分,鼠标移开后分数不变

首先写出html文件,星星图案是iconfont,对星星设置颜色,星星图案分为,实心和空心的

 <link rel="stylesheet" href="https://at.alicdn.com/t/font_1743185_37l8tzue6bb.css">
<style>
    [v-cloak] {
        display: none;
    }
    .star{
        font-size: 30px;
        color: red;
    }
</style>

首先通过v-for循环,设置总分,count在js中传入,为了实现鼠标离开恢复默认星数,这时候引入一个临时变量tempScore,鼠标移动时将数据赋值给这个变量,真正实现评分的变量时score。初始化时tempScore和score值一样,当移动时经过判断给予亮星。
count为数字,item为1-10数据

 <div id="app" v-cloak>
    <i v-for='item in count' class="iconfont star " 
     @mouseenter='tempScore=item'
     @mouseleave='tempScore=score'
     @click='score=item'
     :class="item<=tempScore?'iconstar1':'iconstar'"></i>
 </div>

JS文件

<script>
   new Vue({
        el: '#app',
        data() {
            return {
                count:10,//总分时星星的个数
                score:1,//实际改变亮星
                tempScore:1,//临时储存亮星
            }
        },
    })
</script>

此时一个评分功能实现成功,如果有好几个评分,此时需要对html文件中的内容复制几遍,在js中得多设置几个变量,这样子js会多出很多变量,不简洁,此时需要将评分做成组件。
首先将原先的html部分的内容写入template模板中,但是由于内部不能直接调用外部变量,需要在组件内部,设置自己的变量,对自己内部变量进行赋值,再将数据传出去。
对于需要从外部传入的数据写入props中。

 <div id="app" v-cloak>
   <p> 客服态度:{{kefu}}分,物流速度{{wuliu}}分,快递质量{{kuaidi}}</p>
    <star label='客服态度' :count='10' :score='4' @change='setkefu'></star>
    <star label='物流速度' :count='5' :score='2' color='yellow' @change='setwuliu'></star>
    <star label='快递质量' :count='10' :score='3' color='green' @change='setkuaidi'></star>

</div>
<script>
    Vue.component('star', {
        props: ['label','count','score','color'],//传输参数
        template: `
        <div>
            <span> {{label}}</span> <i v-for='item in count' :style="{color:color}" 
            class="iconfont star " 
            @click='sj_score=item'
            @mouseenter='tempScore=item'
            @mouseleave='tempScore=sj_score'
            :class="item<=tempScore?'iconstar1':'iconstar'"></i>
        </div>`,
        data() {
            return {
                tempScore:this.score,
                sj_score:this.score,
            }
        },
        watch: {
            sj_score(val){//对实现评分的变量进行监听
                this.$emit('change',val)
                //子组件通过$emit触发父组件的自定义事件,将变量传出给父组件
            }
        },

    }
    new Vue({
        el: '#app',
        data() {
            return {
                kefu:0,
                wuliu:0,
                kuaidi:0,
            }
        },
        methods: {//在父组件中将得到的值载赋值给每一个组件中实现评分的变量。
            setkefu(score){
                this.kefu=score
                console.log('客服态度:'+this.kefu);
            },
            setwuliu(score){
                this.wuliu=score
                console.log('物流速度:'+this.wuliu);
            },
            setkuaidi (score){
                this.kuaidi=score
                console.log('快递质量:'+this.kuaidi );
            }
        },
    })
</script>

但是对组件都需要设置事件才能获取到数据,js代码中有不少重复,此时可以用v-model来简化代码,v-model的使用是利用input标签的value和input事件来实现的。
可以对上述的代码进行简化:对进行评分的变量进行双向绑定

 <div id="app" v-cloak>
   <p> 客服态度:{{kefu}}分,物流速度{{wuliu}}分,快递质量{{kuaidi}}</p>
     <star label='客服态度' :count='10' v-model='kefu'></star>
     <star label='物流速度' :count='5'  color='yellow' v-model='wuliu'></star>
     <star label='快递质量' :count='10'  color='green' v-model='kuaidi'></star>
 </div>

在js中传入value数据,并对内部的评分变量进行赋值,最后将$emit的自定义事件变成input。这样子就不用再Vue中在进行传值操作

<script>
        Vue.component('star', {
            props: ['label','count','value','color'],//传输参数
            template: `
            <div>
                <span> {{label}}</span> <i v-for='item in count' :style="{color:color}" 
                class="iconfont star " 
                @click='sj_score=item'
                @mouseenter='tempScore=item'
                @mouseleave='tempScore=sj_score'
                :class="item<=tempScore?'iconstar1':'iconstar'"></i>
            </div>`,
            data() {
                return {
                    tempScore:this.value,
                    sj_score:this.value,
                }
            },
            watch: {
                sj_score(val){
                    this.$emit('input',val)
                }
            },

        })

        new Vue({
            el: '#app',
            data() {
                return {
                    kefu:2,
                    wuliu:0,
                    kuaidi:0,
                }
            },
           
        })
    </script>

搜索组件

以下皆使用v-model的方式进行内外绑定传值。
页面布局

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

    .searcher {
        display: flex;
        width: 200px;
        border: 1px solid red;
    }

    .searcher input {
        flex: 1;
        outline: none;
        text-indent: 8px;
        border: none;
    }

    .searcher button {
        width: 30px;
        height: 30px;
        border: none;
        cursor: pointer;
        background: url('https://img3.doubanio.com/f/sns/f71f15922ebd7c0ff0ea0e7a25577529efd8981a/pics/icon/bn_srh_1.png') center no-repeat;
    }
</style>
<div id="app" v-cloak>
    {{text}}
    <search placeholder='书籍' v-model='text'></search>
    <search ></search>
</div>
<script>
    Vue.component('search', {
        props:{
            value:{
                type:String
            },
            placeholder:{
                type:String,//类型是字符串
               // required:true,//必传
                default:'请输入内容',//不传参数,默认显示内容
                //自定义验证器
                // validator(val){
                //     return ['书籍','电影'].includes(val)
                // }
            }
        },
         data() {
           return {
               keywords:this.value,
           }
       },
       //组件中的元素是输入框和button按钮,当按钮点击时将文本框中的值传出去。
        template: ` 
        <div class="searcher">
        <input type="text" :placeholder='placeholder' v-model='keywords'>
        <button @click='dianji'></button>
        </div>`,
        methods: {
            dianji(){
                this.$emit('input',this.keywords)
                this.keywords=''
            }
        },
    })

    new Vue({
        el: '#app',
        data() {
            return {
                text:'haha',
            }
        },
    })
</script>

菜单组件

<style>
    [v-cloak] {
        display: none;
    }
    *{
        margin: 0;
        padding: 0;
        list-style: none;
    }
    .menu{
        display: flex;
    }
    .menu li{
        padding: 20px 30px;
        cursor: pointer;
        user-select: none;
    }
    .menu li.active{
        background-color: brown;
        color: white;
    }
</style>

在菜单组件中需要传入的数据是菜单的标题和标题下的内容,点击时通过下标进行内容变化。

<div id="app" v-cloak>
    {{cityIndex}}----{{typeIndex}}
    <mymenu  label='城市' :list='citylist' v-model='cityIndex'></mymenu>
    <mymenu  label='类型' :list='typelist'  v-model='typeIndex'></mymenu>
</div>
<script>
    Vue.component('mymenu',{
        props:['label','list' ,'value'],
        template:`
        <ul class="menu">
            <li>{{label}}:</li>
            <li v-for='(item,i) in list' @click='activeIndex=i' :class='{active:activeIndex==i}'>{{item}}</li>
        </ul>
        `,
        data() {
            return {
                activeIndex:this.value,
            }
        },
        watch: {
            activeIndex(val){
                this.$emit('input',val)
            }
        },
    })

    new Vue({
        el: '#app',
        data() {
            return {
                typeIndex:1,
                cityIndex:0,
                citylist:['南京','北京','上海'],
                typelist:['演唱会','综艺','戏曲']
            }
        },
       
    })
</script>

轮播组件

前进后退的箭头可以使用iconfont也可以自己引入图片,以下案例中使用的是iconfont

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

    * {
        margin: 0;
        padding: 0;
        list-style: none;
        user-select: none;
    }

    .swiper {
        width: 1000px;
        height: 400px;
        /* background-color: aqua; */
        margin: 10px auto;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .swiper img {
        width: 100%;
    }

    .swiper ul {
        flex: 1;
    }

    .swiper ul li {
        width: 100%;
    }

    .swiper i {
        display: inline-block;
        width: 5%;
        height: 100px;
        text-align: center;
        line-height: 100px;
        /* background-color: #ccc; */
        color: rgba(0, 0, 0, 0.1);
        font-size: 30px;
    }

    .swiper i:hover {
        background-color: #ccc;
        color: white;
    }
</style>

对于轮播图,需要传入图的数组,以及定时器的时间,还是通过下标来进行图片的切换
以下轮播图实现了通过定时器让图片自动切换,当点击的时候关闭定时器,进行切图,当鼠标离开的时候定时器再次启动。

<div id="app" v-cloak>
    当前是第{{swiperindex+1}}<swiper :list='LBT_img' :time='2000' v-model='swiperindex'></swiper>
</div>
<script>
    Vue.component('swiper', {
        props: ['list','time','value'],
        data() {
            return {
                index: this.value,
                timer: setInterval(this.run, this.time),
            }
        },
        template: `
            <div class="swiper">
                <i class=" iconfont iconhoutui" @click="prev" @mouseleave='start'></i>
                <ul>
                    <li v-for='(item,i) in list' v-show='index===i'>
                        <img :src="item" alt="">
                    </li>
                </ul>
                <i class=" iconfont iconqianjin"  @click='next'  @mouseleave='start'></i>
            </div>
            `,
        methods: {
            run() {
                this.index++
                if (this.index === this.list.length) this.index = 0
            },
            prev() {
                this.index--
                this.index == -1 ? this.index = 4 : false
                clearInterval(this.timer)
            },
            next(){
                this.index++,
                this.index===5?this.index=0:false
                clearInterval(this.timer)
            },
            start() {
                clearInterval(this.timer)
                this.timer = setInterval(this.run, this.time)
            }
        },
        watch: {
            index(val){
                this.$emit('input',val)
            }
        },
    })

    new Vue({
        el: '#app',
        data() {
            return {
                swiperindex: 0,
                LBT_img: [
                    "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/5ade887b241d057d81e2de96590a1a6f.jpg?w=2452&h=920",
                    "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/3a82846d975204e12923de52add19339.jpg?thumb=1&w=1226&h=460&f=webp&q=90",
                    "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/a236efc6d3d8ad1b3ac98f053820e6f1.jpg?thumb=1&w=1226&h=460&f=webp&q=90",
                    "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/ad9c1cb79a2eeb2d5ccf54dfa9782032.jpg?thumb=1&w=1226&h=460&f=webp&q=90",
                    "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/6ef43cf9f138a7fc3a39273d7e3d13b6.jpg?thumb=1&w=1226&h=460&f=webp&q=90"
                ],
            }
        },
})
</script>

分页组件

分页的原理是:通过数据的条数和每一页的条数得到分页的总页数,然后通过下标对数据进行分割。例如:
本案例中页数使用的是ul列表。一页三条数据总共16条,所以通过向上取整获取到总共6页。第一页(下标为0)数据为(1-3),第二页(下标为1)数据为(4-6)…可以得到下面代码的规律。对数据在进行切割。

let start=(this.index)*this.pagesize
let end=start+this.pagesize
this.nowlist=this.datalist.slice(start,end)
<style>
    [v-cloak] {
        display: none;
    }

    * {
        margin: 0;
        padding: 0;
        list-style: none;
        user-select: none;
    }
    .pager {
        width: auto;
        margin: 10px auto;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .pager>div {
        width: 60px;
        height: 41px;
        line-height: 42px;
        text-align: center;
        color: rgb(18, 143, 216);
        margin: 0 3px;
        border-radius: 3px;
        border: 1px solid rgb(18, 143, 216);
    }
    .pager>div.disabled {
        background-color: #ccc;
        color: #fff;
        border: 1px solid #ccc;
        cursor: not-allowed;
    }

    .pager ul {
        display: flex;
        justify-content: center;
        align-items: center;

    }
    .pager ul li {
        padding: 10px 20px;
        border: 1px solid rgb(18, 143, 216);
        color: rgb(18, 143, 216);
        margin: 0 5px;
        border-radius: 3px;
    }
    .pager ul li.active {
        background-color: rgb(18, 143, 216);
        color: white;
    }
</style>

分页的需要传入:数据、每一页几条内容(pagesize)以及数据的内外传输

<div id="app" v-cloak>
    当前是{{index+1}}<div>
        <ul style="width: 400px; margin: 0 auto;">
            <li v-for='item in nowlist'>{{item}}</li>
        </ul>
    </div>
    <pager :datalist='datalist' :pagesize='3' v-model='index' @changelist='getli'></pager>
</div>

默认在第一页,此时上一页和首页被禁用,当为末页时下一页和尾页被禁用。

<script>
    Vue.component('pager', {
        props: ['datalist','pagesize','value'],
        template: `
        <div class="pager">
            <div @click='index=0' :class="{disabled:index===0}">首页</div>
            <div @click='prev' :class="{disabled:index===0}">上一页</div>
            <ul>
                <li v-for='(item,i) in allpage' @click='index=i' :class='{active:index==i}'>{{item}}</li>
            </ul>
            <div @click='next' :class="{disabled:index===(allpage-1)}">下一页</div>
            <div @click='index=(allpage-1) ' :class="{disabled:index===(allpage-1)}">尾页</div>
        </div>
        `,
        data() {
            return {
                page: 1,
                index: this.value,
                allpage: null,
                nowlist:[],
            }
        },
        created() {
            this.getPage()
            this.getlist()
            this.$emit('changelist',this.nowlist)
        },
        methods: {
            prev() {
                if (this.index == 0) return
                this.index--
            },
            next() {
                if (this.index == (this.allpage - 1)) return
                this.index++
            },
            getPage() {
                this.allpage = Math.ceil(this.datalist.length / this.pagesize)
                 console.log(this.pagesize);
               
            },
            getlist(){
                let start=(this.index)*this.pagesize
                let end=start+this.pagesize
                this.nowlist=this.datalist.slice(start,end)
            }
        },
        watch: {
            index(val) {
                this.getlist()
                this.$emit('input', val)
                this.$emit('changelist',this.nowlist)
            },
        },

    })

    new Vue({
        el: '#app',
        data() {
            return {
                index: 0,
                datalist: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,16],
                nowlist: [],
            }
        },
       
        methods: {
            getli(e){
                this.nowlist=e,
                console.log(e);
            }
           
        }

    })
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值