Vue3选择器(Select)

Vue2选择器(Select)

可自定义设置以下属性:

  • 选项数据(options),类型:Array<{label?: string, value?: any, disabled?: boolean}>,默认值[]

  • 选择器字典项的文本字段名(label),类型:string,默认 'label'

  • 选择器字典项的值字段名(value),类型:string,默认 'value'

  • 选择框默认文字(placeholder),类型:string,默认 '请选择'

  • 是否禁用下拉(disabled),类型:boolean,默认 false

  • 是否支持清除(allowClear),类型:boolean,默认false

  • 选择框宽度(width),类型:number,默认200px

  • 选择框高度(height),类型:number,默认36px

  • 下拉面板最多能展示的下拉项数,超过后滚动显示(num),类型:number,默认 6

  • 当前选中的option条目(v-model:selectedValue),类型:string | number,默认 null

效果如下图:

 

 ①创新选择器组件Select.vue:

<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
const props = defineProps({
    options: { // 选项数据
      type: Array<any>,
      default: () => []
    },
    label: { // 选择器字典项的文本字段名
      type: String,
      default: 'label'
    },
    value: { // 选择器字典项的值字段名
      type: String,
      default: 'value'
    },
    placeholder: { // 选择框默认文字
      type: String,
      default: '请选择'
    },
    disabled: { // 是否禁用下拉
      type: Boolean,
      default: false
    },
    allowClear: { // 是否支持清除
      type: Boolean,
      default: false
    },
    width: { // 选择框宽度
      type: Number,
      default: 200
    },
    height: { // 选择框高度
      type: Number,
      default: 36
    },
    num: { // 下拉面板最多能展示的下拉项数,超过后滚动显示
      type: Number,
      default: 6
    },
    selectedValue: { // 当前选中的option条目(v-model)
      type: [Number, String],
      default: null
    }
  })
const selectedName = ref()
const hoverValue = ref() // 鼠标悬浮项的value值
const showOptions = ref(false) // options面板
const activeBlur = ref(true) // 是否激活blur事件
const showClose = ref(false) // 清除按钮显隐
watch(
  () => props.options,  
  (to) => {
  initSelector()
  // console.log('options:', to)
})
watch(
  () => props.selectedValue,  
  (to) => {
  initSelector()
  // console.log('selectedValue:', to)
})
onMounted(() => {
  initSelector()
})
const emits = defineEmits(['update:selectedValue', 'change'])
function initSelector () {
  if (props.selectedValue) {
    const target = props.options.find(option => option[props.value] === props.selectedValue)
    if (target) {
      selectedName.value = target[props.label]
      hoverValue.value = target[props.value]
    } else {
      selectedName.value = props.selectedValue
      hoverValue.value = null
    }
  } else {
    selectedName.value = null
    hoverValue.value = null
  }
}
function onBlur () {
  // console.log('blur')
  if (showOptions.value) {
    showOptions.value = false
  }
}
function onInputEnter () {
  // console.log('input enter')
  if (props.allowClear && selectedName.value) {
    showClose.value = true
  }
}
function onInputLeave () {
  // console.log('input leave')
  if (props.allowClear && showClose.value) {
    showClose.value = false
  }
}
function onHover (value: string|number) {
  hoverValue.value = value
}
function onEnter () {
  activeBlur.value = false
}
function onLeave () {
  hoverValue.value = null
  activeBlur.value = true
}
function openSelect () {
  showOptions.value = !showOptions.value
  if (!hoverValue.value && selectedName.value) {
    const target = props.options.find(option => option[props.label] === selectedName.value)
    hoverValue.value = target ? target[props.value] : null
  }
}
function onClear () {
  showClose.value = false
  selectedName.value = null
  hoverValue.value = null
}
function onChange (value: string|number, label: string, index: number) { // 选中下拉项后的回调
  if (props.selectedValue !== value) {
    selectedName.value = label
    hoverValue.value = value
    emits('update:selectedValue', value)
    emits('change', value, label, index)
  }
  showOptions.value = false
}
</script>
<template>
  <div class="m-select" :style="`height: ${height}px;`">
    <div
      :class="['m-select-wrap', {'hover': !disabled, 'focus': showOptions, 'disabled': disabled}]"
      :style="`width: ${width - 2}px; height: ${height - 2}px;`"
      tabindex="0"
      @mouseenter="onInputEnter"
      @mouseleave="onInputLeave"
      @blur="activeBlur && !disabled ? onBlur() : (e: Event) => e.preventDefault()"
      @click="disabled ? (e: Event) => e.preventDefault() : openSelect()">
      <div
        :class="['u-select-input', {'placeholder': !selectedName}]"
        :style="`line-height: ${height - 2}px;width: ${width - 37}px; height: ${height - 2}px;`"
        :title="selectedName"
      >{
   
   { selectedName || placeholder }}</div>
      <svg :class="['triangle', {'rotate': showOptions, 'show': !showClose}]" viewBox="64 64 896 896" data-icon="down" aria-hidden="true" focusable="false"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg>
      <svg @click.stop="onClear" :class="['close', {'show': showClose}]" focusable="false" data-icon="close-circle" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg>
    </div>
    <Transition name="fade">
      <div
        v-show="showOptions"
        class="m-options-panel"
        @mouseenter="onEnter"
        @mouseleave="onLeave"
        :style="`top: ${height + 6}px; line-height: ${height - 12}px; max-height: ${ num * (height - 2) }px; width: ${width}px;`">
        <p
          v-for="(option, index) in options" :key="index"
          :class="['u-option', {'option-selected': option[label]===selectedName, 'option-hover': !option.disabled&&option[value]===hoverValue, 'option-disabled': option.disabled }]"
          :title="option[label]"
          @mouseenter="onHover(option[value])"
          @click="option.disabled ? (e: Event) => e.preventDefault() : onChange(option[value], option[label], index)">
          {
   
   { option[label] }}
        </p>
      </div>
    </Transition>
  </div>
