vue2 版通用单选组(可通过插槽自定义选项内容)
简介
本文介绍一种适用于 vue2 框架下的通用单选组组件。与单选框组不同,该组件可通过插槽自定义选项的内容,比如添加图片、html 元素、自定义组件等,相当于基础组件的基础组件。
思路借鉴了 element-ui 的单选框组实现思路,分为 RadioList 和 RadioListItem 两个组件。内部使用 ul/dl 和 li/dd 以及 dt 等标签代替 div,满足前台需要保留语义化标签的要求。
使用时也类似单选框组,只需要双向绑定一个变量即可(:value.sync),组件内部会自动维护该变量的更新。如果需要在切换时执行其他任务,可以绑定 handleSelect 事件。
使用样式时,与常规开发相同,在 RadioList 和 RadioListItem 上绑定 class 即可,类名会自动绑定到 ul/dl 和 li/dd 元素上。也可以通过 prop 传递一个常用属性,如 activeColor 用于激活状态的颜色、gap 用于 item 的间隔(包括水平和垂直排列)。两个组件内部都只有一层元素,样式修改也很方便。
使用示例
<RadioList
:value.sync="activeTab"
:gap="5"
class="tab-list"
@handleSelect="handleTabSelect"
>
<RadioListItem
class="tab-item"
:value="i"
v-for="(tab, i) in tabList"
:key="i"
>{{tab}}</RadioListItem>
</RadioList>
代码示例
RadioList
视图
视图部分比较简单,就是一个列表元素。
<template>
<ul
v-if="_isUnorderedList"
class="radio-list"
:style="{ '--direction': direction }"
>
<slot></slot>
</ul>
<dl v-else class="radio-list" :style="{ '--direction': direction }">
<slot name="title">
<dt class="radio-list_title" :class="titleClass">{{ title }}</dt>
</slot>
<div class="radio-list_wrap">
<slot></slot>
</div>
</dl>
</template>
数据和逻辑
这里首先与 element-ui 相同,引入自定义的 emitter ,方便事件派发。同时引入了一个自定义的颜色检查函数,用于检查传入的 activeColor 属性是否属于合法的 css 颜色值。
emitter 源码可以直接看 element-ui 文件;validateCssColor 函数可以看这个链接:https://blog.csdn.net/A_linyuan/article/details/108191740
RadioList 组件会侦听子组件的 RadioItemSelect 事件,并继续向外派发两个自定义事件 update:value 和 handleSelect:前者用于更新业务组件中双向绑定的变量,后者用于处理额外任务,比如将该组件用于二次开发标签页组件,则通过插槽传递给 RadioListItem 的“标签体”可以通过这个事件触发切换。
业务组件中用于接收双向绑定时,可以通过 .sync 修饰符来修饰 value 变量,如: <RadioList :value.sync="activeNavTab"></RadioList>
,此处的 activeNavTab
变量会在选中子项后自动更新。
import Emitter from '../../mixins/emitter';
import validateCssColor from '@/utils/validator/checkIsColor';
export default {
name: 'RadioList',
componentName: 'RadioList',
mixins: [Emitter],
provide() {
return {
isUnorderedList: this._isUnorderedList,
isDefinitionList: this._isDefinitionList,
rowGap: this._rowGap,
columnGap: this._columnGap,
activeColor: this.activeColor,
lazy: this.lazy,
};
},
props: {
// list props
value: {
type: [Number, String],
require: true,
},
listType: {
type: String,
default: 'unordered',
validator(value) {
return ['unordered', 'definition'].includes(value);
},
},
// onclick trigger or onmouseenter trigger, default onclick
lazy: {
type: Boolean,
default: true,
},
// flex-direction
direction: {
type: String,
default: 'row',
validator: function (value) {
return ['row', 'row-reverse', 'column', 'column-reverse'].includes(
value
);
},
},
// list-item_title props
title: {
type: String,
default: '',
},
titleClass: {
type: String,
default: '',
},
// listItem props
gap: {
type: Number,
default: 0,
},
activeColor: {
type: String,
validator(value) {
return validateCssColor(value);
},
},
},
computed: {
_isUnorderedList() {
return this.listType === 'unordered';
},
// _isDefinitionList() {
// return this.listType === 'definition';
// },
_rowGap() {
return this.direction.indexOf('column') > -1 ? this.gap : 0;
},
_columnGap() {
return this.direction.indexOf('row') > -1 ? this.gap : 0;
},
},
created() {
this.$on('RadioItemSelect', (value) => {
this.$emit('update:value', value);
this.$emit('select', value);
});
},
};
样式
默认是 Flex 布局。
<style lang="scss" scoped>
ul.radio-list,
dl > .radio-list_wrap {
display: flex;
flex-direction: var(--direction);
}
</style>
RadioListItem
视图
子组件的视图结构也很简单,根据列表类型不同会展示 li 或 dd 标签、默认只展示绑定的 value 值,但可以通过插槽修改内容。
<template>
<li
v-if="isUnorderedList"
class="radio-list-item"
:class="{ active: isActive }"
:style="{
'--row-gap': rowGap + 'px',
'--column-gap': columnGap + 'px',
'--active-color': activeColor,
}"
@mouseenter="lazy ? () => {} : handleSelect()"
@click="lazy ? handleSelect() : () => {}"
>
<slot>{{value}}</slot>
</li>
<dd
v-else
class="radio-list-item"
:class="{ active: isActive }"
:style="{
'--row-gap': rowGap + 'px',
'--column-gap': columnGap + 'px',
'--active-color': activeColor,
}"
@mouseenter="lazy ? () => {} : handleSelect()"
@click="lazy ? handleSelect() : () => {}"
>
<slot>{{value}}</slot>
</dd>
</template>
数据与逻辑
子组件接收绑定的 value 值,并根据 lazy 值选择是绑定 click 事件或是 mouseenter 事件,默认是 click 。
用户选中时,触发自定义事件 RadioItemSelect;同时,计算属性自动判断并更新所有子项的 active 状态。
import Emitter from '../../mixins/emitter';
export default {
name: 'RadioListItem',
componentName: 'RadioListItem',
mixins: [Emitter],
inject: {
isUnorderedList: {
default: true,
},
// isDefinitionList: {
// default: false,
// },
rowGap: {
default: 0,
},
columnGap: {
default: 0,
},
lazy: {
default: true,
},
activeColor: {
default: 'inherit',
},
},
props: {
value: [Number, String],
},
computed: {
_isTriggerClick() {
return this.trigger === 'click';
},
_isTriggerHover() {
return this.trigger === 'click';
},
isActive() {
return this._radioList.value === this.value;
},
_radioList() {
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'RadioList') {
parent = parent.$parent;
} else {
return parent;
}
}
return false;
},
},
methods: {
handleSelect() {
this.$nextTick(() => {
this.dispatch('RadioList', 'RadioItemSelect', this.value);
});
},
},
};
样式
<style lang="scss" scoped>
.radio-list-item {
&:not(:last-child) {
margin-top: var(--row-gap);
margin-right: var(--column-gap);
}
&.active {
color: var(--active-color);
}
}
</style>