下拉分页组件 select-page

发布时间 2023-12-29 10:55:16作者: 一封未寄出的信
组件使用文档:
https://terryz.gitee.io/vue/#/selectpage/demo
实例:
需要使用的下拉分页的页面:
 
<el-form-item label="公司" prop="carrierId">
          <base-selectPage v-model="ruleForm.carrierId" keyField="crmCorporationId" @change="rows => emitBookingAgent(rows, 1)" searchField="keyword" showField="code" concatField="code_nameCn" :searchParams="{carrier: true, customerStep: 1, shipping: true, allowOrder: false}"
            data="/transportPrice-api/crm/corporation/getSimplePage" :tb-columns="defaultSelectPageColumns" :defaultRow="editType === 'edit' ? {crmCorporationId: ruleForm.carrierId, code: ruleForm.carrierCode, nameCn: ruleForm.carrierNameCn} : {}"
          ></base-selectPage>
 </el-form-item>
 
import { defaultSelectPageColumns} from "@/utils/constants";
==============================================================================================
constants.js     
  // 基础分页下拉Columns
  export const defaultSelectPageColumns = [
      { title: 'CODE', data: 'code', width: 130 },
       title: '中文名', data: 'nameCn', minWidth: 180 },
      { title: '英文名', data: 'nameEn', minWidth: 250  },
  ]
=====================================================================================  
data(){
  return {
    ruleForm: {
       carrierId: "",
    },
     defaultSelectPageColumns: defaultSelectPageColumns,
  }
} 
========================================================================================
字段解释:
  v-model="ruleForm.carrierId"  字段绑定的参数
  keyField="crmCorporationId"  关键字
  searchField="keyword"  搜索
  showField="code"  展示
  concatField="code_nameCn"   回显的时候,代码+中文名
  :searchParams="{carrier: true, customerStep: 1, shipping: true, allowOrder: false}"   搜索条件
  data="/xxxx/xxxx/xxxx/xxxxxxx"   数据接口路径
  :tb-columns="defaultSelectPageColumns"   下拉框的列头设置
  defaultRow="editType === 'edit' ? {crmCorporationId: ruleForm.carrierId, code: ruleForm.carrierCode, nameCn: ruleForm.carrierNameCn} : {}"  默认行显示
 
==============================================================================
main.js  中进行全局的配置
 
import BaseSelectPage from "@/components/BaseSelectPage"
 
全局挂载
Vue.component('BaseSelectPage', BaseSelectPage)
===============================================================================
BaseSelectPage 文件夹
  index.vue
 
<template>
  <el-popover
    :placement="placement"
    width="auto"
    @show="handleShowPopover"
    @hide="handleHidePopover"
    :disabled="disabled"
    ref="popoverRef"
    class="popover-warp"
    trigger="click">
    <div class="select-main" ref="baseSelectPageRef">
      <div class="base-search">
        <!-- 加el-form防止外部form影响该search控件 -->
        <el-form @submit.native.prevent>
          <!-- 搜索 -->
          <el-form-item>
            <el-input v-model="searchVal" prefix-icon="el-icon-search" ref="searchRef" @input="handleSearchValChange" clearable></el-input>
          </el-form-item>
        </el-form>
      </div>

      <div class="base-table">
        <el-table :data="list"
        ref="tableRef"
        highlight-current-row
        @cell-mouse-enter="cellMouseEnter"
        @row-click="rowClick"
        :row-class-name="rowClassName"
        :row-key="keyField"
        max-height="500px"
        v-loading="loading">
          <!-- <el-table-column
            type="selection"
            align="center"
            header-align="center"
            width="40">
          </el-table-column> -->

          <el-table-column
            v-for="(item, index) in tbColumns"
            :key="index"
            :width="item.width"
            :min-width="item.minWidth || 100"
            :prop="item.data"
            :label="item.title"
            :fixed="item.fixed"
            :render-header="item.renderHeader"
            :align="item.align || 'left'"
            :header-align="item.headerAlign || 'left'"
            :class-name="item.className"
            :show-overflow-tooltip="item.showOverflowTooltip === false ? false : true">
          </el-table-column>
        </el-table>
      </div>

      <div class="base-pagination">
        <el-pagination
          @current-change="val => handlePageChange(val, 'pageNo')"
          :current-page="pagination.pageNo"
          :page-size="pagination.pageSize"
          :total="pagination.total"
          layout="prev, pager, next">
        </el-pagination>
      </div>
    </div>

    <div slot="reference" ref="referenceRef" class="referenceClass" :style="{width: width}">
      <el-input :class="multiple && 'hidden-inp'" v-model="showVal" class="reference-inp" readonly :disabled="disabled" :placeholder="placeholder" :id="id" :size="size">
        <div slot="suffix" ref="suffixRef" class="suffixClass">
          <i class="el-input__icon el-icon-arrow-down" v-if="!clearableVisible" :class="{open: !disabled && visible}"></i>
          <i class="el-input__icon el-icon-circle-close" v-else @click="clear"></i>
        </div>
      </el-input>
      <div v-if="multiple" class="base-select_tags" :class="`base-select_tags--${size}`">
        <transition-group v-if="!collapseTags && selectRows.length">
          <el-tag
            v-for="item in selectRows"
            :key="item[keyField]"
            :closable="!disabled"
            :size="collapseTagSize"
            type="info"
            @close="deleteTag($event, item)"
            class="base-tag"
            disable-transitions>
            <span class="el-select__tags-text">{{ formatTagItem(item) }}</span>
          </el-tag>
        </transition-group>
        <span v-if="collapseTags && selectRows.length">
          <el-tag
            v-for="index of showMaxTagsLimit > selectRows.length ? selectRows.length : showMaxTagsLimit"
            :key="selectRows[index-1][keyField]"
            :closable="!disabled"
            :size="collapseTagSize"
            type="info"
            @close="deleteTag($event, selectRows[index-1])"
            class="base-tag"
            disable-transitions>
            <span class="el-select__tags-text">{{ formatTagItem(selectRows[index-1]) }}</span>
          </el-tag>
          <el-tag
            v-if="selectRows.length > showMaxTagsLimit"
            :closable="false"
            :size="collapseTagSize"
            type="info"
            class="base-tag"
            disable-transitions>
            <span class="el-select__tags-text">+ {{ selectRows.length - showMaxTagsLimit }}</span>
          </el-tag>
        </span>
        <span v-if="!selectRows.length" class="base-select_tags-placeholder">{{placeholder}}</span>
        <div class="suffixClass">
          <i class="el-input__icon el-icon-arrow-down" v-if="!clearableVisible" :class="{open: !disabled && visible}"></i>
          <i class="el-input__icon el-icon-circle-close" v-else @click="clear"></i>
        </div>
      </div>
    </div>
  </el-popover>
</template>

<!-- 分页下拉组件 by lyh 2023年2月 -->
<script>
import { getAction } from '@/api/common/index'
import { debounce } from 'throttle-debounce'
import { equals, cutArray } from '@/utils/utils'
import { deepClone } from '@/utils/index'

