vue+antd后台管理系统的远程搜索组件的封装(a-select)

        自入行已开,都是做的vue + element项目开发,个人觉得这一套组合是针对pc端的项目是很流畅丝滑的,但是,最近开始的项目是vue + antd项目,问了一些同行业的大佬,这些大佬基本都是一句“vue + antd吗?莫名其妙的bug解决不了要和蚂蚁金服直接交涉”这样的调侃,反正我觉得antd对于vue框架来说是没有element这么简单易开发,但是,毕竟是成熟的两个重量框架,项目已经使用了,计算遇到问题也是需要尽力去解决,下面我就针对a-select这个组件自行封装一个远程搜索组件,目标是为了页面的复用和简化代码。

一、使用场景

图一 项目使用示例图

图二 antd官网组件

图一为我项目中具体的使用,当获取焦点时会远程搜索相关内容,也可以支持输入内容搜索,支持搜索内容下拉懒加载,当然,也进行了简单的防抖处理

二、代码示例

①、在需要用到该组件的页面进行模板编写、组件引入,注册,比如使用的页面命名为页面A

// 页面A

// html 模板
<a-form-item label="船舶:">
  <remote-select 
    v-model="filter.imo" 
    :allow-clear="true" 
    field-name="vessel" 
    show-word="label" 
    :default-value="defaultList.imo" // 2021/12/31更新,默认值通过props传给子组件
    @change="filterChange('imo')" 
  />
</a-form-item>

// 组件引入
import remoteSelect from '@/components/remoteSelect/index.vue'

// 组件注册
components: { remoteSelect }

methods: {
// 2021/12/31更新, 这里是这个例子的一个使用方法,点击页面的编辑按钮,将本来的数据当成默认值传给子组件
    // 点击编辑按钮
    openPortOfCallEdit(arg) {
      this.drawerTitle = '编辑靠港记录'
      this.cachePurpose = arg.identifiedPurpose
      this.imoDisabled = true
      this.placeNameDisabled = false
      this.cahceSelectedItem = JSON.stringify(arg)
      this.cahceSelectedItem = JSON.parse(this.cahceSelectedItem)
      for (const key in this.selectedItem) {
        if (key === 'imo') {
          this.selectedItem[key].key = arg.imo
          this.selectedItem[key].label = arg.imo
           // 2021/12/31 这里进行默认值格式的转化,同封装组件的格式保持一致
          this.defaultList[key] = {
            key: arg.imo,
            label: arg.imo
          }
        } else if (key === 'portId') {
          this.selectedItem[key].key = arg.portId
          this.selectedItem[key].label = arg.portName
// 2021/12/31 这里进行默认值格式的转化,同封装组件的格式保持一致
          this.defaultList[key] = {
            key: arg.portId,
            label: arg.portName
          }
        } else if (key === 'placeName') {
          // 这里进行靠港目的区分,因为为REP时,用的控件是自行封装的控件,其他的为自带的a-select组件
          if (arg.identifiedPurpose === 'REP') {
            this.selectedItem[key].key = arg.yardId
          } else {
            this.selectedItem[key].value = arg.berthTerminalId
          }
          this.selectedItem[key].label = arg.placeName
          this.defaultList[key] = {
            key: arg.yardId,
            label: arg.placeName
          }
        } else {
          this.selectedItem[key] = arg[key]
        }
      }
      this.ataEdit = arg.ataTime
      this.atbEdit = arg.atbTime
      this.atdEdit = arg.atdTime
      this.isShowEditItemModal = true
    },
}

 ②、remote自定义组件

