vue中继承element-plus 组件,修改源逻辑,ElSelect组件

背景:带下拉框,能够提供选择模板内容后也支持自己进行输入。最理想的是参照El-Select组件,功能相似度高达90%,不过一点不好就是自己默认带查询,当加入multiple、filterable 实现交互效果时,会自动加入opts下拉栏,这是不希望看到的。于是就有了下面的这部分代码。

  • 在el-select 的基础上再次去继承组件属性和逻辑
  • 改写的代码如下
<template>
  <div ref="selectWrapper" v-click-outside:[popperPaneRef]="handleClose" :class="wrapperKls" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave" @click.stop="toggleMenu">
    <el-tooltip
      ref="tooltipRef"
      :visible="dropMenuVisible"
      :placement="placement"
      :teleported="teleported"
      :popper-class="[nsSelect.e('popper'), popperClass]"
      :popper-options="popperOptions"
      :fallback-placements="['bottom-start', 'top-start', 'right', 'left']"
      :effect="effect"
      pure
      trigger="click"
      :transition="`${nsSelect.namespace.value}-zoom-in-top`"
      :stop-popper-mouse-event="false"
      :gpu-acceleration="false"
      :persistent="persistent"
      @show="handleMenuEnter"
    >
      <template #default>
        <div class="select-trigger" @mouseenter="inputHovering = true" @mouseleave="inputHovering = false">
          <div v-if="multiple" ref="tags" :class="[nsSelect.e('tags'), nsSelect.is('disabled', selectDisabled)]" :style="selectTagsStyle">
            <transition v-if="collapseTags && selected.length" @after-leave="resetInputHeight">
              <span :class="[nsSelect.b('tags-wrapper'), { 'has-prefix': prefixWidth && selected.length }]">
                <el-tag
                  v-for="item in showTagList"
                  :key="getValueKey(item)"
                  :closable="!selectDisabled && !item.isDisabled"
                  :size="collapseTagSize"
                  :hit="item.hitState"
                  :type="tagType"
                  disable-transitions
                  @close="deleteTag($event, item)"
                >
                  <span :class="nsSelect.e('tags-text')" :style="tagTextStyle">
                    {{ item.currentLabel }}
                  </span>
                </el-tag>
                <el-tag v-if="selected.length > maxCollapseTags" :closable="false" :size="collapseTagSize" :type="tagType" disable-transitions>
                  <el-tooltip
                    v-if="collapseTagsTooltip"
                    :disabled="dropMenuVisible"
                    :fallback-placements="['bottom', 'top', 'right', 'left']"
                    :effect="effect"
                    placement="bottom"
                    :teleported="teleported"
                  >
                    <template #default>
                      <span :class="nsSelect.e('tags-text')">+ {{ selected.length - maxCollapseTags }}</span>
                    </template>
                    <template #content>
                      <div :class="nsSelect.e('collapse-tags')">
                        <div v-for="item in collapseTagList" :key="getValueKey(item)" :class="nsSelect.e('collapse-tag')">
                          <el-tag
                            class="in-tooltip"
                            :closable="!selectDisabled && !item.isDisabled"
                            :size="collapseTagSize"
                            :hit="item.hitState"
                            :type="tagType"
                            disable-transitions
                            :style="{ margin: '2px' }"
                            @close="deleteTag($event, item)"
                          >
                            <span
                              :class="nsSelect.e('tags-text')"
                              :style="{
                                maxWidth: inputWidth - 75 + 'px',
                              }"
                              >{{ item.currentLabel }}</span
                            >
                          </el-tag>
                        </div>
                      </div>
                    </template>
                  </el-tooltip>
                  <span v-else :class="nsSelect.e('tags-text')">+ {{ selected.length - maxCollapseTags }}</span>
                </el-tag>
              </span>
            </transition>
            <transition v-if="!collapseTags" @after-leave="resetInputHeight">
              <span :class="[nsSelect.b('tags-wrapper'), { 'has-prefix': prefixWidth && selected.length }]">
                <el-tag
                  v-for="item in selected"
                  :key="getValueKey(item)"
                  :closable="!selectDisabled && !item.isDisabled"
                  :size="collapseTagSize"
                  :hit="item.hitState"
                  :type="tagType"
                  disable-transitions
                  @close="deleteTag($event, item)"
                >
                  <span :class="nsSelect.e('tags-text')" :style="{ maxWidth: inputWidth - 75 + 'px' }">{{ item.currentLabel }}</span>
                </el-tag>
              </span>
            </transition>
            <input
              v-if="filterable"
              ref="input"
              v-model="query"
              type="text"
              :class="[nsSelect.e('input'), nsSelect.is(selectSize), nsSelect.is('disabled', selectDisabled)]"
              :disabled="selectDisabled"
              :autocomplete="autocomplete"
              :style="{
                marginLeft: (prefixWidth && !selected.length) || tagInMultiLine ? `${prefixWidth}px` : '',
                flexGrow: 1,
                width: `${inputLength / (inputWidth - 32)}%`,
                maxWidth: `${inputWidth - 42}px`,
              }"
              @focus="handleFocus"
              @blur="handleQueryBlur"
              @keyup="managePlaceholder"
              @keydown="resetInputState"
              @keydown.esc="handleKeydownEscape"
              @keydown.enter.stop.prevent="handleQueryBlur"
              @keydown.delete="deletePrevTag"
              @keydown.tab="visible = false"
              @compositionstart="handleComposition"
              @compositionupdate="handleComposition"
              @compositionend="handleComposition"
              @input="handleQueryChange"
            />
          </div>
          <!-- fix: https://github.com/element-plus/element-plus/issues/11415 -->
          <input
            v-if="isIOS && !multiple && filterable && readonly"
            ref="iOSInput"
            :class="[nsSelect.e('input'), nsSelect.is(selectSize), nsSelect.em('input', 'iOS')]"
            :disabled="selectDisabled"
            type="text"
          />
          <el-input
            :id="id"
            ref="reference"
            v-model="selectedLabel"
            type="text"
            :placeholder="typeof currentPlaceholder === 'function' ? currentPlaceholder() : currentPlaceholder"
            :name="name"
            :autocomplete="autocomplete"
            :size="selectSize"
            :disabled="selectDisabled"
            :readonly="readonly"
            :validate-event="false"
            :class="[nsSelect.is('focus', visible)]"
            :tabindex="multiple && filterable ? -1 : undefined"
            @focus="handleFocus"
            @blur="handleBlur"
            @input="debouncedOnInputChange"
            @paste="debouncedOnInputChange"
            @compositionstart="handleComposition"
            @compositionupdate="handleComposition"
            @compositionend="handleComposition"
            @keydown.down.stop.prevent="navigateOptions('next')"
            @keydown.up.stop.prevent="navigateOptions('prev')"
            @keydown.enter.stop.prevent="selectOption"
            @keydown.esc="handleKeydownEscape"
            @keydown.tab="visible = false"
          >
            <template v-if="$slots.prefix" #prefix>
              <div style="height: 100%; display: flex; justify-content: center; align-items: center">
                <slot name="prefix" />
              </div>
            </template>
            <template #suffix>
              <el-icon v-if="iconComponent && !showClose" :class="[nsSelect.e('caret'), nsSelect.e('icon'), iconReverse]">
                <component :is="iconComponent" />
              </el-icon>
              <el-icon v-if="showClose && clearIcon" :class="[nsSelect.e('caret'), nsSelect.e('icon')]" @click="handleClearClick">
                <component :is="clearIcon" />
              </el-icon>
            </template>
          </el-input>
        </div>
      </template>
      <template #content>
        <el-select-menu>
          <el-scrollbar
            v-show="options.size > 0 && !loading"
            ref="scrollbar"
            tag="ul"
            :wrap-class="nsSelect.be('dropdown', 'wrap')"
            :view-class="nsSelect.be('dropdown', 'list')"
            :class="[nsSelect.is('empty', !allowCreate && Boolean(query) && filteredOptionsCount === 0)]"
          >
            <el-option v-if="showNewOption" :value="query" :created="true" />
            <el-options @update-options="onOptionsRendered">
              <slot />
            </el-options>
          </el-scrollbar>
          <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.size === 0))">
            <slot v-if="$slots.empty" name="empty" />
            <p v-else :class="nsSelect.be('dropdown', 'empty')">
              {{ emptyText }}
            </p>
          </template>
        </el-select-menu>
      </template>
    </el-tooltip>
  </div>