export default {
  name: 'BaseSelectPage',
  props: {
    value: [Array, String, Number],
    /**
     * table 数据
     * Array:前端分页
     * String:后端分页,传的是url
     */
    data: {
      type: [Array, String],
      required: true
    },
    /**
     * 禁用
     */
    disabled: {
      type: Boolean,
      default: false
    },
    /**
     * 清空
     */
    clearable: {
      type: Boolean,
      default: false
    },
    /**
     * 搜索的字段
     */
    searchField: {
      type: String,
      default: 'keyword'
    },
    /**
     * 唯一的key
     */
    keyField: {
      type: String,
      required: true
    },
    /**
     * 要展示的字段
     */
    showField: {
      type: String,
      default: 'code'
    },
    /**
     * 查询参数
     */
    searchParams: {
      type: Object,
      default: () => {}
    },
    /**
     * 是否多选
     */
    multiple: {
      type: Boolean,
      default: false
    },
    pageSize: {
      type: [Number, String],
      default: 10
    },
    /**
     * table columns数据
     */
    tbColumns: {
      type: Array,
      default: () => {
        return [
          // {title: '名称', data: 'value'}
        ]
      }
    },
    /**
     * 服务端分页时初始化默认显示的数据
     * 单选传的是Object,多选传的是Array
     */
    defaultRow: {
      type: [Object, Array],
      default: () => {
        return {}
      }
    },
    /**
     * 需要合并显示的字段
     * name1_name2_name3....
     */
    concatField: String,
    /**
     * placeholder
     */
    placeholder: String,
    /**
     * 多选时是否将选中值按文字的形式展示
     */
    collapseTags: Boolean,
    /**
     * 选中值按文字的形式展示时,最大显示的tag数
     */
    showMaxTagsLimit: {
      type: Number,
      default: 1
    },
    /**
     * 尺寸 small mini...
     */
    size: {
      type: String,
      default: ''
    },
    /**
     * 宽度
     */
    width: {
      type: String,
      default: 'auto'
    },
    /**
     * popover 的显示位置
     */
     placement: {
      type: String,
      default: 'bottom'
    },
    /**
     * 格式化返回值,仅服务端
     * 必须返回这种格式 {list: [], total: 0}
     */
    formatResult: {
      type: Function,
      default: null
    },
    id: String
  },

  data() {
    return {
      selectRows: [], // 选中的行
      searchVal: '',  // 搜索的值
      showVal: '',    // 存储显示的input控件的状态
      loading: false,
      visible: false, // Popover是否显示
      clearableVisible: false, // clearable是否显示
      list: [],       // 服务端分页时,table数据
      pagination: {
        total: 0,
        pageSize: this.pageSize,
        pageNo: 1
      },
      currentRow: {},  // 当前行
      noEmitInput: false, // 不触发emit Input事件
      isServer: toString.call(this.data) === '[object String]' // 是否服务端分页
    };
  },

  components: {},

  created() {
    // 初始回显赋值
    if (!this.multiple) { // 单选
      if (this.isServer) {
        if (Object.keys(this.defaultRow).length && !this.selectRows.length) {
          this.selectRows = [this.defaultRow]
        }
      } else {
        if (this.value && this.data.length) {
          this.noEmit()
          this.selectRows = [this.data.find(r => String(r[this.keyField]) === String(this.value))]
        }
      }
    } else { // 多选
      if (this.isServer) {
        if (this.defaultRow.length && !this.selectRows.length) {
          this.noEmit()
          this.selectRows = deepClone(this.defaultRow)
        }
      } else {
        if (this.value.length && this.data.length) {
          let arr = []
          this.value.forEach(r => {
            const row = this.data.find(r1 => r1[this.keyField] === r)
            if (row) arr.push(row)
          })
          this.noEmit()
          this.selectRows = arr
        }
      }
    }
  },

  mounted() {
    this.suffixRefEvent()

    try {
      // 设置base-table最小宽度
      const popEl = this.$refs.popoverRef.$el
      document.querySelector(`#${popEl.firstChild.id}`).querySelector('.base-table').style.minWidth = (popEl.offsetWidth - 24) + 'px'
    } catch {}
  },

  methods: {
    // Popover显示事件
    handleShowPopover() {
      this.visible = true
      this.currentRow = {}

      // 服务端分页
      if (toString.call(this.data) === '[object String]') {
        this.fetchData()
      }
      // 前端分页
      if (toString.call(this.data) === '[object Array]') {
        this.localData('', true)
      }

      this.$emit('visible-change', true)

      this.initEvent()

      try {
        this.$nextTick(() => {
          this.resetPopoverTop()

          // 设置边框颜色
          const setting = localStorage.getItem('layout-setting')
          const popEl = this.$refs.popoverRef.$el
          if (this.multiple) {
            popEl.querySelector('.base-select_tags').style.borderColor = setting && JSON.parse(setting)?.['theme']
          } else {
            popEl.querySelector('.reference-inp').querySelector('.el-input__inner').style.borderColor = setting && JSON.parse(setting)?.['theme']
          }
        })
      } catch {}
    },

    // Popover隐藏事件
    handleHidePopover() {
      this.visible = false
      this.searchVal = ''

      this.$emit('visible-change', false)

      // 重置边框颜色
      try {
        this.$nextTick(() => {
          const popEl = this.$refs.popoverRef.$el
          if (this.multiple) {
            const tagsEl = popEl.querySelector('.base-select_tags')
            tagsEl.style.borderColor = ''
            tagsEl.classList.remove('_bgc')
            tagsEl.classList.add('_bgc')
          } else {
            const inpEl = popEl.querySelector('.reference-inp').querySelector('.el-input__inner')
            inpEl.style.borderColor = ''
            inpEl.classList.remove('_bgc')
            inpEl.classList.add('_bgc')
          }
        })
      } catch {}
    },

    // 调整下拉框位置
    resetPopoverTop() {
      this.$nextTick(() => {
        const tagsEl = this.$refs.popoverRef.$el.querySelector('.base-select_tags')
        const popperElm = this.$refs.popoverRef.popperElm
        if (!tagsEl) return
        const { bottom, top } = tagsEl.getBoundingClientRect() || {}
        const { top: popperElmTop } = popperElm.getBoundingClientRect() || {}
        if (popperElmTop > top) {
          popperElm.style.top = bottom + 'px'
        }
      })
    },

    // 服务端分页时获取数据
    async fetchData() {
      const { pageSize, pageNo } = this.pagination

      const query = {
        pageSize, pageNo,
        ...this.searchParams,
        ...(this.searchField ? {[this.searchField]: this.searchVal} : {})
      }

      this.loading = true
      const {code, data} = await getAction(this.data, query).finally(() => this.loading = false)

      if (code != 0) {
        this.list = []
        this.$set(this.pagination, 'total', 0)
        return
      }

      const {list, total} = toString.call(this.formatResult) != '[object Null]' ? this.formatResult(data) : data

      this.list = list || []
      this.$set(this.pagination, 'total', total || 0)

      this.setSelectRows(1)
    },

    /**
     * 前端分页时获取数据
     * type:1 搜索
     * isShow:是否Popover显示时调用
     * isArrow:是否键盘左右键切换页码,或点击页码时调用
     */
    localData(type, isShow, isArrow) {
      let len = this.data.length, arr = []
      if (type === 1) {
        const searchFilterArr = this.data.filter(r => r[this.searchField].toLowerCase().includes(this.searchVal.toLowerCase()))
        len = searchFilterArr.length
        arr = cutArray(searchFilterArr, this.pageSize)
        this.$set(this.pagination, 'total', len)
        if(!isArrow || !len) this.$set(this.pagination, 'pageNo', 1)
        this.list = len ? arr[this.pagination.pageNo-1] : []
      } else {
        if (len) {
          arr = cutArray(this.data, this.pageSize)
          this.$set(this.pagination, 'total', len)
          this.list = arr[this.pagination.pageNo-1]
        } else {
          this.list = []
          this.$set(this.pagination, 'total', 0)
        }
      }

      // 切换到选中数据的分页
      if (isShow) {
        if (!this.multiple) {
          if (this.value) {
            arr.forEach((r, i) => {
              if (r.some(r1 => String(r1[this.keyField]) === String(this.value))) {
                this.list = r
                this.$set(this.pagination, 'pageNo', i+1)
              }
            })
          }
        }
      }

      this.setSelectRows(2)
    },

    // 存在默认值情况下,重新赋值
    setSelectRows(type) {
      if (!this.multiple) {
        if (type === 1) {
          if (Object.keys(this.defaultRow).length && this.selectRows.length && equals(this.defaultRow, this.selectRows[0])) {
            this.list.forEach(r => {
              if (r[this.keyField] == this.defaultRow[this.keyField]) {
                this.noEmit()
                this.selectRows = [r]
              }
            })
          }
        } else {
        }
      } else {}
    },

    // 初始化键盘事件
    initEvent() {
      let that = this
      this.$nextTick(() => {
        this.$refs.searchRef.focus()
        this.$refs.baseSelectPageRef.onkeydown = function (e) {
          e = window.event || e

          // 设置当前行
          const setCurrentRow = (row) => {
            that.$refs.tableRef.setCurrentRow(row)
            that.currentRow = row
          }

          const { pageNo, total, pageSize } = that.pagination
          switch (e.key) {
            case 'ArrowRight':
              if (that.pagination.pageNo === parseInt(total / pageSize)+1) return
              that.$set(that.pagination, 'pageNo', pageNo+1)
              that.isServer ? that.fetchData() : that.localData(that.searchVal ? 1 : '', false, true)
              that.currentRow = {}
              break;
            case 'ArrowLeft':
              if (that.pagination.pageNo === 1) return
              that.$set(that.pagination, 'pageNo', pageNo-1)
              that.isServer ? that.fetchData() : that.localData(that.searchVal ? 1 : '', false, true)
              that.currentRow = {}
              break;
            case 'ArrowUp':
              if (!Object.keys(that.currentRow).length) {
                const row = that.list[that.list.length-1]
                setCurrentRow(row)
              } else {
                for (let i = 0; i < that.list.length; i++) {
                  const row = that.list[i];

                  // 滚动到最后一行
                  if (that.currentRow[that.keyField] === that.list[0][that.keyField]) {
                    const curRow = that.list[that.list.length-1]
                    setCurrentRow(curRow)
                    break
                  }
                  // 滚动到上一行
                  if (that.currentRow[that.keyField] === row[that.keyField]) {
                    const curRow = that.list[i-1]
                    setCurrentRow(curRow)
                    break
                  }
                }
              }
              break;
            case 'ArrowDown':
              if (!Object.keys(that.currentRow).length) {
                const row = that.list[0]
                setCurrentRow(row)
              } else {
                for (let i = 0; i < that.list.length; i++) {
                  const row = that.list[i];

                  // 滚动到第一行
                  if (that.currentRow[that.keyField] === that.list[that.list.length-1][that.keyField]) {
                    const curRow = that.list[0]
                    setCurrentRow(curRow)
                    break
                  }
                  // 滚动到下一行
                  if (that.currentRow[that.keyField] === row[that.keyField]) {
                    const curRow = that.list[i+1]
                    setCurrentRow(curRow)
                    break
                  }
                }
              }
              break;
            case 'Enter':
              const curRow = that.currentRow
              if (!that.multiple) {
                if (Object.keys(curRow).length) {
                  that.selectRows = [curRow]
                  that.$refs.tableRef.clearSelection()
                  that.$refs.tableRef.toggleRowSelection(curRow)
                  that.$refs.popoverRef.doClose()
                }
              } else {
                // 多选
                if (Object.keys(curRow).length) {
                  if (!that.selectRows.some(r => r[that.keyField] === curRow[that.keyField])) {
                    that.selectRows.push(curRow)
                  } else {
                    const index = that.selectRows.findIndex(r => r[that.keyField] === curRow[that.keyField])
                    that.selectRows.splice(index, 1)
                  }
                }
              }
              break;
          }

          that.$emit('keydownEnter', e, that)
        }
      })
    },

    // 鼠标滑动事件
    cellMouseEnter(row, column, cell, event) {
      this.$refs.tableRef.setCurrentRow(row)
      // 保存当前行
      this.currentRow = row
    },

    // 设置选中行颜色
    rowClassName({row, rowIndex}) {
      if (Array.isArray(this.selectRows)) {
        if (this.selectRows.some(r => r?.[this.keyField] == row?.[this.keyField])) return 'base-select-row'
      }
    },

    // 行单击
    rowClick(row, column, event) {
      if (!this.multiple) {
        this.selectRows = [row]
        this.$refs.popoverRef.doClose()
      } else {
        if (!this.selectRows.some(r => r[this.keyField] === row[this.keyField])) {
          this.selectRows.push(row)
        } else {
          const index = this.selectRows.findIndex(r => r[this.keyField] === row[this.keyField])
          this.selectRows.splice(index, 1)
        }
        this.resetPopoverTop()
      }
    },

    // 页码改变事件
    handlePageChange(val, type) {
      this.pagination[type] = val

      if (toString.call(this.data) === '[object String]') {
        this.fetchData()
      } else {
        this.localData(this.searchVal ? 1 : '', false, true)
      }
    },

    // 顶部搜索框改变事件
    handleSearchValChange: debounce(300, function() {
      this.currentRow = {}
      this.$set(this.pagination, 'pageNo', 1)

      if (toString.call(this.data) === '[object String]') {
        this.fetchData()
      } else {
        this.localData(1)
      }
    }),

    // 给删除按钮添加事件
    suffixRefEvent() {
      this.$refs.referenceRef.onmouseenter = () => {
        this.clearableVisible = !!this.value && this.clearable && !this.disabled
      }
      this.$refs.referenceRef.onmouseleave = () => {
        this.clearableVisible = false
      }
    },

    // 清空数据
    clear(e) {
      e.stopPropagation()
      this.selectRows = []
      this.clearableVisible = false
    },

    isEmpty(v) {
      return v === '' || v === null || v === undefined
    },

    // 不提交input事件和change事件
    noEmit() {
      this.noEmitInput = true
      setTimeout(() => {
        this.noEmitInput = false
      }, 300)
    },

    // 刷新
    reload() {
      this.noEmit()

      this.selectRows = []
      this.showVal = ''
      this.list = []
      this.pagination = {
        total: 0,
        pageSize: this.pageSize,
        pageNo: 1
      }
      this.currentRow = {}
    },

    // 格式化tag的显示内容
    formatTagItem(item) {
      if (this.isEmpty(item[this.showField])) return ''
      if (this.concatField) {
        const fieldArr = this.concatField.split('_')
        let arr = []
        fieldArr.forEach(r => {
          const s = item[r] || ''
          arr.push(s)
        })
        return arr.length ? arr.filter(r => r).join('/') : ''
      } else {
        return item[this.showField]
      }
    },

    // 多选时,删除tags
    deleteTag(e, item) {
      const index = this.selectRows.findIndex(r => r[this.keyField] === item[this.keyField])
      const delArr = this.selectRows.splice(index, 1)
      this.$emit('remove-tag', delArr)
    }
  },

  computed: {
    keys() {
      return (this.multiple ? this.selectRows.map(r => r[this.keyField]) : this.selectRows?.[0]?.[this.keyField]) || ''
    },
    selectSize() {
      return this.size || (this.$ELEMENT || {}).size;
    },
    collapseTagSize() {
      return ['small', 'mini'].indexOf(this.selectSize) > -1
        ? 'mini'
        : 'small';
    },
  },

  watch: {
    value: function(n, o) {
      if (!n) {
        if (this.selectRows.length) {
          this.selectRows = []
          this.currentRow = {}
        }
      } else {
        if (toString.call(this.data) === '[object String]') {

        } else {
          // value改变时,前端分页默认回显数据
          if (this.data.length) {
            if (this.multiple) {
              let arr = []
              n.forEach(r => {
                const row = this.data.find(r1 => r1[this.keyField] === r)
                if (row) arr.push(row)
              })
              this.noEmit()
              this.selectRows = arr
            } else {
              this.noEmit()
              this.selectRows = !this.isEmpty(n) ? [this.data.find(r => String(r[this.keyField]) === String(n))] : []
              this.currentRow = {}
            }
          }
        }
      }
    },
    selectRows: {
      handler: function(n, o) {
        if (n.length) {
          if (!this.multiple) {
            if (this.concatField) { // 需要拼接返回字段值
              const fieldArr = this.concatField.split('_')
              let arr = []
              fieldArr.forEach(r => {
                const s = this.selectRows?.[0]?.[r] || ''
                arr.push(s)
              })
              this.showVal = arr.length ? arr.filter(r => r).join('/') : ''
            } else {
              this.showVal = this.selectRows?.[0]?.[this.showField] || ''
            }
          }
        } else {
          this.showVal = ''
        }

        const toEmit = () => {
          this.$emit('input', this.keys)
          this.$emit('change', this.selectRows.slice())
        }

        if (!this.noEmitInput) {
          if (!this.multiple) {
            if (this.selectRows.length === 1) {
              if (this.selectRows[0][this.keyField] != this.defaultRow[this.keyField]) {
                toEmit()
              }
            } else {
              toEmit()
            }
          } else {
            // 多选
            if (this.selectRows.length && this.defaultRow.length) {
              if (this.selectRows.map(r => r[this.keyField]).join() != this.defaultRow.map(r => r[this.keyField]).join()) {
                toEmit()
              }
            } else {
              toEmit()
            }
          }
        }
      },
      deep: true
    },
    defaultRow: function(n, o) {
      if (!this.multiple && toString.call(this.data) === '[object String]') {
        if (Object.keys(n).length && !equals(n, o)) {
          if (!this.isEmpty(n[this.keyField])) {
            this.noEmit()
            const row = this.list.find(r => r[this.keyField] === n[this.keyField])
            this.selectRows = [row || n]
          }
        }
      } else {}
    },
    data: function(n) {
      if (toString.call(n) === '[object String]') {

      } else {
        // data切换时,前端分页默认回显数据
        if (n.length && this.value) {
          this.noEmit()
          this.selectRows = [n.find(r => String(r[this.keyField]) === String(this.value))]
        }
        this.currentRow = {}
      }
    }
  }
};
</script>
<style scoped lang="scss">
.popover-warp {
  display: block;
}
.select-main {
}
.base-search {
  margin: 0 0 12px;
  ::v-deep .el-form-item {
    margin-bottom: 0;
  }
  ::v-deep .el-form-item--small .el-input__suffix .el-icon-circle-close {
    line-height: 28px !important;
  }
}
.base-table {
  max-width: 600px;
  ::v-deep .el-table thead th.el-table__cell {
    background-color: #fff !important;
  }
  ::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
    background-color: #f5f7fa;
  }
  ::v-deep .el-table__body tr.current-row > td.el-table__cell {
    background-color: #f5f7fa;
  }
  ::v-deep .el-table__body tr.base-select-row {
    color: var(--current-color, #1890ff);
    font-weight: 600;
  }
  ::v-deep .el-table th.el-table__cell.is-leaf,::v-deep .el-table td.el-table__cell {
    border-bottom: 0;
  }
  ::v-deep .el-table::before {
    height: 0;
  }
}
.base-pagination {
  margin-top: 5px;
  text-align: center;
  ::v-deep .el-pagination {
    padding: 0;
    color: #505050;
  }
}
::v-deep .el-icon-arrow-down {
  position: absolute;
  top: 0px;
  right: 0px;
  transition: transform .3s ease;
  transform: rotateZ(0deg);
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #C0C4CC;
}
::v-deep .el-icon-circle-close {
  cursor: pointer;
  position: absolute;
  top: 0px;
  right: 0px;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #C0C4CC;
}
::v-deep .open {
  transform: rotateZ(-180deg);
}
.referenceClass {
  position: relative;
}
::v-deep .referenceClass .el-input__inner, .suffixClass {
  cursor: pointer;
}

