首页 > 其他分享 >uniapp+vue3封装下拉框(支持单选、多选)

uniapp+vue3封装下拉框(支持单选、多选)

时间:2024-07-10 13:29:44浏览次数:19  
标签:uniapp width color value item 单选 border 下拉框 select

子组件代码

<template>
  <view class="uni-select-dc">
    <view
      ref="select"
      class="uni-select-dc-select"
      :class="{ active: active }"
      @click.stop="handleSelect"
    >
      <!-- 禁用mask -->
      <view class="uni-disabled" v-if="disabled"></view>
      <!-- 清空 -->
      <view
        class="close-icon close-postion"
        v-if="realValue.length && !active && !disabled && showClearIcon"
      >
        <text @click.stop="handleRemove(null)"></text>
      </view>
      <!-- 显示框 -->
      <view class="uni-select-multiple" v-show="realValue.length">
        <!-- 多选时展示内容 -->
        <template v-if="multiple">
          <view
            class="uni-select-multiple-item"
            v-for="(item, index) in changevalue"
            :key="index"
          >
            {{ item.text }}
            <view class="close-icon" v-if="showValueClear">
              <text @click.stop="handleRemove(index)"></text>
            </view>
          </view>
        </template>
        <!-- 单选时展示内容 -->
        <view v-else class="single-text">
          {{ changevalue.length ? changevalue[0].text : '' }}
        </view>
      </view>
      <!-- 为空时的显示文案 -->
      <view v-if="realValue.length == 0 && showplaceholder">
        {{ placeholder }}
      </view>
      <!-- 右边的下拉箭头 -->
      <view
        :class="{
          disabled: disabled,
          'uni-select-dc-icon': !downInner,
          'uni-select-dc-inner': downInner,
        }"
      >
        <text></text>
      </view>
    </view>
    <!-- 下拉选项 -->
    <scroll-view class="uni-select-dc-options" :scroll-y="true" v-show="active">
      <template v-if="options.length">
        <view
          class="uni-select-dc-item"
          :class="{ active: realValue.includes((item as any)[svalue]) }"
          v-for="(item, index) in options"
          :key="index"
          @click.stop="handleChange(index, item)"
        >
          {{ (item as any)[slabel] }}
        </view>
      </template>
      <template v-else>
        <view class="uni-select-dc-item">无匹配项</view>
      </template>
    </scroll-view>
  </view>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'

const props = defineProps({
  // 是否显示全部清空按钮
  showClearIcon: {
    type: Boolean,
    default: false,
  },
  // 是否多选
  multiple: {
    type: Boolean,
    default: false,
  },
  // 下拉箭头是否在框内
  downInner: {
    type: Boolean,
    default: true,
  },
  // 是否显示单个删除
  showValueClear: {
    type: Boolean,
    default: true,
  },
  // 禁用选择
  disabled: {
    type: Boolean,
    default: false,
  },
  options: {
    type: Array,
    /**
     * default
     */
    default() {
      return []
    },
  },
  value: {
    type: Array,
    /**
     * default
     */
    default() {
      return []
    },
  },
  // 提示文案
  placeholder: {
    type: String,
    default: '请选择',
  },
  // 是否显示提示文案
  showplaceholder: {
    type: Boolean,
    default: true,
  },
  // 默认取text
  slabel: {
    type: String,
    default: 'text',
  },
  // 默认取value
  svalue: {
    type: String,
    default: 'value',
  },
})
const emit = defineEmits(['change'])
const active = ref<boolean>(false) // 组件是否激活,
let changevalue = reactive<Record<any, any>>([])
let realValue = reactive<Record<string, any>>([])
const select = ref(null)

/**
 * 初始化函数
 */
const init = () => {
  if (props.value.length > 0) {
    props.options.forEach((item) => {
      props.value.forEach((i) => {
        if ((item as any)[props.svalue] === i) {
          changevalue.push(item)
        }
      })
    })
    realValue = props.value
  } else {
    changevalue = []
    realValue = []
  }
}

watch(
  () => props.value,
  () => {
    init()
  },
  { immediate: true },
)

/**
 * 点击展示选项
 */
const handleSelect = () => {
  if (props.disabled) return
  active.value = !active.value
}

/**
 * 关闭下拉框
 */
const closeDropdown = () => {
  if (active.value) {
    active.value = false
  }
}

onMounted(() => {
  document.addEventListener('click', closeDropdown)
})

onUnmounted(() => {
  document.removeEventListener('click', closeDropdown)
})

/**
 * 移除数据
 * @param index index
 */
const handleRemove = (index: any) => {
  if (index === null) {
    realValue = []
    changevalue = []
  } else {
    realValue.splice(index, 1)
    changevalue.splice(index, 1)
  }
  emit('change', changevalue, realValue)
}

/**
 * 点击组件某一项
 * @param index index
 * @param item item
 */
const handleChange = (index: any, item: any) => {
  // 如果是单选框,选中一项后直接关闭
  if (!props.multiple) {
    changevalue.length = 0
    realValue.length = 0
    changevalue.push(item)
    realValue.push(item[props.svalue])
    active.value = !active.value
  } else {
    // 多选操作
    const arrIndex = realValue.indexOf(item[props.svalue])
    if (arrIndex > -1) {
      // 如果该选项已经选中,当点击后就不选中
      changevalue.splice(arrIndex, 1)
      realValue.splice(arrIndex, 1)
    } else {
      // 否则选中该选项
      changevalue.push(item)
      realValue.push(item[props.svalue])
    }
  }
  // 触发回调函数
  emit('change', changevalue, realValue)
}
</script>

