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>
posted @ 2022-10-21 17:53  CJc_3103  阅读(491)  评论(0编辑  收藏  举报