uniapp和vue3+ts创建自定义下拉选择框组件

使用uniapp开发小程序的时候,使用了uview的ui组件,但是里面没有下拉选择组件,只有Picker 选择器,但是我们想要使用下拉选择的组件,所以需要自定义个一个下拉选择的自定义组件,我就只能自己动手创建这个自定义组件了,支持:多选,单选,下拉框在框内还是框外

实现的效果:

自定义组件源代码:Select.vue

javascript 复制代码
<template>
  <!-- <view class="uni-select-dc" :style="{ 'z-index': zindex }"> -->
  <view class="uni-select-dc" :style="{ 'z-index': zindex }">
    <view 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">
        <view class="uni-select-multiple-item" v-if="multiple" 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>
        <!-- 单选时展示内容 -->
        <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>
        <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>
    </scroll-view>
  </view>
</template>
 
<script lang="ts" setup>
import { onMounted, reactive, ref } 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,
  },
  zindex: {
    type: Number,
    default: 999,
  },
  // 禁用选择
  disabled: {
    type: Boolean,
    default: false,
  },
  options: {
    type: Array,
    default() {
      return [];
    },
  },
  value: {
    type: Array,
    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>>([]);
onMounted(() => {
  init();
});

// 初始化函数
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;
    console.log("props---", changevalue);

  } else {
    changevalue = [];
    realValue = [];
  }
};
// 点击展示选项
const handleSelect = () => {
  if (props.disabled) return;
  active.value = !active.value;
};
// 移除数据
const handleRemove = (index: any) => {
  if (index === null) {
    realValue = [];
    changevalue = [];
  } else {
    realValue.splice(index, 1);
    changevalue.splice(index, 1);
  }
  emit("change", changevalue, realValue);
};
// 点击组件某一项
const handleChange = (index, item) => {
  console.log("选中了某一项", index, item);
  // 如果是单选框,选中一项后直接关闭
  if (!props.multiple) {
    console.log("关闭下拉框");
    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;
  z-index: 999;

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

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

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

      &::before,
      &::after {
        content: "";
        position: absolute;
        left: 20%;
        top: 50%;
        height: 1px;
        width: 60%;
        transform: rotate(45deg);
        background-color: #909399;
      }

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

  //所有情空的定位
  .close-postion {
    position: absolute;
    right: 35px;
    top: 0;
    height: 100%;
    width: 15px;
  }

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

    .single-text {
      color: #333;
    }

    .uni-select-multiple-item {
      background: #f4f4f5;
      margin-right: 5px;
      padding: 2px 4px;
      border-radius: 4px;
      color: #909399;
      display: flex;
      flex-shrink: 0;
    }
  }

  // select部分
  .uni-select-dc-select {
    user-select: none;
    position: relative;
    z-index: 3;
    height: 30px;
    padding: 0 30px 0 10px;
    box-sizing: border-box;
    border-radius: 4px;
    border: 1px solid rgb(229, 229, 229);
    display: flex;
    align-items: center;
    font-size: 12px;
    color: #999;
    min-width: 210px;

    .uni-disabled {
      position: absolute;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 19;
      cursor: no-drop;
      background: rgba(255, 255, 255, 0.5);
    }

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

      &.active {
        color: #333;
      }
    }

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

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

      &.disabled {
        cursor: no-drop;

        text {
          width: 20rpx;
          height: 20rpx;
          border: 2px solid #ff0000;
          border-radius: 50%;
          transition: 0.3s;
          position: relative;
          z-index: 999;

          &::after {
            content: "";
            position: absolute;
            top: 50%;
            left: 0;
            width: 100%;
            height: 2px;
            margin-top: -1px;
            background-color: #ff0000;
            transform: rotate(45deg);
          }
        }
      }
    }

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

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

      &.disabled {
        cursor: no-drop;

        text {
          width: 20rpx;
          height: 20rpx;
          border: 2px solid #ff0000;
          border-radius: 50%;
          transition: 0.3s;
          position: relative;
          z-index: 999;

          &::after {
            content: "";
            position: absolute;
            top: 50%;
            left: 0;
            width: 100%;
            height: 2px;
            margin-top: -1px;
            background-color: #ff0000;
            transform: rotate(45deg);
          }
        }
      }
    }

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

    &.active .uni-select-dc-inner {
      text {
        position: absolute;
        right: 10px;
        top: 12px;
        transform: rotate(-225deg);
      }
    }
  }

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

    .uni-select-dc-item {
      padding: 0 10px;
      box-sizing: border-box;
      cursor: pointer;
      line-height: 2.5;
      transition: 0.3s;
      font-size: 14px;

      &.active {
        color: #409eff;

        background-color: #f5f7fa &hover {
          color: #409eff;
          background-color: #f5f7fa;
        }
      }

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

父组件调用:

javascript 复制代码
<template>
  <view class="arena-main">
    自定义下拉选择组件:
    <Select :value="monIndex" downInner :options="options" @change="changeValue">        
    </Select>
  </view>
</template>

<script setup lang="ts">
import Select from "@/components/select/index.vue"
import { reactive } from "vue";

let monIndex = reactive([1]);
const changeValue = (item: any, value: any) => {
  console.log("父组件接收到选中的值", item, value);
  monIndex = value;
};

const options = [
  { value: 0, text: "测试1" },
  { value: 1, text: "测试2" },
  { value: 2, text: "测试3" },
  { value: 3, text: "测试4" },
  { value: 4, text: "测试5" },
];


</script>

<style lang="scss" scoped>
.arena-main {
  padding: 10px;
}
</style>

使用参数说明:

javascript 复制代码
monIndex:选中的值列表

options:可选项列表

multiple:是否为多选

downInner:下拉箭头是否在选择框内

showValueClear:是否显示单个删除

disabled:是否禁用

placeholder:占位符

slabel:label标签展示某项

svalue:value选中的值
相关推荐
彭世瑜9 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40410 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish10 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five12 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序12 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫54112 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
RAY_CHEN.13 分钟前
vue3 pinia 中actions修改状态不生效
vue.js·typescript·npm
酷酷的威朗普13 分钟前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
前端每日三省14 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
小刺猬_98514 分钟前
(超详细)数组方法 ——— splice( )
前端·javascript·typescript