起因:
- 最近做项目时使用了select标签,希望能更改select的样式,期望效果是select和option的背景都能透明,没有边框。期望效果是option也能透明,于是写了这样的css,
select {
border-color: transparent;
background: transparent;
outline: none;
color: #fff;
option {
color: black;
opacity: 0;
background-color: transparent;
appearance: none;
}
}
- 效果并不理想,如图所示,option 的样式没有生效,网上也没有搜到解决方案,有人说option的样式没法修改,抓耳挠腮了好一阵,决定自己封装一个select标签组件。
封装简版select组件:
- 这个版本的select是一个整体的组件,父组件传入数据数组,select组件依次展示。
- select.vue
<template>
<div id="myselelct">
<div class="title" @click="showOption=!showOption">
<span>{{ choiceTitle }}</span>
<span class="icon">﹀</span>
</div>
<div v-show="showOption">
<div v-for="ch in options" :key="ch.key" @click="changeOption(ch.key)">
{{ ch.text }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
options: { // 接收父组件传入的所有选项
type: Array,
default() {
return []; // 接收数据格式必须为:[{key:1,text:"hahha"}]
},
},
value: { // 接收父组件传入的当前值,名字必须value,这样父组件才能用v-model绑定
type: String,
default: "",
},
},
data() {
return {
showOption: false,// 控制是否显示下拉选框
};
},
computed: {
choiceTitle() { // 当前选中的title
let curOption = this.options.filter(item=>item.key == this.value)
if (curOption.length > 0) return curOption[0].text
return "请选择";
},
},
methods: {
changeOption(val) {
this.$emit("input", val); // 传给父组件的事件名必须是input,这样才能用v-model绑定
this.showOption = false; // 收起下拉框
},
},
};
</script>
<style lang="less" scoped>
#myselelct {
display: inline-block;
padding: 5px;
cursor: default;
.title {
.icon {
margin-left: 5px;
vertical-align: middle;
}
}
}
</style>
使用:
- 只需要传入所有选项(:options="type"),与当前选中项即可(v-model="choiceType")
<my-select class="myselect" :options="type" v-model="choiceType" />
...
<style lang="less" scoped>
.myselect {
position: absolute;
z-index: 10;
color: #fff;
}
</style>
- 这就实现预期效果了:
封装完select组件,突然想起elementui不就有这个组件吗,决定搞一个简版的el-slelect组件
参考element-ui,设计select与option组件
element ui的select使用
- v-model中的value就是当前被选中的el-option的 value 属性值
<template>
<el-select v-model="value" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
难点与思路:
- 组件
el-select里面需要插入el-option- 解决方案:使用插槽
el-select如何获取插槽中el-option组件的数据,或者怎么判断当前选中的是哪一个选项- 两个组件是嵌套关系,并非父子关系,不能使用
props,推荐使用provide和inject - 将el-select的
this传递给el-option,在el-option中就可以操作el-select
- 两个组件是嵌套关系,并非父子关系,不能使用
// provide和inject的简单使用,详细使用参考官网
// 外层组件
export default {
provide() {
return {
data: '这是外层组件的数据',
};
},
}
// 内层组件
export default {
inject: ["data"],
}
- 使用时需要
v-model绑定- 这涉及到v-model原理,子组件在
props接收一个value,并向父组件传递一个input自定义事件
- 这涉及到v-model原理,子组件在
代码实现:
select组件
- ySelect.vue
<template>
<div id="select" class="y-select" :style="{ width: width + 'px' }">
<div class="title">
<span v-if="!curLabel" class="placeholder">{{ placeholder }}</span>
<span v-else>{{ curLabel }}</span>
<span class="icon" @click="showOption">﹀</span>
</div>
<div class="option" v-show="isShow">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
placeholder: { // 暗字
type: String,
default: "请选择",
},
value: { // 接收父组件传入的value,名字必须value,这样父组件才能用v-model绑定
type: [Number, String],
},
width: { // 宽度
type: Number,
default: 200,
},
},
// 向插槽中的组件传入数据,传入当前组件的this,在option组件中可以直接访问
provide() {
return {
select: this,
};
},
data() {
return {
isShow: false, // 是否下拉
curLabel: "", // 当前选中的文字
curValue: "", // 当前选中的value
options: [], // 存储插值中的所有option,在option组件的created时push
};
},
mounted() {
// 与父组件传入的value对比,确定当前选中的curvalue
let curOption = this.options.filter((item) => item.value === this.value);
if (curOption.length > 0) {
this.curLabel = curOption[0].label;
this.curValue = curOption[0].value;
}
},
methods: {
showOption() { // 点击下拉按钮,收起/展开options
this.isShow = !this.isShow;
},
},
watch: {
// 当前选中项变化,触发input事件,父元素既可以使用v-model监听
curValue() {
this.$emit("input", this.curValue);
},
},
};
</script>
<style scoped>
.y-select {
color: #666;
display: inline-block;
}
.title {
display: flex;
justify-content: space-between;
padding: 3px 6px;
border: 1px solid #666;
color: #666;
}
.title span {
/* 一行显示,超长省略号 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.placeholder {
color: #bbb;
}
.icon {
cursor: pointer;
}
</style>
option组件
- yOptions.vue
<template>
<div id="option" class="y-option" ref="option">
<div class="content" @click="clickItem">
{{ label }}
</div>
</div>
</template>
<script>
export default {
props: ["label", "value"], // label是选项文字,vaue是选项值
inject: ["select"], // 接收select组件中的provide数据,"select"就是父组件的this
created() {
// 将自身的label和value,push到给select父组件的options中
this.select.options.push({
label: this.label,
value: this.value,
});
},
methods: {
clickItem() {
// 改变select中当前选中的元素
this.select.curValue = this.value;
this.select.curLabel = this.label;
// 调用select中的方法,收起下拉选框
this.select.showOption();
},
},
};
</script>
<style scoped>
.content {
color: #666;
border-right: 1px solid #666;
border-left: 1px solid #666;
border-bottom: 1px solid #666;
padding: 5px 30px 5px 6px;
text-align: left;
cursor: pointer;
/* 一行显示,超长省略号 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
使用
- app.vue
<template>
<div id="app">
<y-select v-model="myValue">
<y-option
v-for="item in options"
:label="item.label"
:value="item.key"
:key="item.key"
></y-option>
</y-select>
</div>
</template>
<script>
import ySelect from "./components/ySelect.vue";
import yOption from "./components/yOption.vue";
export default {
components: { yOption, ySelect },
data() {
return {
myValue: "key5",
options: [
{ key: "key1", label: "选项1" },
{ key: "key2", label: "选项2" },
{ key: "key3", label: "选项3" },
{ key: "key4", label: "选项4changhcadfjadfajhjfkjg" },
{ key: "key5", label: "选项5" },
{ key: "key6", label: "选项6" },
{ key: "key7", label: "选项7" },
],
};
},
};
</script>
效果图
关于样式修改,可以在父组件中,使用 .y-select 或 .y-option进行修改,这里就不作阐述了。