.hidden-inp {
  z-index: -1;
  opacity: 0;
}

.base-select_tags {
  position: absolute;
  top: 0;
  width: 100%;
  z-index: 1;
  min-height: 32px;
  line-height: 32px;
  padding: 2px 25px 2px 5px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  border-radius: 4px;
  background-color: #fff;
  display: flex;
  align-items: center;
  &:hover {
    border-color: #c0c4cc;
  }
  > span {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
  }
  .base-select_tags-placeholder {
    position: absolute;
    color: #c0c4cc;
    font-size: 13px;
  }
}

::v-deep .base-select_tags .el-icon-arrow-down, ::v-deep .base-select_tags .el-icon-circle-close  {
  right: 4px;
}

._bgc {
  border-color: #dcdfe6;
  &:hover {
    border-color: #c0c4cc;
  }
}

.base-tag {
  margin: 2px 6px 2px 0;
}



.el-form-item--mini .base-select_tags {
  min-height: 28px;
  line-height: 28px;
  padding: 1px 25px 1px 5px;
}
.el-form-item--small .base-select_tags {
  min-height: 32px;
  line-height: 32px;
}
.el-form-item--medium .base-select_tags {
  min-height: 36px;
  line-height: 36px;
}
.el-form-item--default .base-select_tags {
  min-height: 40px;
  line-height: 40px;
}

