vxe-grid 全局自定义filter过滤器,支持字典过滤

一、vxe-table的全局筛选器filters的实现

官网例子:https://vxetable.cn/#/table/renderer/filter
进入之后:我们可以参照例子自行实现,也可以下载它的源码,进行调整

在这里插入图片描述
下载好后并解压,用vscode将解压后的文件打开。全局检索FilterInput,你工作中用的vxe-table多少版本的那就进多少版本的目录
在这里插入图片描述

点击进入,按照这个路劲找到对应文件夹
在这里插入图片描述
将filter相关的vue,复制到自己的项目中components文件夹下
在这里插入图片描述
将filter.tsx 到你的项目中去,常用的FilterInput和FilterContent组件,将其他的FilterExtend 和FilterComplex 相关的可以先注释掉。
在这里插入图片描述

打开filter.ts
在这里插入图片描述
这样就可以使用了,使用方式,按照官网的例子放到columns里即可,filters的结构不允许改变,他与filterRendes的组件是一一对应的
在这里插入图片描述
这里提供v4的相关源码代码:
FilterInput.vue

<template>
  <div class="my-filter-input">
    <vxe-input type="text" v-model="demo1.option.data" placeholder="支持回车筛选" @keyup="keyupEvent" @input="changeOptionEvent"></vxe-input>
  </div>
</template>

<script lang="ts">
import {
      
       defineComponent, PropType, reactive } from 'vue'
import {
      
       VxeInputEvents, VxeGlobalRendererHandles } from 'vxe-table'

export default defineComponent({
      
      
  name: 'FilterInput',
  props: {
      
      
    params: Object as PropType<VxeGlobalRendererHandles.RenderFilterParams>
  },
  setup (props) {
      
      
    const demo1 = reactive({
      
      
      option: null as any
    })

    const load = () => {
      
      
      const {
      
       params } = props
      if (params) {
      
      
        const {
      
       column } = params
        const option = column.filters[0]
        demo1.option = option
      }
    }

    const changeOptionEvent = () => {
      
      
      const {
      
       params } = props
      const {
      
       option } = demo1
      if (params && option) {
      
      
        const {
      
       $panel } = params
        const checked = !!option.data
        $panel.changeOption(null, checked, option)
      }
    }

    const keyupEvent: VxeInputEvents.Keyup = ({
       
        $event }) => {
      
      
      const {
      
       params } = props
      if (params) {
      
      
        const {
      
       $panel } = params
        if ($event.keyCode === 13) {
      
      
          $panel.confirmFilter($event)
        }
      }
    }

    load()

    return {
      
      
      demo1,
      changeOptionEvent,
      keyupEvent
    }
  }
})
</script>

<style scoped>
.my-filter-input {
      
      
  padding: 10px;
}
</style>

FilterContent.vue

<template>
  <div class="my-filter-content">
    <div class="my-fc-search">
      <div class="my-fc-search-top">
        <vxe-input v-model="demo1.option.data.sVal" placeholder="搜索"></vxe-input>
      </div>
      <div class="my-fc-search-content">
        <template v-if="demo1.valList.length">
          <ul class="my-fc-search-list my-fc-search-list-head">
            <li class="my-fc-search-item">
              <vxe-checkbox v-model="demo1.isAll" @change="changeAllEvent">全选</vxe-checkbox>
            </li>
          </ul>
          <ul class="my-fc-search-list my-fc-search-list-body">
            <li class="my-fc-search-item" v-for="(item, sIndex) in demo1.valList" :key="sIndex">
              <vxe-checkbox v-model="item.checked">{
    
    {
    
     item.value }}</vxe-checkbox>
            </li>
          </ul>
        </template>
        <template v-else>
          <div class="my-fc-search-empty">无匹配项</div>
        </template>
      </div>
    </div>
    <div class="my-fc-footer">
      <vxe-button status="primary" @click="confirmFilterEvent">确认</vxe-button>
      <vxe-button @click="resetFilterEvent">重置</vxe-button>
    </div>
  </div>
</template>

<script lang="ts">
import {
    
     defineComponent, PropType, reactive } from 'vue'
import {
    
     VxeGlobalRendererHandles } from 'vxe-table'
import XEUtils from 'xe-utils'