</template>
<style lang="less" scoped>
.m-select {
  position: relative;
  display: inline-block;
  font-size: 14px;
  font-weight: 400;
  color: rgba(0,0,0,.65);
}
.fade-enter-active, .fade-leave-active {
  transform: scaleY(1);
  transform-origin: 0% 0%;
  opacity: 1;
  transition: all .3s;
}
.fade-enter-from {
  transform: scaleY(0.8);
  transform-origin: 0% 0%;
  opacity: 0;
}
.fade-leave-to {
  transform: scaleY(1);
  opacity: 0;
}
.m-select-wrap {
  position: relative;
  display: inline-block;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  cursor: pointer;
  transition: all .3s cubic-bezier(.645,.045,.355,1);
  .u-select-input {
    display: block;
    text-align: left;
    margin-left: 11px;
    margin-right: 24px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .placeholder {
    color: #bfbfbf;
  }
  .triangle {
    position: absolute;
    top: 0;
    bottom: 0;
    margin: auto 0;
    right: 12px;
    width: 12px;
    height: 12px;
    fill: rgba(0,0,0,.25);
    opacity: 0;
    pointer-events: none;
    transition: all 0.3s ease-in-out;
  }
  .rotate {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
  }
  .close {
    opacity: 0;
    pointer-events: none;
    transition: all 0.3s ease-in-out;
    position: absolute;
    top: 0;
    bottom: 0;
    margin: auto 0;
    right: 12px;
    width: 12px;
    height: 12px;
    fill: rgba(140, 140, 140, 0.6);
    &:hover {
      fill: rgba(100, 100, 100,.8);
    }
  }
  .show {
    opacity: 1;
    pointer-events: auto;
  }
}
.hover { // 悬浮时样式
  &:hover {
    border-color: @themeColor;
  }
}
.focus { // 激活时样式
  border-color: @themeColor;
  box-shadow: 0 0 0 2px fade(@themeColor, 20%);
}
.disabled { // 下拉禁用样式
  color: rgba(0,0,0,.25);
  background: #f5f5f5;
  user-select: none;
  cursor: not-allowed;
}
.m-options-panel {
  position: absolute;
  z-index: 999;
  overflow: auto;
  background: #FFF;
  padding: 4px 0;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,15%);
  .u-option { // 下拉项默认样式
    text-align: left;
    position: relative;
    display: block;
    padding: 5px 12px;
    font-weight: 400;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    cursor: pointer;
    transition: background .3s ease;
  }
  .option-selected { // 被选中的下拉项样式
    font-weight: 600;
    background: #fafafa;
  }
  .option-hover { // 悬浮时的下拉项样式
    // background: #e6f7ff;
    background: saturate(fade(@themeColor, 12%), 30%);
  }
  .option-disabled { // 禁用某个下拉选项时的样式
    color: rgba(0,0,0,.25);
    user-select: none;
    cursor: not-allowed;
  }
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import { Select } from './Select.vue'
import { ref, watch, onMounted } from 'vue'
import { rafTimeout } from '../../packages' // 使用requestAnimationFrame封装的等效setTimeout

const options = ref([
      {
        label: '北京市',
        value: 1
      },
      {
        label: '上海市上海市上海市上海市',
        value: 2,
        disabled: true
      },
      {
        label: '郑州市',
        value: 3
      },
      {
        label: '纽约市纽约市纽约市纽约市',
        value: 4
      },
      {
        label: '旧金山',
        value: 5
      },
      {
        label: '悉尼市',
        value: 6
      },
      {
        label: '伦敦市',
        value: 7
      },
      {
        label: '巴黎市',
        value: 8
      }
    ])
const selectedValue = ref(1)

watch(selectedValue, (to) => {
  console.log('selectedValue:', to)
})
onMounted(() => {
  rafTimeout(() => { // 模拟接口调用
    selectedValue.value = 3
  }, 1000)
})
function onChange (value: string|number, label: string,  index: number) {
  console.log('item:', value, label, index)
}
</script>
<template>
  <div>
    <h2 class="mb10">Select 选择器基本使用</h2>
    <Select
      :options="options"
      label="label"
      value="value"
      placeholder="请选择城市"
      :disabled="false"
      :width="160"
      :height="36"
      :num="6"
      v-model:selectedValue="selectedValue"
      @change="onChange" />
    <h2 class="mt30 mb10">Select 选择器支持清除(allowClear)</h2>
    <Select
      :options="options"
      label="label"
      value="value"
      placeholder="请选择城市"
      :disabled="false"
      :width="160"
      :height="36"
      :num="6"
      allowClear
      v-model:selectedValue="selectedValue"
      @change="onChange" />
  </div>
</template>
<style lang="less" scoped>
</style>

猜你喜欢

转载自blog.csdn.net/Dandrose/article/details/129874094