vue 自定义select组件(可清除,超出滚动,禁用)

因为element-ui给的下拉图标不合适,所以尝试封装了一个下拉组件,做个记录

主要思路

  • input为只读,获取焦点展开下拉列表
  • 鼠标划出下拉框收缩列表
  • 数据如果为后台拉取,需要监听下拉项,重新设置
  • input在有值的情况下,如果设置了清除,可以清除
  • 借助el-scroll-bar 实现下拉项超出滚动
  • 为了遍历对象的数据,做了一个转换

效果:
在这里插入图片描述
禁用:
在这里插入图片描述
接下来就是枯燥乏味的代码:
selector.vue

html

<template>
  <div class="selector" @mouseleave.stop="dropBoxBlur">
    <div
      class="input-wrapper"
      :class="{
        focus: optionsVisible,
        'show-clear': currentOption !== '' && isClear,
        'show-disable': disabled
      }"
    >
      <input
        v-model="currentOption"
        :placeholder="placeHolder"
        type="text"
        @click="inputFocus"
        readonly
      />
      <div v-if="isClear && currentOption !== ''" class="clear" @click="clear">
        X
      </div>
      <!-- 下拉列表面板 -->
      <div v-show="optionsVisible" class="list-box">
        <el-scrollbar wrap-class="scroll-viewPort-select">
          <ul ref="list">
            <li
              v-for="(value, k) in options"
              :key="k"
              @click.stop="check(value)"
            >
              <span>{{ value[1] }}</span>
            </li>
          </ul>
        </el-scrollbar>
      </div>
    </div>
  </div>
</template>

JS

<script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
interface ObjectData {
  [propName: string]: any
}
@Component
export default class Selector extends Vue {
  // 下拉选项
  @Prop({ required: true }) optionData!: Array<any>;
  // 提示文字
  @Prop({ default: "请选择" }) placeHolder!: string;
  // 默认项
  @Prop({ default: "" }) currentItem!: string | number;
  // 是否可清除
  @Prop({ default: "false", type: String || Boolean }) clearable!:
    | string
    | boolean;
  @Prop({ default: false }) disable!: string | boolean;
  private currentOption = this.currentItem || "";
  private disabled = this.disable;
  private optionsVisible = false;
  private options: Array<any> = this.convert2Array(this.optionData);
  private inputFocus(): void {
    if (this.disabled) return;
    this.optionsVisible = true;
  }
  private dropBoxBlur(): void {
    if (this.optionsVisible) this.optionsVisible = !this.optionsVisible;
  }
  private clear() {
    this.currentOption = "";
    this.$emit("change", "");
  }
  private check(val: Array<any>): void {
    this.currentOption = val[1];
    this.$emit("change", Number(val[0]) || val[0]);
    this.optionsVisible = false;
  }
  // 监听外部选项变化,
  @Watch("currentItem")
  currentChange(val: string) {
    this.currentOption = val;
    console.log("wa ", val);
    this.setDefaultOption();
  }
  /*
    监听接收对象或者数组
  */
  @Watch("optionData", { deep: true })
  optionDataChange(data: any) {
    this.options = this.convert2Array(data);
    this.setDefaultOption();
  }
  //  最后都转化为数组,如:
  //  [[key, value],[key, value],...]
  private convert2Array(origin: Array<any> | ObjectData): Array<any> {
    if (Object.prototype.toString.call(origin).slice(8, -1) === "Object") {
      const arr: Array<any> = [];
      for (const k in origin) {
        arr.push([k, (origin as ObjectData)[k]]);
      }
      return [...arr];
    } else {
      return [...(origin as Array<any>)];
    }
  }
  // 设置默认项
  private setDefaultOption(): void {
    console.log(this.options, this.currentOption);
    this.options.forEach((item: Array<any>) => {
      if (item[0] == this.currentOption) this.currentOption = item[1];
    });
  }
  // 是否显示清除图标
  get isClear(): boolean {
    return !!this.clearable && this.clearable !== "false" ? true : false;
  }
}
</script>

css

<style lang="less" scoped>
@selector_color: #767676;
@color_normal: #a99;
.selector {
  display: inline-block;
  vertical-align: middle;
  width: 100%;
  height: 0.3rem;
  .input-wrapper {
    position: relative;
    width: 100%;
    height: 100%;
    line-height: 0.3rem;
    input {
      width: 100%;
      height: 100%;
      box-sizing: border-box;
      outline: none;
      text-indent: 0.05rem;
      cursor: pointer;
      border: 1px solid gray;
    }
    &:hover {
      &.show-clear .clear {
        display: inline-block;
      }
      &.show-clear::after {
        display: none;
      }
      &.show-disable input {
        cursor: not-allowed;
      }
    }
    /* 三角 */
    &::after {
      content: "";
      position: absolute;
      right: 0.08rem;
      top: 50%;
      transform: translateY(-50%);
      display: block;
      border-top: 0.08rem solid @selector_color;
      border-left: 0.06rem solid transparent;
      border-right: 0.06rem solid transparent;
      transition: all 0.5s;
    }
    &.focus {
      .clear {
        display: none;
      }
      &::after {
        display: block;
        transform: rotate(180deg);
        transform-origin: 50% 25%;
        transition: all 0.5s;
      }
      &:not(.is-empty)::after {
        display: block;
      }
    }
    /* 清除图标 */
    .clear {
      display: none;
      position: absolute;
      padding-top: 1px;
      right: 5px;
      top: 50%;
      transform: translateY(-50%);
      width: 0.15rem;
      height: 0.15rem;
      line-height: 0.15rem;
      color: @color_normal;
      border: 1px solid @color_normal;
      border-radius: 50%;
      font-size: 12px;
      z-index: 2;
      cursor: pointer;
      box-sizing: border-box;
    }
    .list-box {
      width: 100%;
      width: 100%;
      top: 100%;
      position: absolute;
      overflow: hidden;
      z-index: 10;
      box-sizing: border-box;
      border: 1px solid #888;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      background-color: #fff;
      ul {
        width: 100%;
        list-style: none;
        margin: 0;
        padding: 0;
        li {
          text-align: left;
          text-indent: 0.02rem;
          letter-spacing: 1px;
          width: 100%;
          line-height: 0.28rem;
          cursor: default;
          &:hover {
            background-color: #eef;
          }
        }
      }
    }
  }
}
</style>
<style lang="less">
.scroll-viewPort-select {
  max-height: 2rem;
}
</style>

父组件

<template>
  <div class="views">
    <div class="box">
      <Selector :option-data="optionsData1" clearable="true" />
    </div>
  </div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import Selector from "../components/selector.vue";
@Component({
  components: {
    Selector
  }
})
export default class Views extends Vue {
  private optionsData1 = [
    [1, "vue"],
    [2, "react"],
    [3, "angular"],
    [1, "vue"],
    [2, "react"],
    [3, "angular"],
    [1, "vue"],
    [2, "react"],
    [3, "angular"]
  ];
}
</script>
<style lang="less" scoped>
.box {
  margin: 1rem auto;
  width: 2rem;
}
</style>

需要注意

  • selector父级盒子需要给宽度
  • html设置了根字体 font-size: calc(100px + 1*(100vw - 1000px)/46);,不然样式会错乱

需要完善的地方:

  • 多选,将选的值做个累加即可
  • 分组,数据源加个类别在下拉列表处理一下样式就行

我还有个问题就是,怎么才能让下拉列表高度展开时有个过渡的效果,因为下拉项不是不确定吗,后面参考element select的实现把这个效果实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值