export default defineComponent({
    
    
  name: 'FilterContent',
  props: {
    
    
    params: Object as PropType<VxeGlobalRendererHandles.RenderFilterParams>
  },
  setup (props) {
    
    
    interface ColValItem {
    
    
      checked: boolean;
      value: string;
    }

    const demo1 = reactive({
    
    
      isAll: false,
      option: null as any,
      colValList: [] as ColValItem[],
      valList: [] as ColValItem[]
    })

    const load = () => {
    
    
      const {
    
     params } = props
      if (params) {
    
    
        const {
    
     $table, column } = params
        const {
    
     fullData } = $table.getTableData()
        const option = column.filters[0]
        const {
    
     vals } = option.data
        const colValList = Object.keys(XEUtils.groupBy(fullData, column.field)).map((val) => {
    
    
          return {
    
    
            checked: vals.includes(val),
            value: val
          } as ColValItem
        })
        demo1.option = option
        demo1.colValList = colValList
        demo1.valList = colValList
      }
    }

    const searchEvent = () => {
    
    
      const {
    
     option, colValList } = demo1
      if (option) {
    
    
        demo1.valList = option.data.sVal ? colValList.filter((item) => item.value.indexOf(option.data.sVal) > -1) : colValList
      }
    }

    const changeAllEvent = () => {
    
    
      const {
    
     isAll } = demo1
      demo1.valList.forEach((item) => {
    
    
        item.checked = isAll
      })
    }

    const confirmFilterEvent = () => {
    
    
      const {
    
     params } = props
      const {
    
     option, valList } = demo1
      if (params && option) {
    
    
        const {
    
     data } = option
        const {
    
     $panel } = params
        data.vals = valList.filter((item) => item.checked).map((item) => item.value)
        $panel.changeOption(null, true, option)
        $panel.confirmFilter()
      }
    }

    const resetFilterEvent = () => {
    
    
      const {
    
     params } = props
      if (params) {
    
    
        const {
    
     $panel } = params
        $panel.resetFilter()
      }
    }

    load()

    return {
    
    
      demo1,
      searchEvent,
      changeAllEvent,
      confirmFilterEvent,
      resetFilterEvent
    }
  }
})
</script>

<style>
.my-filter-content {
    
    
  padding: 10px;
  user-select: none;
}
.my-filter-content .my-fc-search .my-fc-search-top {
    
    
  position: relative;
  padding: 5px 0;
}
.my-filter-content .my-fc-search .my-fc-search-top > input {
    
    
  border: 1px solid #ABABAB;
  padding: 0 20px 0 2px;
  width: 200px;
  height: 22px;
  line-height: 22px;
}
.my-filter-content .my-fc-search .my-fc-search-content {
    
    
  padding: 2px 10px;
}
.my-filter-content .my-fc-search-empty {
    
    
  text-align: center;
  padding: 20px 0;
}
.my-filter-content .my-fc-search-list {
    
    
  margin: 0;
  padding: 0;
  list-style: none;
}
.my-filter-content .my-fc-search-list-body {
    
    
  overflow: auto;
  height: 120px;
}
.my-filter-content .my-fc-search-list .my-fc-search-item {
    
    
  padding: 2px 0;
  display: block;
}
.my-filter-content .my-fc-footer {
    
    
  text-align: right;
  padding-top: 10px;
}
.my-filter-content .my-fc-footer button {
    
    
  padding: 0 15px;
  margin-left: 15px;
}
</style>

FilterExtend.vue

<template>
  <div class="my-filter-excel">
    <div class="my-fe-top">
      <ul class="my-fe-menu-group">
        <li class="my-fe-menu-link">
          <span>升序</span>
        </li>
        <li class="my-fe-menu-link">
          <span>倒序</span>
        </li>
      </ul>
      <ul class="my-fe-menu-group">
        <li class="my-fe-menu-link" @click="resetFilterEvent">
          <span>清除筛选</span>
        </li>
        <li class="my-fe-menu-link">
          <i class="vxe-icon-question-circle-fill my-fe-menu-link-left-icon"></i>
          <span>筛选条件</span>
          <i class="vxe-icon-question-circle-fill my-fe-menu-link-right-icon"></i>
          <div class="my-fe-menu-child-list">
            <ul class="my-fe-child-menu-group-list" v-for="(cList, gIndex) in demo1.caseGroups" :key="gIndex">
              <li v-for="(cItem, cIndex) in cList" :key="cIndex" class="my-fe-child-menu-item" @click="childMenuClickEvent(cItem)">
                <span>{
   
   { cItem.label }}</span>
              </li>
            </ul>
          </div>
        </li>
      </ul>
    </div>
    <div class="my-fe-search">
      <div class="my-fe-search-top">
        <input v-model="demo1.option.data.sVal" placeholder="搜索"/>
        <i class="vxe-icon-question-circle-fill my-fe-search-icon"></i>
      </div>
      <ul class="my-fe-search-list">
        <li class="my-fe-search-item" @click="sAllEvent">
          <i class="vxe-icon-question-circle-fill my-fe-search-item-icon"></i>
          <span>(全选)</span>
        </li>
        <li class="my-fe-search-item" v-for="(val, sIndex) in searchList" :key="sIndex" @click="sItemEvent(val)">
          <i :class="[demo1.option.data.vals.indexOf(val) === -1 ? 'vxe-icon-question-circle-fill my-fe-search-item-icon' : 'vxe-icon-question-circle-fill my-fe-search-item-icon']"></i>
          <span>{
   
   { val }}</span>
        </li>
      </ul>
    </div>
    <div class="my-fe-footer">
      <vxe-button status="primary" @click="confirmFilterEvent">确认</vxe-button>
      <vxe-button @click="resetFilterEvent">重置</vxe-button>
    </div>
  </div>