</template>

<script>
import { defineComponent, toRefs } from 'vue'
import { ElSelect } from 'element-plus'
import { useSelectStates } from 'element-plus/lib/components/select/src/useSelect'

export default defineComponent({
  name: 'MySelect',
  extends: ElSelect,
  setup(props, ctx) {
    const states = useSelectStates(props)
    const { query } = toRefs(states)

    const handleQueryBlur = (e) => {
      const val = e.target.value?.trim() || ''
      if (val && val.length > 0) {
        ctx.emit('update:modelValue', [...props.modelValue, val])
        states.query = ''
      }
    }
    const handleQueryChange = (val) => {
      if (states.previousQuery === val || states.isOnComposition) return
      if (val && val.length > 0) {
        states.currentPlaceholder = ''
      }
    }
    return {
      ...ElSelect.setup(props, ctx),
      handleQueryBlur,
      handleQueryChange,
      query,
    }
  },
})
</script>
  • 使用改写的组件
<my-select class="w-full" v-model="dialog.form.content" multiple placeholder="请选择水印内容" filterable clearable>
  <el-option v-for="item in list" :key="item.value" :label="item.label" :value="item.label" />
</my-select>

== 你以为完了,别慌,vue2版本的也有 ==

<template>
  <div class="el-select" :class="[selectSize ? 'el-select--' + selectSize : '']" @click.stop="toggleMenu" v-clickoutside="handleClose">
    <div class="el-select__tags" v-if="multiple" ref="tags" :style="{ 'max-width': inputWidth - 32 + 'px', width: '100%' }">
      <transition-group @after-leave="resetInputHeight">
        <el-tag v-for="item in selected" :key="getValueKey(item)" :closable="!selectDisabled" :size="collapseTagSize" :hit="item.hitState" type="info" @close="deleteTag($event, item)" disable-transitions>
          <span class="el-select__tags-text">{{ item.currentLabel }}</span>
        </el-tag>
      </transition-group>

      <input
        type="text"
        class="el-select__input"
        :class="[selectSize ? `is-${selectSize}` : '']"
        :disabled="selectDisabled"
        :autocomplete="autoComplete || autocomplete"
        @focus="handleFocus"
        @blur="handleQueryBlur"
        @compositionstart="handleComposition"
        @compositionupdate="handleComposition"
        @compositionend="handleComposition"
        @keydown.enter.prevent="handleQueryBlur"
        @keydown.esc.stop.prevent="visible = false"
        v-model="query"
        @input="debouncedQueryChange"
        v-if="filterable"
        :style="{ 'flex-grow': '1', width: inputLength / (inputWidth - 32) + '%', 'max-width': inputWidth - 42 + 'px' }"
        ref="input"
      />
    </div>

    <el-input
      ref="reference"
      v-model="selectedLabel"
      type="text"
      :placeholder="currentPlaceholder"
      :name="name"
      :id="id"
      :autocomplete="autoComplete || autocomplete"
      :size="selectSize"
      :disabled="selectDisabled"
      :readonly="readonly"
      :validate-event="false"
      :class="{ 'is-focus': visible }"
      :tabindex="multiple && filterable ? '-1' : null"
      @focus="handleFocus"
      @blur="handleBlur"
      @input="debouncedOnInputChange"
      @keydown.native.down.stop.prevent="handleNavigate('next')"
      @keydown.native.up.stop.prevent="handleNavigate('prev')"
      @keydown.native.enter.prevent="selectOption"
      @keydown.native.esc.stop.prevent="visible = false"
      @keydown.native.tab="visible = false"
      @compositionstart="handleComposition"
      @compositionupdate="handleComposition"
      @compositionend="handleComposition"
      @mouseenter.native="inputHovering = true"
      @mouseleave.native="inputHovering = false"
    >
      <template slot="prefix" v-if="$slots.prefix">
        <slot name="prefix"></slot>
      </template>
      <template slot="suffix">
        <i v-show="!showClose" :class="['el-select__caret', 'el-input__icon', 'el-icon-' + iconClass]"></i>
        <i v-if="showClose" class="el-select__caret el-input__icon el-icon-circle-close" @click="handleClearClick"></i>
      </template>
    </el-input>

    <transition name="el-zoom-in-top" @before-enter="handleMenuEnter" @after-leave="doDestroy">
      <el-select-menu ref="popper" :append-to-body="popperAppendToBody" v-show="visible && emptyText !== false">
        <el-scrollbar tag="ul" wrap-class="el-select-dropdown__wrap" view-class="el-select-dropdown__list" ref="scrollbar" :class="{ 'is-empty': !allowCreate && query && filteredOptionsCount === 0 }" v-show="options.length > 0 && !loading">
          <el-option :value="query" created v-if="showNewOption"> </el-option>
          <slot></slot>
        </el-scrollbar>
        <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.length === 0))">
          <slot name="empty" v-if="$slots.empty"></slot>
          <p class="el-select-dropdown__empty" v-else>
            {{ emptyText }}
          </p>
        </template>
      </el-select-menu>
    </transition>
  </div>
</template>

<script>
import { Select } from 'element-ui'

export default {
  name: 'MySelect',
  extends: Select,
  methods: {
    handleQueryBlur(e) {
      const val = e.target?.value?.trim() || ''
      if (val && val.length > 0) {
        this.value.push(val)
      }
      this.softFocus = false
    },
    handleQueryChange(val) {
      if (this.previousQuery === val || this.isOnComposition) return
      if (val && val.length > 0) {
        this.currentPlaceholder = ''
      }
    },
  },
  //  End
}
</script>
最后总结一些,element-plus 改写起来会麻烦一些,属性没有一起暴露,改代码时动的刀子也会多一点。

你可能感兴趣的:(javascript,开发语言,ecmascript)