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

被折叠的 条评论
为什么被折叠?