<style lang="scss" scoped>
.uni-select-dc {
  position: relative;
  width: 100%;

  .uni-select-mask {
    width: 100%;
    height: 100%;
  }

  /* 删除按钮样式 */
  .close-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 100%;
    cursor: pointer;

    text {
      position: relative;
      width: 26rpx;
      height: 26rpx;
      background: #c0c4cc;
      border: 1px solid #bbb;
      border-radius: 50%;

      &::before,
      &::after {
        position: absolute;
        top: 45%;
        left: 20%;
        width: 60%;
        height: 2rpx;
        background-color: #fff;
        transform: rotate(45deg);
        content: '';
      }

      &::after {
        transform: rotate(-45deg);
      }
    }
  }

  // 所有清空的定位
  .close-postion {
    position: absolute;
    top: 0;
    right: 70rpx;
    width: 30rpx;
    height: 100%;
  }

  /* 多选盒子 */
  .uni-select-multiple {
    display: flex;
    flex-wrap: wrap;

    .single-text {
      color: #333;
    }

    .uni-select-multiple-item {
      display: flex;
      flex-shrink: 0;
      margin: 6rpx 10rpx 6rpx 0;
      padding: 4rpx 8rpx;
      color: #3a3a3a;
      background: #f4f4f5;
      border-radius: 8rpx;
    }
  }

  // select部分
  .uni-select-dc-select {
    position: relative;
    z-index: 3;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    width: 100%;
    min-height: 70rpx;
    padding: 0 60rpx 0 20rpx;
    color: #999;
    font-size: 24rpx;
    border: 1px solid rgb(229, 229, 229);
    border-radius: 8rpx;
    user-select: none;

    .uni-disabled {
      position: absolute;
      left: 0;
      z-index: 1;
      width: 100%;
      height: 100%;
      background-color: #ebf1f67a;
      border-color: #e5e5e5;
      cursor: no-drop;
    }

    .uni-select-dc-input {
      display: block;
      box-sizing: border-box;
      width: 96%;
      overflow: hidden;
      color: #999;
      font-size: 28rpx;
      line-height: 60rpx;
      white-space: nowrap;
      text-overflow: ellipsis;

      &.active {
        color: #333;
      }
    }

    .uni-select-dc-icon {
      position: absolute;
      top: 0;
      right: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 36rpx;
      height: 100%;
      border-left: 1px solid rgb(229, 229, 229);
      cursor: pointer;

      text {
        display: block;
        width: 0;
        height: 0;
        border-color: #bbb transparent transparent;
        border-style: solid;
        border-width: 12rpx 12rpx 0;
        transition: 0.3s;
      }

      &.disabled {
        cursor: no-drop;
      }
    }

    .uni-select-dc-inner {
      position: absolute;
      top: 15%;
      right: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 40rpx;
      height: 100%;
      cursor: pointer;

      text {
        position: absolute;
        top: 12rpx;
        right: 20rpx;
        display: block;
        width: 12rpx;
        height: 12rpx;
        border: 1px solid #999999;
        border-color: transparent transparent#bbb #bbb;
        transform: rotate(-45deg);
        transition: 0.3s;
      }

      &.disabled {
        cursor: no-drop;
      }
    }

    // 激活之后,图标旋转180度
    &.active .uni-select-dc-icon {
      text {
        transform: rotate(180deg);
      }
    }

    &.active .uni-select-dc-inner {
      text {
        position: absolute;
        top: 24rpx;
        right: 20rpx;
        transform: rotate(-225deg);
      }
    }
  }

  // options部分
  .uni-select-dc-options {
    position: absolute;
    top: calc(100% + 5px);
    left: 0;
    z-index: 9;
    box-sizing: border-box;
    width: 100%;
    max-height: 400rpx;
    padding: 10rpx 0;
    background: #fff;
    border: 1px solid rgb(229, 229, 229);
    border-radius: 4px;
    user-select: none;

    .uni-select-dc-item {
      box-sizing: border-box;
      padding: 0 20rpx;
      font-size: 28rpx;
      line-height: 2.5;
      cursor: pointer;
      transition: 0.3s;
      -webkit-user-select: none;
      -moz-user-select: none;
      // 取消长按的背景色
      -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
      -moz-user-focus: none;

      &.active {
        color: #3774c6;
        background-color: #f5f7fa;

        &:hover {
          color: #3774c6;
          background-color: #f5f7fa;
        }
      }

      &:hover {
        background-color: #f5f5f5;
      }
    }
  }
}
</style>

父组件代码

const monIndex = ref([0])
const changeValue = (item: any, value: any) => {
  monIndex.value = value
}


 <select
    v-model:value="monIndex"
    :options="[
      { id: 0, text: '测试1' },
      { id: 1, text: '测试2' },
      { id: 2, text: '测试3' },
      { id: 3, text: '测试4' },
      { id: 4, text: '测试5' },
    ]"
    svalue="id"
    @change="changeValue"
    placeholder="请选择归属部门"
  ></select>

单选效果图

多选效果图

标签:uniapp,width,color,value,item,单选,border,下拉框,select
From: https://blog.csdn.net/qq_43751614/article/details/140319118

相关文章