@media (max-width: 768px) {
  .base-table {
    max-width: 85vw;
  }
}
</style>
 
 
==========================================================================
 
api接口
 
getAction:
 
export function getAction(url,params) {
  return request({
    url,
    method: 'get',
    params
  })
}
=====================================================
throttle-debounce:
 
declare module 'throttle-debounce' {
  type throttleFn = (
    delay: number,
    noTrailing: boolean,
    callback?: Function,
    debounceMode?: boolean
  ) => Function;

  type debounceFn = (
    delay: number,
    atBegin: boolean,
    callback?: Function
  ) => Function;

  const throttle: throttleFn;
  const debounce: debounceFn;
}
=========================================================================
util.js:
 
equals:
 
/**
 * 判断两个对象是否相等
 * @param {*} x
 * @param {*} y
 * @returns
 */
export function equals(x,y){
  var f1=x instanceof Object;
  var f2=y instanceof Object;
  if(!f1 || !f2){
      return x===y
  }
  if(Object.keys(x).length!== Object.keys(y).length){
      return false
  }
  for(var p in x){
      var a= x[p] instanceof Object;
      var b= y[p] instanceof Object;
      if(a && b){
          equals(x[p],y[p])
      }else if(x[p]!=y[p]){
          return false;
      }
  }
  return true;
}
 
 
cutArray:
 
/**
 * 按指定长度切割数组
 * @param {*} array
 * @param {*} subLength
 * @returns
 */