<template>
  <a-select
    :key="keyItem" // 1 
    :value="currentValue" // 2
    label-in-value // 3
    show-search // 4
    style="width: 100%"
    :default-active-first-option="false" // 5
    :show-arrow="false" // 6
    :filter-option="false" // 7
    :disabled="disabled" // 8
    :allow-clear="clearable" // 9
    :option-label-prop="showWord" // 10
    @popupScroll="handlePopupScroll" // 11
    @focus="handleFocus" // 12
    @search="handleSearch" // 13
    @change="handleChange" // 14
    @blur="handlerBlur" // 15
  >
    <a-select-option 
      v-for="item in data" // 16
      :key="item.value"  
      :value="item.value" // 17
      :label="item.label" // 18
    >
      <a-tooltip>
        <template slot="title">{{ item.label }}</template>
        <div class="option-item">
          <div class="option-label">
            <span class="label-name">{{ item.label }}</span>
            <span v-if="ifShowFieldName" class="field-name">({{ fieldName }})</span>
          </div>
          <div v-if="fieldName === 'vessel' || fieldName === 'port'">{{ item.value }}</div>
        </div>
      </a-tooltip>
    </a-select-option>
  </a-select>
</template>
<script>
import { postAction } from '@/api/manage'
import { typeOf } from '@/utils/commonTools.js'
export default {
  props: {
    // 返回给父组件的值
    value: {
      type: [String, Number, Array, Object]
    },
    // 2021/12/31更新, 默认值
    defaultValue: {
      type: Object,
      default: () => {}
    },
    // 后台接口根据该字段搜索不同的内容
    fieldName: {
      type: String,
      default: ''
    },
    // 下拉框选中内容回填字段
    showWord: {
      type: String,
      default: 'label'
    },
    disabled: {
      type: Boolean,
      default: false
    },
    ifShowFieldName: {
      type: Boolean,
      default: false
    },
    allowClear: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      data: [],
      currentValue: '',
      searchStr: '',
      listQuery: {
        pageNo: 1,
        pageSize: 10
      },
      pages: 1,
      timer: null,
      keyItem: 100 // 这个参数是为了使下拉框的滚动条回到顶端,会更加的秏性能,但是目前没有找到别的办法解决这个问题
    }
  },
  computed: {
    // 这里的计算属性:输入框可以清除功能,手动取消清除属性
    // 因为a-select组件加上allowClear属性时,初始化的时候也会有清除的小图标,以及清除后还会有小图标,必须再次点击清除图标才会消失的bug
    clearable() {
      if (this.allowClear) {
        if (!this.currentValue || !this.currentValue.key) {
          return false
        } else {
          return true
        }
      } else {
        return false
      }
    }
  },
  watch: {
    // 这里是因为可能会有默认值的情况,所以进行特别的处理
    value: {
      handler(newVal, oldVal) {
        this.$nextTick(() => {
          this.currentValue = typeOf(newVal) === 'object' ? newVal : typeOf(newVal) === 'string' || typeOf(newVal) === 'number' ? { key: newVal } : ''
        })
      },
      immediate: true
    },
    //2021/12/31更新, 如果有默认值,进行回调操作
    defaultValue: {
      handler(newVal, oldVal) {
        if (Object.keys(newVal).length) {
          this.currentValue = newVal
        }
      }
      // immediate: true
    }
  },
  mounted() {},
  beforeDestroy() {
    clearTimeout(this.timer)
    this.timer = null
  },
  methods: {
    // 这个方法是我个人项目url地址的映射处理
    getUrlFn() {
      switch (this.fieldName) {
        case 'vessel':
          return '/ship/findShipPage'
        case 'port':
          return '/port/findPortPage'
        case 'berth':
          return '/portBerthTerminal/findPortBerthTerminalPage'
        case 'shipyard':
          return '/shipyard/findShipyardPage'
      }
    },
    // 下拉滚动加载
    handlePopupScroll(e) {
      const { target } = e
      var total = target.scrollTop + target.offsetHeight
      var scrollHeight = target.scrollHeight
      // this.listQuery.pageNo是当前页 this.pages是总页数
      if (total === scrollHeight && this.listQuery.pageNo < this.pages) {
        this.listQuery.pageNo++
        this.getSearchResult(this.searchStr, data => (this.data = this.data.concat(data)))
      }
    },
    // 获取数据列表 调接口
    getSearchResult(value, cb) {
      if (this.timer) {
        clearTimeout(this.timer)
        this.timer = null
      }
      this.timer = setTimeout(() => {
        const params = {
          query: value,
          ...this.listQuery
        }
        postAction(this.getUrlFn(), params).then(res => {
          const result = res.data.records
          this.pages = res.data.pages
          const data = []
          result.forEach(item => {
            data.push({
              // 船舶imo字段,港口取portCode字段, 船厂和泊位取id字段
              value: item.imo || item.portCode || item.id,
              // 船舶imo字段,港口取portName字段, 船厂取yardName字段,泊位取berthTerminalName字段
              label: item.shipName || item.portName || item.yardName || item.berthTerminalName
            })
          })
          cb(data)
        })
      }, 500)
    },
    // 获取焦点的回调
    handleFocus(value) {
      this.searchStr = value
      this.listQuery.pageNo = 1
      this.data.length = 0
      this.getSearchResult(value, data => (this.data = data))
    },
    // 失焦的时候
    handlerBlur() {
      this.keyItem = Math.random()
    },
    // 输入内容的时候触发
    handleSearch(value) {
      this.listQuery.pageNo = 1
      this.data.length = 0
      this.searchStr = value
      this.getSearchResult(value, data => (this.data = data))
    },
    // 选中下拉框内容
    handleChange(value, option) {
      this.currentValue = value
      this.listQuery.pageNo = 1
      this.data.length = 0
      this.$emit('input', this.currentValue)
      this.$emit('change')
    }
  }
}
</script>
<style lang="scss" scoped>
  .option-item {
    display: flex;
    justify-content: space-between;
  }
  .option-label {
    display: flex;
    // width: calc(100% - 70px);
    // .label-name {
    //   overflow: hidden;
    //   text-overflow: ellipsis;
    //   white-space: nowrap;
    // }
  }
  .field-name {
    margin-left: 5px;
    color: #1890ff;
  }