</template>

<script lang="ts">
import {
      
       defineComponent, PropType, reactive, computed } from 'vue'
import {
      
       VXETable, VxeGlobalRendererHandles } from 'vxe-table'
import XEUtils from 'xe-utils'

export default defineComponent({
      
      
  name: 'FilterExtend',
  props: {
      
      
    params: Object as PropType<VxeGlobalRendererHandles.RenderFilterParams>
  },
  setup (props) {
      
      
    interface CaseItem {
      
      
      label: string;
      value: string;
    }

    const demo1 = reactive({
      
      
      option: null as any,
      colValList: [] as string[],
      caseGroups: [
        [
          {
      
       value: 'equal', label: '等于' },
          {
      
       value: 'ne', label: '不等于' }
        ],
        [
          {
      
       value: 'greater', label: '大于' },
          {
      
       value: 'ge', label: '大于或等于' },
          {
      
       value: 'less', label: '小于' },
          {
      
       value: 'le', label: '小于或等于' }
        ]
      ] as CaseItem[][]
    })

    const searchList = computed(() => {
      
      
      const {
      
       option, colValList } = demo1
      if (option) {
      
      
        return option.data.sVal ? colValList.filter(val => val.indexOf(option.data.sVal) > -1) : colValList
      }
      return []
    })

    const load = () => {
      
      
      const {
      
       params } = props
      if (params) {
      
      
        const {
      
       $table, column } = params
        const {
      
       fullData } = $table.getTableData()
        const option = column.filters[0]
        const colValList = Object.keys(XEUtils.groupBy(fullData, column.field))
        demo1.option = option
        demo1.colValList = colValList
      }
    }

    const sAllEvent = () => {
      
      
      const {
      
       option } = demo1
      if (option) {
      
      
        const {
      
       data } = option
        if (data.vals.length > 0) {
      
      
          data.vals = []
        } else {
      
      
          data.vals = demo1.colValList
        }
      }
    }

    const sItemEvent = (val: string) => {
      
      
      const {
      
       option } = demo1
      if (option) {
      
      
        const {
      
       data } = option
        const vIndex = data.vals.indexOf(val)
        if (vIndex === -1) {
      
      
          data.vals.push(val)
        } else {
      
      
          data.vals.splice(vIndex, 1)
        }
      }
    }

    const confirmFilterEvent = () => {
      
      
      const {
      
       params } = props
      const {
      
       option } = demo1
      if (params && option) {
      
      
        const {
      
       data } = option
        const {
      
       $panel } = params
        data.f1 = ''
        data.f2 = ''
        $panel.changeOption(null, true, option)
        $panel.confirmFilter()
      }
    }

    const resetFilterEvent = () => {
      
      
      const {
      
       params } = props
      if (params) {
      
      
        const {
      
       $panel } = params
        $panel.resetFilter()
      }
    }

    const childMenuClickEvent = (cItem: CaseItem) => {
      
      
      VXETable.modal.alert({
      
       content: cItem.label })
    }

    load()

    return {
      
      
      demo1,
      searchList,
      sAllEvent,
      sItemEvent,
      confirmFilterEvent,
      resetFilterEvent,
      childMenuClickEvent
    }
  }
})
</script>