export function cutArray(array, subLength) {
  let index = 0;
  let newArr = [];
  while(index < array.length) {
    newArr.push(array.slice(index, index += subLength));
  }
  return newArr;
}
 
deepClone:
 
// 深拷贝对象
// https://github.com/JakHuang/form-generator/blob/dev/src/utils/index.js#L107
export function deepClone(obj) {
  const _toString = Object.prototype.toString

  // null, undefined, non-object, function
  if (!obj || typeof obj !== 'object') {
    return obj
  }

  // DOM Node
  if (obj.nodeType && 'cloneNode' in obj) {
    return obj.cloneNode(true)
  }

  // Date
  if (_toString.call(obj) === '[object Date]') {
    return new Date(obj.getTime())
  }

  // RegExp
  if (_toString.call(obj) === '[object RegExp]') {
    const flags = []
    if (obj.global) { flags.push('g') }
    if (obj.multiline) { flags.push('m') }
    if (obj.ignoreCase) { flags.push('i') }

    return new RegExp(obj.source, flags.join(''))
  }

  const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}

  for (const key in obj) {
    result[key] = deepClone(obj[key])
  }

  return result
}
============================================================================
文件下说明: REAdME.md
<h2 align="center">?分页下拉组件</h2>

类似[v-selectPage](https://terryz.gitee.io/vue/#/selectpage/demo)组件,大部分属性相同,可参考它的文档
<br>?支持服务端,前端分页。
<br>?支持单选,多选。
<br>?by lyh
<br>

#### ?说明文档
#### <b>Attributes</b>

- <b>value</b><br>
  类型:`Array, String, Number`<br>
  v-model绑定的数据,多选时,传值是`Array`。 单选传`String`, `Number`<br>

- <b>searchParams</b><br>
  类型:`Object`<br>
  自定义搜索字段<br>

- <b>defaultRow</b><br>
  类型:`Object`(单选) || `Array`(多选)<br>
  服务端分页时初始化默认显示的数据<br>
  数据必须和`data`里的数据对应<br>

- <b>tbColumns</b><br>
  类型:`Array`<br>
  和el-table的属性配置相同<br>
 
- <b>concatField</b><br>
  类型:`String`<br>
  是否需要拼接某些字段返回值,`showField`和`concatField`必传一个<br>
  使用:`name1_name2_name3....`<br>

- <b>collapseTags</b><br>
  类型:`Boolean`<br>
  多选时是否将选中值按文字的形式展示<br>
  默认:`false`

- <b>showMaxTagsLimit</b><br>
  类型:`Number`<br>
  选中值按文字的形式展示时,最大显示的`tag`数<br>
  默认:`1`<br>

- <b>size</b><br>
  类型:`String`<br>
  尺寸 `small mini...`<br>
  默认:`''`<br>

- <b>width</b><br>
  类型:`String`<br>
  宽度<br>
  默认:`auto`<br>

- <b>placement</b><br>
  类型:`String`<br>
  `popover` 的显示位置<br>
  默认:`bottom`<br>

- <b>formatResult</b><br>
  类型:`Function`<br>
  处理服务端回显数据的函数,<br>
  必须返回这种格式 `{list: [], total: 0}`<br>

- <b>id</b><br>
  类型:`String`<br>
  配置的`id`<br>

#### <b>Events</b>
- <b>change</b><br>
  说明:值改变时触发<br>
  回调参数:目前的选中值<br>

- <b>visible-change</b><br>
  说明:分页下拉框出现/隐藏时触发<br>
  回调参数:出现则为 `true`,隐藏则为 `false`<br>

- <b>remove-tag</b><br>
  说明:多选模式下移除`tag`时触发<br>
  回调参数:移除的`tag`值<br>

- <b>keydownEnter</b><br>
  说明:下拉框里的搜索框`enter`时触发<br>
  回调参数:keydown事件`event`,组件`this`<br>
  <br><br><br>
=================================================================
文件夹下demo
<template>
  <el-popover
    :placement="placement"
    width="auto"
    @show="handleShowPopover"
    @hide="handleHidePopover"
    :disabled="disabled"
    ref="popoverRef"
    class="popover-warp"
    trigger="click">
    <div class="select-main" ref="baseSelectPageRef">
      <div class="base-search">
        <!-- 加el-form防止外部form影响该search控件 -->
        <el-form @submit.native.prevent>
          <!-- 搜索 -->
          <el-form-item>
            <el-input v-model="searchVal" prefix-icon="el-icon-search" ref="searchRef" @input="handleSearchValChange" clearable></el-input>
          </el-form-item>
        </el-form>
      </div>

      <div class="base-table">
        <el-table :data="list"
        ref="tableRef"
        highlight-current-row
        @cell-mouse-enter="cellMouseEnter"
        @row-click="rowClick"
        :row-class-name="rowClassName"
        :row-key="keyField"
        max-height="500px"
        v-loading="loading">
          <!-- <el-table-column
            type="selection"
            align="center"
            header-align="center"
            width="40">
          </el-table-column> -->

          <el-table-column
            v-for="(item, index) in tbColumns"
            :key="index"
            :width="item.width"
            :min-width="item.minWidth || 100"
            :prop="item.data"
            :label="item.title"
            :fixed="item.fixed"
            :render-header="item.renderHeader"
            :align="item.align || 'left'"
            :header-align="item.headerAlign || 'left'"
            :class-name="item.className"
            :show-overflow-tooltip="item.showOverflowTooltip === false ? false : true">
          </el-table-column>
        </el-table>
      </div>

      <div class="base-pagination">
        <el-pagination
          @current-change="val => handlePageChange(val, 'pageNo')"
          :current-page="pagination.pageNo"
          :page-size="pagination.pageSize"
          :total="pagination.total"
          layout="prev, pager, next">
        </el-pagination>
      </div>
    </div>

    <div slot="reference" ref="referenceRef" class="referenceClass" :style="{width: width}">
      <el-input :class="multiple && 'hidden-inp'" v-model="showVal" class="reference-inp" readonly :disabled="disabled" :placeholder="placeholder" :size="size">
        <div slot="suffix" ref="suffixRef" class="suffixClass">
          <i class="el-input__icon el-icon-arrow-down" v-if="!clearableVisible" :class="{open: !disabled && visible}"></i>
          <i class="el-input__icon el-icon-circle-close" v-else @click="clear"></i>
        </div>
      </el-input>
      <div v-if="multiple" class="base-select_tags" :class="`base-select_tags--${size}`">
        <transition-group v-if="!collapseTags && selectRows.length">
          <el-tag
            v-for="item in selectRows"
            :key="item[keyField]"
            :closable="!disabled"
            :size="collapseTagSize"
            type="info"
            @close="deleteTag($event, item)"
            class="base-tag"
            disable-transitions>
            <span class="el-select__tags-text">{{ formatTagItem(item) }}</span>
          </el-tag>
        </transition-group>
        <span v-if="collapseTags && selectRows.length">
          <el-tag
            v-for="index of showMaxTagsLimit > selectRows.length ? selectRows.length : showMaxTagsLimit"
            :key="selectRows[index-1][keyField]"
            :closable="!disabled"
            :size="collapseTagSize"
            type="info"
            @close="deleteTag($event, selectRows[index-1])"
            class="base-tag"
            disable-transitions>
            <span class="el-select__tags-text">{{ formatTagItem(selectRows[index-1]) }}</span>
          </el-tag>
          <el-tag
            v-if="selectRows.length > showMaxTagsLimit"
            :closable="false"
            :size="collapseTagSize"
            type="info"
            class="base-tag"
            disable-transitions>
            <span class="el-select__tags-text">+ {{ selectRows.length - showMaxTagsLimit }}</span>
          </el-tag>
        </span>
        <span v-if="!selectRows.length" class="base-select_tags-placeholder">{{placeholder}}</span>
        <div class="suffixClass">
          <i class="el-input__icon el-icon-arrow-down" v-if="!clearableVisible" :class="{open: !disabled && visible}"></i>
          <i class="el-input__icon el-icon-circle-close" v-else @click="clear"></i>
        </div>
      </div>
    </div>
  </el-popover>
</template>

<!-- 分页下拉组件 by lyh 2023年2月 -->
<script>
import { getAction } from '@/api/common/index'
import { debounce } from 'throttle-debounce'
import { equals, cutArray } from '@/utils/utils'
import { deepClone } from '@/utils/index'

export default {
  name: 'BaseSelectPage',
  props: {
    value: [Array, String, Number],
    /**
     * table 数据
     * Array:前端分页
     * String:后端分页,传的是url
     */
    data: {
      type: [Array, String],
      required: true
    },
    /**
     * 禁用
     */
    disabled: {
      type: Boolean,
      default: false
    },
    /**
     * 清空
     */
    clearable: {
      type: Boolean,
      default: false
    },
    /**
     * 搜索的字段
     */
    searchField: {
      type: String,
      required: true
    },
    /**
     * 唯一的key
     */
    keyField: {
      type: String,
      required: true
    },
    /**
     * 要展示的字段
     */
    showField: {
      type: String,
      required: true
    },
    /**
     * 查询参数
     */
    searchParams: {
      type: Object,
      default: () => {}
    },
    /**
     * 是否多选
     */
    multiple: {
      type: Boolean,
      default: false
    },
    pageSize: {
      type: [Number, String],
      default: 10
    },
    /**
     * table columns数据
     */
    tbColumns: {
      type: Array,
      default: () => {
        return [
          // {title: '名称', data: 'value'}
        ]
      }
    },
    /**
     * 服务端分页时初始化默认显示的数据
     * 单选传的是Object,多选传的是Array
     */
    defaultRow: {
      type: [Object, Array],
      default: () => {
        return {}
      }
    },
    /**
     * 需要合并显示的字段
     * name1_name2_name3....
     */
    concatField: String,
    /**
     * placeholder
     */
    placeholder: String,
    /**
     * 多选时是否将选中值按文字的形式展示
     */
    collapseTags: Boolean,
    /**
     * 选中值按文字的形式展示时,最大显示的tag数
     */
    showMaxTagsLimit: {
      type: Number,
      default: 1
    },
    /**
     * 尺寸 small mini...
     */
    size: {
      type: String,
      default: ''
    },
    /**
     * 宽度
     */
    width: {
      type: String,
      default: 'auto'
    },
    /**
     * popover 的显示位置
     */
     placement: {
      type: String,
      default: 'bottom'
    },
    /**
     * 格式化返回值
     * 必须返回这种格式 {list: [], total: 0}
     */
    formatResult: {
      type: Function,
      default: null
    }
  },

  data() {
    return {
      selectRows: [], // 选中的行
      searchVal: '',  // 搜索的值
      showVal: '',    // 存储显示的input控件的状态
      loading: false,
      visible: false, // Popover是否显示
      clearableVisible: false, // clearable是否显示
      list: [],       // 服务端分页时,table数据
      pagination: {
        total: 0,
        pageSize: this.pageSize,
        pageNo: 1
      },
      currentRow: {},  // 当前行
      noEmitInput: false, // 不触发emit Input事件
      isServer: toString.call(this.data) === '[object String]' // 是否服务端分页
    };
  },

  components: {},

  created() {
    // 初始回显赋值
    if (!this.multiple) { // 单选
      if (this.isServer) {
        if (Object.keys(this.defaultRow).length && !this.selectRows.length) {
          this.selectRows = [this.defaultRow]
        }
      } else {
        if (this.value && this.data.length) {
          this.noEmit()
          this.selectRows = [this.data.find(r => String(r[this.keyField]) === String(this.value))]
        }
      }
    } else { // 多选
      if (this.isServer) {
        if (this.defaultRow.length && !this.selectRows.length) {
          this.noEmit()
          this.selectRows = deepClone(this.defaultRow)
        }
      } else {
        if (this.value.length && this.data.length) {
          let arr = []
          this.value.forEach(r => {
            const row = this.data.find(r1 => r1[this.keyField] === r)
            if (row) arr.push(row)
          })
          this.noEmit()
          this.selectRows = arr
        }
      }
    }
  },

  mounted() {
    this.suffixRefEvent()

    try {
      // 设置base-table最小宽度
      const popEl = this.$refs.popoverRef.$el
      document.querySelector(`#${popEl.firstChild.id}`).querySelector('.base-table').style.minWidth = (popEl.offsetWidth - 24) + 'px'
    } catch {}
  },

  methods: {
    // Popover显示事件
    handleShowPopover() {
      this.visible = true
      this.currentRow = {}

      // 服务端分页
      if (toString.call(this.data) === '[object String]') {
        this.fetchData()
      }
      // 前端分页
      if (toString.call(this.data) === '[object Array]') {
        this.localData('', true)
      }

      this.$emit('visible-change', true)

      this.initEvent()

      try {
        this.$nextTick(() => {
          this.resetPopoverTop()

          // 设置边框颜色
          const setting = localStorage.getItem('layout-setting')
          const popEl = this.$refs.popoverRef.$el
          if (this.multiple) {
            popEl.querySelector('.base-select_tags').style.borderColor = setting && JSON.parse(setting)?.['theme']
          } else {
            popEl.querySelector('.reference-inp').querySelector('.el-input__inner').style.borderColor = setting && JSON.parse(setting)?.['theme']
          }
        })
      } catch {}
    },

    // Popover隐藏事件
    handleHidePopover() {
      this.visible = false
      this.searchVal = ''

      this.$emit('visible-change', false)

      // 重置边框颜色
      try {
        this.$nextTick(() => {
          const popEl = this.$refs.popoverRef.$el
          if (this.multiple) {
            const tagsEl = popEl.querySelector('.base-select_tags')
            tagsEl.style.borderColor = ''
            tagsEl.classList.remove('_bgc')
            tagsEl.classList.add('_bgc')
          } else {
            const inpEl = popEl.querySelector('.reference-inp').querySelector('.el-input__inner')
            inpEl.style.borderColor = ''
            inpEl.classList.remove('_bgc')
            inpEl.classList.add('_bgc')
          }
        })
      } catch {}
    },

    // 调整下拉框位置
    resetPopoverTop() {
      this.$nextTick(() => {
        const tagsEl = this.$refs.popoverRef.$el.querySelector('.base-select_tags')
        const popperElm = this.$refs.popoverRef.popperElm
        if (!tagsEl) return
        const { bottom, top } = tagsEl.getBoundingClientRect() || {}
        const { top: popperElmTop } = popperElm.getBoundingClientRect() || {}
        if (popperElmTop > top) {
          popperElm.style.top = bottom + 'px'
        }
      })
    },

    // 服务端分页时获取数据
    async fetchData() {
      const { pageSize, pageNo } = this.pagination

      const query = {
        pageSize, pageNo,
        ...this.searchParams,
        ...(this.searchField ? {[this.searchField]: this.searchVal} : {})
      }

      this.loading = true
      const {code, data} = await getAction(this.data, query).finally(() => this.loading = false)

      if (code != 0) {
        this.list = []
        this.$set(this.pagination, 'total', 0)
        return
      }

      const {list, total} = toString.call(this.formatResult) != '[object Null]' ? this.formatResult(data) : data

      this.list = list || []
      this.$set(this.pagination, 'total', total || 0)

      this.setSelectRows(1)
    },

    /**
     * 前端分页时获取数据
     * type:1 搜索
     * isShow:是否Popover显示时调用
     * isArrow:是否键盘左右键切换页码时调用
     */
    localData(type, isShow, isArrow) {
      let len = this.data.length, arr = []
      if (type === 1) {
        const searchFilterArr = this.data.filter(r => r[this.searchField].toLowerCase().includes(this.searchVal.toLowerCase()))
        len = searchFilterArr.length
        arr = cutArray(searchFilterArr, this.pageSize)
        this.$set(this.pagination, 'total', len)
        if(!isArrow || !len) this.$set(this.pagination, 'pageNo', 1)
        this.list = len ? arr[this.pagination.pageNo-1] : []
      } else {
        if (len) {
          arr = cutArray(this.data, this.pageSize)
          this.$set(this.pagination, 'total', len)
          this.list = arr[this.pagination.pageNo-1]
        } else {
          this.list = []
          this.$set(this.pagination, 'total', 0)
        }
      }

      // 切换到选中数据的分页
      if (isShow) {
        if (!this.multiple) {
          if (this.value) {
            arr.forEach((r, i) => {
              if (r.some(r1 => String(r1[this.keyField]) === String(this.value))) {
                this.list = r
                this.$set(this.pagination, 'pageNo', i+1)
              }
            })
          }
        }
      }

      this.setSelectRows(2)
    },

    // 存在默认值情况下,重新赋值
    setSelectRows(type) {
      if (!this.multiple) {
        if (type === 1) {
          if (Object.keys(this.defaultRow).length && this.selectRows.length && equals(this.defaultRow, this.selectRows[0])) {
            this.list.forEach(r => {
              if (r[this.keyField] == this.defaultRow[this.keyField]) {
                this.noEmit()
                this.selectRows = [r]
              }
            })
          }
        } else {
        }
      } else {}
    },

    // 初始化键盘事件
    initEvent() {
      let that = this
      this.$nextTick(() => {
        this.$refs.searchRef.focus()
        this.$refs.baseSelectPageRef.onkeydown = function (e) {
          e = window.event || e

          // 设置当前行
          const setCurrentRow = (row) => {
            that.$refs.tableRef.setCurrentRow(row)
            that.currentRow = row
          }

          const { pageNo, total, pageSize } = that.pagination
          switch (e.code) {
            case 'ArrowRight':
              if (that.pagination.pageNo === parseInt(total / pageSize)+1) return
              that.$set(that.pagination, 'pageNo', pageNo+1)
              that.isServer ? that.fetchData() : that.localData(that.searchVal ? 1 : '', false, true)
              that.currentRow = {}
              break;
            case 'ArrowLeft':
              if (that.pagination.pageNo === 1) return
              that.$set(that.pagination, 'pageNo', pageNo-1)
              that.isServer ? that.fetchData() : that.localData(that.searchVal ? 1 : '', false, true)
              that.currentRow = {}
              break;
            case 'ArrowUp':
              if (!Object.keys(that.currentRow).length) {
                const row = that.list[that.list.length-1]
                setCurrentRow(row)
              } else {
                for (let i = 0; i < that.list.length; i++) {
                  const row = that.list[i];

                  // 滚动到最后一行
                  if (that.currentRow[that.keyField] === that.list[0][that.keyField]) {
                    const curRow = that.list[that.list.length-1]
                    setCurrentRow(curRow)
                    break
                  }
                  // 滚动到上一行
                  if (that.currentRow[that.keyField] === row[that.keyField]) {
                    const curRow = that.list[i-1]
                    setCurrentRow(curRow)
                    break
                  }
                }
              }
              break;
            case 'ArrowDown':
              if (!Object.keys(that.currentRow).length) {
                const row = that.list[0]
                setCurrentRow(row)
              } else {
                for (let i = 0; i < that.list.length; i++) {
                  const row = that.list[i];

                  // 滚动到第一行
                  if (that.currentRow[that.keyField] === that.list[that.list.length-1][that.keyField]) {
                    const curRow = that.list[0]
                    setCurrentRow(curRow)
                    break
                  }
                  // 滚动到下一行
                  if (that.currentRow[that.keyField] === row[that.keyField]) {
                    const curRow = that.list[i+1]
                    setCurrentRow(curRow)
                    break
                  }
                }
              }
              break;
            case 'Enter':
              const curRow = that.currentRow
              if (!that.multiple) {
                if (Object.keys(curRow).length) {
                  that.selectRows = [curRow]
                  that.$refs.tableRef.clearSelection()
                  that.$refs.tableRef.toggleRowSelection(curRow)
                  that.$refs.popoverRef.doClose()
                }
              } else {
                // 多选
                if (Object.keys(curRow).length) {
                  if (!that.selectRows.some(r => r[that.keyField] === curRow[that.keyField])) {
                    that.selectRows.push(curRow)
                  } else {
                    const index = that.selectRows.findIndex(r => r[that.keyField] === curRow[that.keyField])
                    that.selectRows.splice(index, 1)
                  }
                }
              }
              break;
          }
        }
      })
    },

    // 鼠标滑动事件
    cellMouseEnter(row, column, cell, event) {
      this.$refs.tableRef.setCurrentRow(row)
      // 保存当前行
      this.currentRow = row
    },

    // 设置选中行颜色
    rowClassName({row, rowIndex}) {
      if (Array.isArray(this.selectRows)) {
        if (this.selectRows.some(r => r?.[this.keyField] == row?.[this.keyField])) return 'base-select-row'
      }
    },

    // 行单击
    rowClick(row, column, event) {
      if (!this.multiple) {
        this.selectRows = [row]
        this.$refs.popoverRef.doClose()
      } else {
        if (!this.selectRows.some(r => r[this.keyField] === row[this.keyField])) {
          this.selectRows.push(row)
        } else {
          const index = this.selectRows.findIndex(r => r[this.keyField] === row[this.keyField])
          this.selectRows.splice(index, 1)
        }
        this.resetPopoverTop()
      }
    },

    // 页码改变事件
    handlePageChange(val, type) {
      this.pagination[type] = val

      if (toString.call(this.data) === '[object String]') {
        this.fetchData()
      } else {
        this.localData()
      }
    },

    // 顶部搜索框改变事件
    handleSearchValChange: debounce(300, function() {
      this.currentRow = {}
      this.$set(this.pagination, 'pageNo', 1)

      if (toString.call(this.data) === '[object String]') {
        this.fetchData()
      } else {
        this.localData(1)
      }
    }),

    // 给删除按钮添加事件
    suffixRefEvent() {
      this.$refs.referenceRef.onmouseenter = () => {
        this.clearableVisible = !!this.value && this.clearable
      }
      this.$refs.referenceRef.onmouseleave = () => {
        this.clearableVisible = false
      }
    },

    // 清空数据
    clear(e) {
      e.stopPropagation()
      this.selectRows = []
      this.clearableVisible = false
    },

    isEmpty(v) {
      return v === '' || v === null || v === undefined
    },

    // 不提交input事件和change事件
    noEmit() {
      this.noEmitInput = true
      setTimeout(() => {
        this.noEmitInput = false
      }, 300)
    },

    // 刷新
    reload() {
      this.noEmit()

      this.selectRows = []
      this.showVal = ''
      this.list = []
      this.pagination = {
        total: 0,
        pageSize: this.pageSize,
        pageNo: 1
      }
      this.currentRow = {}
    },

    // 格式化tag的显示内容
    formatTagItem(item) {
      if (this.isEmpty(item[this.showField])) return ''
      if (this.concatField) {
        const fieldArr = this.concatField.split('_')
        let arr = []
        fieldArr.forEach(r => {
          const s = item[r] || ''
          arr.push(s)
        })
        return arr.length ? arr.filter(r => r).join('/') : ''
      } else {
        return item[this.showField]
      }
    },

    // 多选时,删除tags
    deleteTag(e, item) {
      const index = this.selectRows.findIndex(r => r[this.keyField] === item[this.keyField])
      const delArr = this.selectRows.splice(index, 1)
      this.$emit('remove-tag', delArr)
    }
  },

  computed: {
    keys() {
      return (this.multiple ? this.selectRows.map(r => r[this.keyField]) : this.selectRows?.[0]?.[this.keyField]) || ''
    },
    selectSize() {
      return this.size || (this.$ELEMENT || {}).size;
    },
    collapseTagSize() {
      return ['small', 'mini'].indexOf(this.selectSize) > -1
        ? 'mini'
        : 'small';
    },
  },

  watch: {
    value: function(n, o) {
      if (!n) {
        if (this.selectRows.length) {
          this.selectRows = []
          this.currentRow = {}
        }
      } else {
        if (toString.call(this.data) === '[object String]') {

        } else {
          // value改变时,前端分页默认回显数据
          if (this.data.length) {
            if (this.multiple) {
              let arr = []
              n.forEach(r => {
                const row = this.data.find(r1 => r1[this.keyField] === r)
                if (row) arr.push(row)
              })
              this.noEmit()
              this.selectRows = arr
            } else {
              this.noEmit()
              this.selectRows = !this.isEmpty(n) ? [this.data.find(r => String(r[this.keyField]) === String(n))] : []
              this.currentRow = {}
            }
          }
        }
      }
    },
    selectRows: {
      handler: function(n, o) {
        if (n.length) {
          if (!this.multiple) {
            if (this.concatField) { // 需要拼接返回字段值
              const fieldArr = this.concatField.split('_')
              let arr = []
              fieldArr.forEach(r => {
                const s = this.selectRows?.[0]?.[r] || ''
                arr.push(s)
              })
              this.showVal = arr.length ? arr.filter(r => r).join('/') : ''
            } else {
              this.showVal = this.selectRows?.[0]?.[this.showField] || ''
            }
          }
        } else {
          this.showVal = ''
        }

        const toEmit = () => {
          this.$emit('input', this.keys)
          this.$emit('change', this.selectRows.slice())
        }

        if (!this.noEmitInput) {
          if (!this.multiple) {
            if (this.selectRows.length === 1) {
              if (this.selectRows[0][this.keyField] != this.defaultRow[this.keyField]) {
                toEmit()
              }
            } else {
              toEmit()
            }
          } else {
            // 多选
            if (this.selectRows.length && this.defaultRow.length) {
              if (this.selectRows.map(r => r[this.keyField]).join() != this.defaultRow.map(r => r[this.keyField]).join()) {
                toEmit()
              }
            } else {
              toEmit()
            }
          }
        }
      },
      deep: true
    },
    defaultRow: function(n, o) {
      // 存在问题1:外部值变更,就算值没变,也会触发defaultRow的watch
      if (!this.multiple && toString.call(this.data) === '[object String]') {
        if (Object.keys(n).length && !equals(n, o)) {
          if (!this.isEmpty(n[this.keyField])) {
            this.noEmit()
            const row = this.list.find(r => r[this.keyField] === n[this.keyField])
            this.selectRows = [row || n]
          }
        }
      } else {}
    },
    data: function(n) {
      if (toString.call(n) === '[object String]') {

      } else {
        // data切换时,前端分页默认回显数据
        if (n.length && this.value) {
          this.noEmit()
          this.selectRows = [n.find(r => String(r[this.keyField]) === String(this.value))]
        }
        this.currentRow = {}
      }
    }
  }
};
</script>
<style scoped lang="scss">
.popover-warp {
  display: block;
}
.select-main {
}
.base-search {
  margin: 0 0 12px;
  ::v-deep .el-form-item {
    margin-bottom: 0;
  }
  ::v-deep .el-form-item--small .el-input__suffix .el-icon-circle-close {
    line-height: 28px !important;
  }
}
.base-table {
  max-width: 600px;
  ::v-deep .el-table thead th.el-table__cell {
    background-color: #fff !important;
  }
  ::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
    background-color: #f5f7fa;
  }
  ::v-deep .el-table__body tr.current-row > td.el-table__cell {
    background-color: #f5f7fa;
  }
  ::v-deep .el-table__body tr.base-select-row {
    color: var(--current-color, #1890ff);
    font-weight: 600;
  }
  ::v-deep .el-table th.el-table__cell.is-leaf,::v-deep .el-table td.el-table__cell {
    border-bottom: 0;
  }
  ::v-deep .el-table::before {
    height: 0;
  }
}
.base-pagination {
  margin-top: 5px;
  text-align: center;
  ::v-deep .el-pagination {
    padding: 0;
    color: #505050;
  }
}
::v-deep .el-icon-arrow-down {
  position: absolute;
  top: 0px;
  right: 0px;
  transition: transform .3s ease;
  transform: rotateZ(0deg);
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #C0C4CC;
}
::v-deep .el-icon-circle-close {
  cursor: pointer;
  position: absolute;
  top: 0px;
  right: 0px;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #C0C4CC;
}
::v-deep .open {
  transform: rotateZ(-180deg);
}
.referenceClass {
  position: relative;
}
::v-deep .referenceClass .el-input__inner, .suffixClass {
  cursor: pointer;
}

.hidden-inp {
  z-index: -1;
  opacity: 0;
}

.base-select_tags {
  position: absolute;
  top: 0;
  width: 100%;
  z-index: 1;
  min-height: 32px;
  line-height: 32px;
  padding: 2px 25px 2px 5px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  border-radius: 4px;
  background-color: #fff;
  display: flex;
  align-items: center;
  &:hover {
    border-color: #c0c4cc;
  }
  > span {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
  }
  .base-select_tags-placeholder {
    position: absolute;
    color: #c0c4cc;
    font-size: 13px;
  }
}

::v-deep .base-select_tags .el-icon-arrow-down, ::v-deep .base-select_tags .el-icon-circle-close  {
  right: 4px;
}

._bgc {
  border-color: #dcdfe6;
  &:hover {
    border-color: #c0c4cc;
  }
}

.base-tag {
  margin: 2px 6px 2px 0;
}



.el-form-item--mini .base-select_tags {
  min-height: 28px;
  line-height: 28px;
  padding: 1px 25px 1px 5px;
}
.el-form-item--small .base-select_tags {
  min-height: 32px;
  line-height: 32px;
}
.el-form-item--medium .base-select_tags {
  min-height: 36px;
  line-height: 36px;
}
.el-form-item--default .base-select_tags {
  min-height: 40px;
  line-height: 40px;
}

@media (max-width: 768px) {
  .base-table {
    max-width: 85vw;
  }
}
</style>