</style>

代码中的数字的释义:

1:个人不才,每次获取焦点的时候的下拉滚动条都保持在上一次滚动过的位置,没有通过dom的方式进行还原,所以就采用极端的方式,这里有大佬可以帮忙指点就好了;

2:指定当前选中的条目;

3:参照官方,是否把每个选项的 label 包装到 value 中,会把 Select 的 value 类型从 string 变为 {key: string, label: vNodes} 的格式;

4:使单选模式可搜索;

5:是否默认高亮第一个选项;

6:是否显示下拉小箭头;

7:是否根据输入项进行筛选。当其为一个函数时,会接收 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false;

8:是否禁用;

9:是否可以清除,这里也有bug,详情可以参照代码中的解释用法;

10:回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 value。这里我进行的props传值可控处理;

11:下拉滚动懒加载方法,具体参照官方说明;

12:获得焦点时回调;

13:文本框值变化时回调;

14:选中 option,或 input 的 value 变化(combobox 模式下)时,调用此函数;

15:失去焦点的时回调;这里同数字1处有关联,失焦的时候销毁重新传染一个a-select组件

16:远程搜索的结果组成的数组结构数据

17:option的属性value

18:option的属性label

③、具体的使用

// 页面A
methods: {
  // 这里是remote这个组件选中了对应的项,然后父组件通过同名函数箭筒到的回调,在里面进行一些逻辑操作
    filterChange(type) {
      // 无论控件选中前后的数值是否一致,都会清空后面关联的控件内容
      type === 'imo' ? 
        this.filter.shipVoyageId = undefined : 
        this.filter.portBerthTerminalId = undefined
      this.ipagination.current = 1
      this.getPortOfCallData()
    },
}

大概就是这样一些情况,封装的过程中的确遇到了很多的坑,比如下拉滚动懒加载的滚动条的问题、选中options的某一项时的回填显示问题、添加清除内容属性时初始化就有清除图标的问题,我都有在里面进行备注使用的解释,不完美,还有很多的地方需要进行优化更改,也一直在进行优化中,望看见的朋友可以提出自己的建议!!