<style>
.my-filter-excel {
      
      
  user-select: none;
}
.my-filter-excel .my-fe-top .my-fe-menu-group {
      
      
  position: relative;
  margin: 0;
  padding: 0;
}
.my-filter-excel .my-fe-top .my-fe-menu-group:after {
      
      
  content: "";
  position: absolute;
  width: 190px;
  right: 0;
  bottom: 0;
  border-bottom: 1px solid #E2E4E7;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link {
      
      
  position: relative;
  padding: 4px 20px 4px 30px;
  cursor: pointer;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link:hover {
      
      
  background-color: #C5C5C5;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link-left-icon {
      
      
  position: absolute;
  left: 10px;
  top: 6px;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link-right-icon {
      
      
  position: absolute;
  right: 10px;
  top: 6px;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link:hover .my-fe-menu-child-list {
      
      
  display: block;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link .my-fe-menu-child-list {
      
      
  display: none;
  position: absolute;
  top: 0;
  right: -120px;
  width: 120px;
  background-color: #fff;
  border: 1px solid #DADCE0;
  box-shadow: 3px 3px 4px -2px rgba(0, 0, 0, 0.6);
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link .my-fe-child-menu-group-list {
      
      
  position: relative;
  padding: 0;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link .my-fe-child-menu-group-list:after {
      
      
  content: "";
  position: absolute;
  width: 90px;
  right: 0;
  bottom: 0;
  border-bottom: 1px solid #E2E4E7;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link .my-fe-child-menu-group-list > .my-fe-child-menu-item {
      
      
  position: relative;
  padding: 4px 20px 4px 30px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.my-filter-excel .my-fe-top .my-fe-menu-group .my-fe-menu-link .my-fe-child-menu-group-list > .my-fe-child-menu-item:hover {
      
      
  background-color: #C5C5C5;
}
.my-filter-excel .my-fe-search {
      
      
  padding: 0 10px 0 30px;
}
.my-filter-excel .my-fe-search .my-fe-search-top {
      
      
  position: relative;
  padding: 5px 0;
}
.my-filter-excel .my-fe-search .my-fe-search-top > input {
      
      
  border: 1px solid #ABABAB;
  padding: 0 20px 0 2px;
  width: 200px;
  height: 22px;
  line-height: 22px;
}
.my-filter-excel .my-fe-search .my-fe-search-top > .my-fe-search-icon {
      
      
  position: absolute;
  right: 5px;
  top: 10px;
}
.my-filter-excel .my-fe-search .my-fe-search-list {
      
      
  margin: 0;
  border: 1px solid #E2E4E7;
  padding: 2px 10px;
  overflow: auto;
  height: 140px;
}
.my-filter-excel .my-fe-search .my-fe-search-list .my-fe-search-item {
      
      
  cursor: pointer;
  padding: 2px 0;
}
.my-filter-excel .my-fe-search .my-fe-search-list .my-fe-search-item .my-fe-search-item-icon {
      
      
  width: 16px;
}
.my-filter-excel .my-fe-footer {
      
      
  text-align: right;
  padding: 10px 10px 10px 0;
}
.my-fe-popup .my-fe-popup-filter {
      
      
  padding-left: 30px;
}
.my-fe-popup .my-fe-popup-filter > .my-fe-popup-filter-select {
      
      
  width: 120px;
}
.my-fe-popup .my-fe-popup-filter > .my-fe-popup-filter-input {
      
      
  margin-left: 15px;
  width: 380px;
}
.my-fe-popup .my-fe-popup-describe {
      
      
  padding: 20px 0 10px 0;
}
.my-fe-popup .my-fe-popup-concat {
      
      
  padding-left: 50px;
}
.my-fe-popup .my-fe-popup-footer {
      
      
  text-align: right;
}
</style>

FilterComplex.vue

<template>
  <div class="my-filter-complex">
    <div class="my-fc-type">
      <vxe-radio v-model="demo1.option.data.type" name="fType" label="has">包含</vxe-radio>
      <vxe-radio v-model="demo1.option.data.type" name="fType" label="eq">等于</vxe-radio>
    </div>
    <div class="my-fc-name">
      <vxe-input v-model="demo1.option.data.name" type="text" placeholder="请输入名称" @input="changeOptionEvent()"></vxe-input>
    </div>
    <div class="my-fc-footer">
      <vxe-button status="primary" @click="confirmEvent">确认</vxe-button>
      <vxe-button @click="resetEvent">重置</vxe-button>
    </div>
  </div>
</template>

<script lang="ts">
import {
    
     defineComponent, PropType, reactive } from 'vue'
import {
    
     VxeGlobalRendererHandles } from 'vxe-table'

export default defineComponent({
    
    
  name: 'FilterComplex',
  props: {
    
    
    params: Object as PropType<VxeGlobalRendererHandles.RenderFilterParams>
  },
  setup (props) {
    
    
    const demo1 = reactive({
    
    
      option: null as any
    })

    const load = () => {
    
    
      const {
    
     params } = props
      if (params) {
    
    
        const {
    
     column } = params
        const option = column.filters[0]
        demo1.option = option
      }
    }

    const changeOptionEvent = () => {
    
    
      const {
    
     params } = props
      const {
    
     option } = demo1
      if (params && option) {
    
    
        const {
    
     $panel } = params
        const checked = !!option.data.name
        $panel.changeOption(null, checked, option)
      }
    }

    const confirmEvent = () => {
    
    
      const {
    
     params } = props
      if (params) {
    
    
        const {
    
     $panel } = params
        $panel.confirmFilter()
      }
    }

    const resetEvent = () => {
    
    
      const {
    
     params } = props
      if (params) {
    
    
        const {
    
     $panel } = params
        $panel.resetFilter()
      }
    }

    load()

    return {
    
    
      demo1,
      changeOptionEvent,
      confirmEvent,
      resetEvent
    }
  }
})
</script>

<style scoped>
.my-filter-complex {
    
    
  width: 260px;
  padding: 5px 15px 10px 15px;
}
.my-filter-complex .my-fc-type {
    
    
  padding: 8px 0;
}
.my-filter-complex .my-fc-footer {
    
    
  text-align: center;
}
</style>

filter.tsx

import {
    
     VXETable } from 'vxe-table'
import FilterInput from './components/FilterInput.vue'
import FilterContent from './components/FilterContent.vue'
import FilterComplex from './components/FilterComplex.vue'
import FilterExtend from './components/FilterExtend.vue'

// 创建一个简单的输入框筛选
VXETable.renderer.add('FilterInput', {
    
    
  // 筛选模板
  renderFilter (renderOpts, params) {
    
    
    return <FilterInput params={
    
     params }></FilterInput>
  },
  // 重置数据方法
  filterResetMethod (params) {
    
    
    const {
    
     options } = params
    options.forEach((option) => {
    
    
      option.data = ''
    })
  },
  // 重置筛选复原方法(当未点击确认时,该选项将被恢复为默认值)
  filterRecoverMethod ({
    
     option }) {
    
    
    option.data = ''
  },
  // 筛选方法
  filterMethod (params) {
    
    
    const {
    
     option, row, column } = params
    const {
    
     data } = option
    const cellValue = row[column.field]
    if (cellValue) {
    
    
      return cellValue.indexOf(data) > -1
    }
    return false
  }
})

// 创建一个条件的渲染器
VXETable.renderer.add('FilterComplex', {
    
    
  // 不显示底部按钮,使用自定义的按钮
  showFilterFooter: false,
  // 筛选模板
  renderFilter (renderOpts, params) {
    
    
    return <FilterComplex params={
    
     params }></FilterComplex>
  },
  // 重置数据方法
  filterResetMethod (params) {
    
    
    const {
    
     options } = params
    options.forEach((option) => {
    
    
      option.data = {
    
     type: 'has', name: '' }
    })
  },
  // 筛选数据方法
  filterMethod (params) {
    
    
    const {
    
     option, row, column } = params
    const cellValue = row[column.field]
    const {
    
     name } = option.data
    if (cellValue) {
    
    
      return cellValue.indexOf(name) > -1
    }
    return false
  }
})

// 创建一个支持列内容的筛选
VXETable.renderer.add('FilterContent', {
    
    
  // 不显示底部按钮,使用自定义的按钮
  showFilterFooter: false,
  // 筛选模板
  renderFilter (renderOpts, params) {
    
    
    return <FilterContent params={
    
     params }></FilterContent>
  },
  // 重置数据方法
  filterResetMethod (params) {
    
    
    const {
    
     options } = params
    options.forEach((option) => {
    
    
      option.data = {
    
     vals: [], sVal: '' }
    })
  },
  // 筛选数据方法
  filterMethod (params) {
    
    
    const {
    
     option, row, column } = params
    const {
    
     vals } = option.data
    const cellValue = row[column.field]
    return vals.includes(cellValue)
  }
})

// 创建一个复杂的筛选器
VXETable.renderer.add('FilterExtend', {
    
    
  // 不显示底部按钮,使用自定义的按钮
  showFilterFooter: false,
  // 筛选模板
  renderFilter (renderOpts, params) {
    
    
    return <FilterExtend params={
    
     params }></FilterExtend>
  },
  // 重置数据方法
  filterResetMethod (params) {
    
    
    const {
    
     options } = params
    options.forEach((option) => {
    
    
      option.data = {
    
     vals: [], sVal: '', fMenu: '', f1Type: '', f1Val: '', fMode: 'and', f2Type: '', f2Val: '' }
    })
  },
  // 筛选数据方法
  filterMethod (params) {
    
    
    const {
    
     option, row, column } = params
    const cellValue = row[column.field]
    const {
    
     vals, f1Type, f1Val } = option.data
    if (cellValue) {
    
    
      if (f1Type) {
    
    
        return cellValue.indexOf(f1Val) > -1
      } else if (vals.length) {
    
    
        // 通过指定值筛选
        return vals.includes(cellValue)
      }
    }
    return false
  }
})

二、自行优化filters实现,支持字典的转换

通过上面的方式,已经实现了过滤器,但是多次使用,衍生出一个问题,就是如果我的列是个字典列,并且字典值的转换用的是columns内的solt,如FilterContent组件,它将会渲染的时字典的value列表,并非用户看到的中文列表,用户怎么知道你过滤器的看到的1,2,3和你的数据时对应的,或者时FilterInput 你输入的必须时字典code,不能是中文,否则将无法筛选出数据。

解决问题1: 在数据库查询数据时,根据该列关联字典表,将中文给转换过来,
解决问题2:前端查询数据后,在给表格赋值时将字典数据转换回来,这样有些麻烦,
解决问题3:优化筛选组件,使其支持字典的筛选。由于项目中只用到了FilterInput和FilterContent这里只提供这两个组件的优化。
1. 先说下在哪里做了调整
filtes的结构上做了些调整,data里都增加了字典的数据, filterContent 参数移除sval参数,同样在filterContent 组件内input筛选也将被移除,只保留多选列表进行筛选
在这里插入图片描述
FilterInput
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

“FilterContent.vue”,它与FilterInput不一样,需要面板打开时,就显示过滤列表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 调整后的代码
FilterContent.vue

<template>
  <div class="my-filter-content">
    <div class="my-fc-search">
      <div class="my-fc-search-top">
        <!-- <vxe-input
          v-model="demo1.option.data.sVal"
          placeholder="搜索"
        ></vxe-input> -->
      </div>
      <div class="my-fc-search-content">
        <template v-if="demo1.valList.length">
          <ul class="my-fc-search-list my-fc-search-list-head">
            <li class="my-fc-search-item">
              <vxe-checkbox v-model="demo1.isAll" @change="changeAllEvent"
                >全选</vxe-checkbox
              >
            </li>
          </ul>
          <ul class="my-fc-search-list my-fc-search-list-body">
            <li
              v-for="(item, sIndex) in demo1.valList"
              :key="sIndex"
              class="my-fc-search-item"
            >
              <vxe-checkbox v-model="item.checked">{
   
   {
                item.label
              }}</vxe-checkbox>
            </li>
          </ul>
        </template>
        <template v-else>
          <div class="my-fc-search-empty">无匹配项</div>
        </template>
      </div>
    </div>
    <div class="my-fc-footer">
      <vxe-button status="primary" @click="confirmFilterEvent">确认</vxe-button>
      <vxe-button @click="resetFilterEvent">重置</vxe-button>
    </div>
  </div>
</template>

<script lang="ts">
  import {
      
       defineComponent, PropType, reactive } from 'vue';
  import {
      
       VxeGlobalRendererHandles } from 'vxe-table';
  import XEUtils from 'xe-utils';

  export default defineComponent({
      
      
    name: 'FilterContent',
    props: {
      
      
      params: Object as PropType<VxeGlobalRendererHandles.RenderFilterParams>,
    },
    setup(props) {
      
      
      interface ColValItem {
      
      
        checked: boolean;
        value: string;
        label: string;
      }

      const demo1 = reactive({
      
      
        isAll: false,
        option: null as any,
        colValList: [] as ColValItem[],
        valList: [] as ColValItem[],
      });

      const load = () => {
      
      
        const {
      
       params } = props;
        if (params) {
      
      
          const {
      
       $table, column } = params;
          const {
      
       fullData } = $table.getTableData();
          const option: any = column.filters[0];
          const {
      
       vals } = option.data;
          /*
           * 重置数据,解决bug:可能由于动态渲染columns,导致过滤面板勾选后点击确认按钮,过滤后无法更新表格数据,
           * 经过尝试,点击重置按钮,在进行过滤,可正常过滤,所以这里重置下data ;问题得以解决
           */
          option.data = {
      
      
            vals: option.data.vals,
            // sVal: option.data.sVal,
            dict: option.data.dict ? option.data.dict : undefined,
          };

          if (option.data.dict && option.data.dict.length > 0) {
      
      
            const colValList = Object.keys(
              XEUtils.groupBy(fullData, column.field)
            ).map((val) => {
      
      
              const value = option.data.dict.filter(
                (item) => String(item.value) === String(val)
              );
              let label = val;
              if (value.length > 0) {
      
      
                label = value[0]
                  .label;
              }
              return {
      
      
                checked: vals.includes(val),
                value: val,
                label,
              } as ColValItem;
            });
            demo1.option = option;
            demo1.colValList = colValList;
            demo1.valList = colValList;
          } else {
      
      
            const colValList = Object.keys(
              XEUtils.groupBy(fullData, column.field)
            ).map((val) => {
      
      
              return {
      
      
                checked: vals.includes(val),
                value: val,
                label: val,
              } as ColValItem;
            });
            demo1.option = option;
            demo1.colValList = colValList;
            demo1.valList = colValList;
          }
        }
      };

      // const searchEvent = () => {
      
      
      //   const { option, colValList } = demo1;
      //   if (option) {
      
      
      //     demo1.valList = option.data.sVal
      //       ? colValList.filter(
      //           (item) => item.value.indexOf(option.data.sVal) > -1
      //         )
      //       : colValList;
      //   }
      // };

      const changeAllEvent = () => {
      
      
        const {
      
       isAll } = demo1;
        demo1.valList.forEach((item) => {
      
      
          item.checked = isAll;
        });
      };

      const confirmFilterEvent = () => {
      
      
        const {
      
       params } = props;
        const {
      
       option, valList } = demo1;
        if (params && option) {
      
      
          const {
      
       data } = option;
          const {
      
       $panel } = params;
          data.vals = valList
            .filter((item) => item.checked)
            .map((item) => item.value);
          $panel.changeOption(null, true, option);
          $panel.confirmFilter();
        }
      };

      const resetFilterEvent = () => {
      
      
        const {
      
       params } = props;
        if (params) {
      
      
          const {
      
       $panel } = params;
          $panel.resetFilter();
        }
      };

      load();

      return {
      
      
        demo1,
        // searchEvent,
        changeAllEvent,
        confirmFilterEvent,
        resetFilterEvent,
      };
    },
  });
</script>

<style>
  .my-filter-content {
      
      
    padding: 10px;
    user-select: none;
  }
  .my-filter-content .my-fc-search .my-fc-search-top {
      
      
    position: relative;
    padding: 5px 0;
  }
  .my-filter-content .my-fc-search .my-fc-search-top > input {
      
      
    border: 1px solid #ababab;
    padding: 0 20px 0 2px;
    width: 200px;
    height: 22px;
    line-height: 22px;
  }
  .my-filter-content .my-fc-search .my-fc-search-content {
      
      
    padding: 2px 10px;
  }
  .my-filter-content .my-fc-search-empty {
      
      
    text-align: center;
    padding: 20px 0;
  }
  .my-filter-content .my-fc-search-list {
      
      
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .my-filter-content .my-fc-search-list-body {
      
      
    overflow: auto;
    height: 120px;
  }
  .my-filter-content .my-fc-search-list .my-fc-search-item {
      
      
    padding: 2px 0;
    display: block;
  }
  .my-filter-content .my-fc-footer {
      
      
    text-align: right;
    padding-top: 10px;
  }
  .my-filter-content .my-fc-footer button {
      
      
    padding: 0 15px;
    margin-left: 15px;
  }
</style>

FilterInput.vue

<template>
  <div class="my-filter-input">
    <vxe-input
      v-model="demo1.option.data.sVal"
      type="text"
      placeholder="支持回车筛选"
      @keyup="keyupEvent"
      @input="changeOptionEvent"
    ></vxe-input>
  </div>
</template>

<script lang="ts">
  import {
      
       defineComponent, PropType, reactive } from 'vue';
  import {
      
       VxeInputEvents, VxeGlobalRendererHandles } from 'vxe-table';

  export default defineComponent({
      
      
    name: 'FilterInput',
    props: {
      
      
      params: Object as PropType<VxeGlobalRendererHandles.RenderFilterParams>,
    },
    setup(props) {
      
      
      const demo1 = reactive({
      
      
        option: null as any,
      });

      const load = () => {
      
      
        const {
      
       params } = props;
        if (params) {
      
      
          const {
      
       column } = params;
          const option = column.filters[0];
          option.data = {
      
      
            sVal: option.data.sVal,
            dict: option.data.dict ? option.data.dict : undefined,
          };
          demo1.option = option;
        }
      };

      const changeOptionEvent = () => {
      
      
        const {
      
       params } = props;
        const {
      
       option } = demo1;
        if (params && option) {
      
      
          const {
      
       $panel } = params;
          const checked = !!option.data.sVal;
          $panel.changeOption(null, checked, option);
        }
      };

      const keyupEvent: VxeInputEvents.Keyup = ({
       
        $event }) => {
      
      
        const {
      
       params } = props;
        if (params) {
      
      
          const {
      
       $panel } = params;
          if ($event.keyCode === 13) {
      
      
            $panel.confirmFilter($event);
          }
        }
      };

      load();

      return {
      
      
        demo1,
        changeOptionEvent,
        keyupEvent,
      };
    },
  });
</script>

<style scoped>
  .my-filter-input {
      
      
    padding: 10px;
  }
</style>

filter.tsx

import {
    
     VXETable } from 'vxe-table';
import FilterInput from '@/components/Filter/FilterInput.vue';
import FilterContent from '@/components/Filter/FilterContent.vue';
// import FilterComplex from './components/FilterComplex.vue'
// import FilterExtend from './components/FilterExtend.vue'

// 创建一个简单的输入框筛选
VXETable.renderer.add('FilterInput', {
    
    
  // 筛选模板
  renderFilter: (renderOpts, params) => {
    
    
    return [<FilterInput params={
    
     params }></FilterInput>];
  },
  // 重置数据方法
  filterResetMethod (params) {
    
    
    const {
    
     options } = params;
    options.forEach((option) => {
    
    
      option.data = {
    
    
        sVal: '',
        dict: option.data.dict ? option.data.dict : undefined,
      };
    });
  },
  // 重置筛选复原方法(当未点击确认时,该选项将被恢复为默认值)
  filterRecoverMethod ({
    
     option }) {
    
    
    option.data = {
    
    
      sVal: '',
      dict: option.data.dict ? option.data.dict : undefined,
    };
  },
  // 筛选方法
  filterMethod (params) {
    
    
    const {
    
     option, row, column } = params;
    const {
    
     data } = option;
    if(data.dict && data.dict.length > 0) {
    
    
      const cellValue = row[column.field];
      const dictItem = data.dict.filter((item)=>item.label.indexOf(data.sVal) > -1).map((item) => item.value);
      if(dictItem.length > 0){
    
    
        return dictItem.indexOf(cellValue) > -1;
      }
    } else {
    
    
      const cellValue = row[column.field];
      if (cellValue) {
    
    
        return cellValue.indexOf(data.sVal) > -1;
      }
    }
    return false;
  },
});

// // 创建一个条件的渲染器
// VXETable.renderer.add('FilterComplex', {
    
    
//   // 不显示底部按钮,使用自定义的按钮
//   showFilterFooter: false,
//   // 筛选模板
//   renderFilter (renderOpts, params) {
    
    
//     return <FilterComplex params={ params }></FilterComplex>
//   },
//   // 重置数据方法
//   filterResetMethod (params) {
    
    
//     const { options } = params
//     options.forEach((option) => {
    
    
//       option.data = { type: 'has', name: '' }
//     })
//   },
//   // 筛选数据方法
//   filterMethod (params) {
    
    
//     const { option, row, column } = params
//     const cellValue = row[column.field]
//     const { name } = option.data
//     if (cellValue) {
    
    
//       return cellValue.indexOf(name) > -1
//     }
//     return false
//   }
// })

// 创建一个支持列内容的筛选
VXETable.renderer.add('FilterContent', {
    
    
  // 不显示底部按钮,使用自定义的按钮
  showFilterFooter: false,
  // 筛选模板
  renderFilter (renderOpts, params) {
    
    
    return [<FilterContent params={
    
     params }></FilterContent>];
  },
  // 重置数据方法
  filterResetMethod (params) {
    
    
    const {
    
     options } = params;
    options.forEach((option) => {
    
    
      option.data = {
    
     vals: [],dict: option.data.dict ? option.data.dict : undefined};
    });
  },
  // 筛选数据方法
  filterMethod (params) {
    
    
    const {
    
     option, row, column } = params;
    const {
    
     vals } = option.data;
    const cellValue = row[column.field];
    console.log(cellValue,vals.includes(cellValue))
    return vals.includes(cellValue);
  },
});

// // 创建一个复杂的筛选器
// VXETable.renderer.add('FilterExtend', {
    
    
//   // 不显示底部按钮,使用自定义的按钮
//   showFilterFooter: false,
//   // 筛选模板
//   renderFilter (renderOpts, params) {
    
    
//     return <FilterExtend params={ params }></FilterExtend>
//   },
//   // 重置数据方法
//   filterResetMethod (params) {
    
    
//     const { options } = params
//     options.forEach((option) => {
    
    
//       option.data = { vals: [], sVal: '', fMenu: '', f1Type: '', f1Val: '', fMode: 'and', f2Type: '', f2Val: '' }
//     })
//   },
//   // 筛选数据方法
//   filterMethod (params) {
    
    
//     const { option, row, column } = params
//     const cellValue = row[column.field]
//     const { vals, f1Type, f1Val } = option.data
//     if (cellValue) {
    
    
//       if (f1Type) {
    
    
//         return cellValue.indexOf(f1Val) > -1
//       } else if (vals.length) {
    
    
//         // 通过指定值筛选
//         return vals.includes(cellValue)
//       }
//     }
//     return false
//   }
// })


猜你喜欢

转载自blog.csdn.net/weixin_43865196/article/details/129416996