tips:

①2021/12/31更新(更新的内容见代码注释,注释的开头用的是2021/12/31更新)

        上一个版本办忽略了一个默认值的问题,要是有默认值,应该将默认值回填到选中框内,这就需要将默认值的格式同其本身转换的格式保持一致

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: antd的a-select组件可以用于下拉列表的选择功能,但是其宽度默认是随着option选项的内容而变化的。如果想要保持a-select的宽度不随option选项的内容而变化,可以通过设置css样式来实现。 首先在a-select组件上添加一个类名,例如"fixed-width-select",然后在css文件或样式标签中为该类名添加样式,设置其宽度为一个固定的值,例如200px: .fixed-width-select .ant-select-selector { width: 200px; } 这样就可以使a-select组件的宽度保持不变,而不受option选项内容的影响。但需要注意的是,如果option选项内容过长,可能会导致显示不完全,这时可以通过设置overflow属性来解决,例如设置为"auto"或"ellipsis",以便在需要时显示滚动条或省略号。 总之,通过设置css样式可以轻松实现antd的a-select组件固定宽度不随option选项变化的效果。 ### 回答2: antd的a-select组件是一个多功能的下拉框,它可以方便地呈现各种选项,并且提供了多种选择交互方式。在默认情况下,a-select组件的宽度会自适应其包含的选项的长度。但是,在某些场景下,我们可能需要固定a-select的宽度,不随选项的变化而变化。 一般来说,固定a-select的宽度可以通过CSS样式控制。我们可以在a-select组件的容器上设置宽度,例如: ``` <div style={{width: 200}}> <a-select> <a-select-option value="1">选项1</a-select-option> <a-select-option value="2">选项2</a-select-option> <a-select-option value="3">选项3</a-select-option> </a-select> </div> ``` 在上面的例子中,我们使用<div>容器固定了a-select组件的宽度为200px。无论选项的长度如何变化,a-select的宽度都会保持不变。 除了使用CSS样式来控制宽度外,我们还可以使用a-select组件提供的props来设置宽度。a-select的props中有一个叫做"style"的属性,我们可以在其中指定宽度。例如: ``` <a-select style={{width: 200}}> <a-select-option value="1">选项1</a-select-option> <a-select-option value="2">选项2</a-select-option> <a-select-option value="3">选项3</a-select-option> </a-select> ``` 这里我们将style属性直接传递给了a-select组件,并指定了宽度为200px。与使用容器设置宽度类似,这样做可以固定a-select的宽度,不会随选项的变化而变化。 需要注意的一点是,在使用固定宽度的a-select组件时,如果选项的长度超过了宽度,那么选项的文本内容将会被截断。这会影响用户的选择体验,因此在设计时需要注意选项的长度问题。 ### 回答3: antd框架中的a-select组件是一个下拉选择器,它的宽度默认是自适应的,也就是说它的宽度会根据最长项的长度进行自动调整,这样会给用户带来一定的操作体验上的不便,因此需要对它进行一定的宽度控制。通常的解决方法是通过设置a-select组件的style属性中的width来固定其宽度。但是这种方式无法达到预期的效果,因为此时a-select的宽度固定后,如果下拉列表中的选项过长,那么就会出现内容溢出。 解决这个问题的方式是,通过对a-select的宽度进行固定,同时在每个option项中添加样式,使其宽度与a-select保持一致。具体来说,可以通过在option项中添加style属性来设置宽度值,例如: ``` <a-select style={{width: 200}}> <a-select-option value="1" style={{width: 200}}>选项一</a-select-option> <a-select-option value="2" style={{width: 200}}>选项二</a-select-option> <a-select-option value="3" style={{width: 200}}>选项三</a-select-option> </a-select> ``` 这种方式可以实现a-select与option项的宽度固定,同时也可以保证option项的宽度与a-select组件的宽度一致,有效避免了出现内容溢出的问题。在实际使用中,也可以通过CSS样式表来进行样式的统一管理,方便维护和更新。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值