需求:有一组数据,需要展示成为树的形式,由于数据量庞大需要将树设置为懒加载(异步树),并且需要针对在input输入关键字触发搜索之后,得到一颗搜索树为同步树,并将关键字渲染高亮。

此篇只记录,远程搜索和同步树的实现,使用autocomplete远程搜索组件和el-tree组件。

非搜索树状态下

element中浏览器搜索失效 el-tree搜索_element中浏览器搜索失效


搜索树状态下

element中浏览器搜索失效 el-tree搜索_下拉框_02

1. 远程搜索框

<el-autocomplete
        v-model="filterText" //绑定的值
        ref="selectSuggest"
        :fetch-suggestions="querySearchAsync" //获取后台数据
        :trigger-on-focus="triggerOnFocus"    //在获得焦点时是否显示下拉框
        :debounce="300"      //防抖函数,300毫秒请求一次后台
        placeholder="搜索组织"
        class="inline-input"
        :clear-icon-click="clearSearch"      //添加清除标志
        @select="handlefilterNode"      // 在点击下拉框后触发函数,可写入请求搜索树后台接口
        @keyup.enter.native="handlefilterNode"      // 设置回车键
      >
        <i slot="suffix"
          class="el-input__icon h-icon-search"      //加入搜索icon
          @click="handlefilterNode"      //添加搜索icon触发函数
        ></i>
      </el-autocomplete>

搜索使用了autocomplete组件,对其做一些设置,模仿百度搜索的功能。

// 设置当搜索内未输入的时候,不显示下拉框
 computed: {
    triggerOnFocus: function () {
      return this.filterText !== ''
    }
  },

// 调用后台函数获得联想词条
  querySearchAsync (queryString, cb) {
      if (queryString) {
        const data = {
          //传给后端的字段
          name: queryString,
        }
        // 后端函数调用返回联想词列表
        getSelectSearchKey(data).then(res => {
          if (res.data.length > 0) {
			  // 组件将data渲染成为select下拉框的样式
			  cb(res.data)
          } else {
		 	// 关闭下拉框事件
            this.$refs.selectSuggest.close()
          }
        }).catch(err => {
          console.error(err)
        })
      }
    },

值得注意的是,cb(res.data)要处理的数据res.data必须是键值对的形式,否则会报错。比如:

[{
      value: 'perfect三全鲜食(北新泾店)'
    },
    {
      value: 'Hot honey 首尔炸鸡(仙霞路)'
    },
    {
      value: '新旺角茶餐厅'
    },
    {
      value: '泷千家(天山西路店)'
    }]
// 请求后台获得搜索树——同步树
    handlefilterNode (searchVal) {
      // 是否处于搜索状态
      this.searchFlag = true
      searchVal = this.filterText
      // 搜索内容是否为空
      if (searchVal !== '') {
        const data = {
        //传给后台的字段
          name: searchVal
        }
        //调用后台接口
        queryOrgByFuzzy(data).then(res => {
          // 将拿到的平行结构的数据,直接赋给搜索树绑定的data
          this.searchData = res.data
        }).catch(err => {
          console.error(err)
        })
        // 关闭下拉框事件
        this.$refs.selectSuggest.close()
      } else {
        this.clearSearch()
      }
    },
  • this.$refs.selectSuggest.close()在autocomplete这个组件中,有close的方法用于关闭下拉框,原组件下拉框只要聚焦input就会有下拉框。按照百度的做法,没有联想词条以及搜索树请求到时,下拉框属于关闭状态。
  • 并且原组件是没有适配回车键和搜索icon,回车或者点击搜索icon时,下拉框不会关闭,需要使用this.$refs.selectSuggest.close()主动关闭。
  • 原官网并没有写出该方法,建议找不到所需要的方法时,尝试打印this.$refs.selectSuggest,尝试没有被列出的方法。
// 图标清除输入
    clearSearch () {
      if (this.searchFlag) {
        this.searchFlag = false
        //可在这请求异步树,回到非搜索树状态
      }
    },

2. 同步树

<el-tree
              ref="searchTree"
              :data="searchData"
              show-checkbox      // 设置多选框
              :props="defaultProps"      
              :node-key="nodeKey"      // 节点key值,值必须唯一
              :parent-key="parentKey"      // 父节点key值,值必须唯一
              :check-strictly='checkStrictly'      // 设置父子不关联
              :default-checked-keys="defaultCheckedKeys"      // 设置默认选中
              simple-data      
              default-expand-all      // 默认展开全部
              @check="handleSearchCheckChange"      //点击复选框触发
              :render-content="highlightRender"      // 搜索框搜索后高亮渲染
            />

2.1. data: 将要显示得数据

数据格式1:

data: [
          {
            label: '一级 1',
            children: [
              { label: '二级 1-1', children: [{ label: '三级 1-1-1' }] }
            ]
          },
          {
            label: '一级 2',
            children: [
              { label: '二级 2-1', children: [{ label: '三级 2-1-1' }] },
              { label: '二级 2-2', children: [{ label: '三级 2-2-1' }] }
            ]
          },
          {
            label: '一级 3',
            children: [
              { label: '二级 3-1', children: [{ label: '三级 3-1-1' }] },
              { label: '二级 3-2', children: [{ label: '三级 3-2-1' }] }
            ]
          }
        ]

数据格式2:

simpleData: [
          { id: 1, label: '一级 1' },
          { id: 4, pId: 1, label: '二级 1-1' },
          { id: 9, pId: 4, label: '三级 1-1-1' },
          { id: 10, pId: 4, label: '三级 1-1-2' },
          { id: 2, label: '一级 2' },
          { id: 5, pId: 2, label: '二级 2-1' },
          { id: 6, pId: 2, label: '二级 2-2' }
        ],

配合simple-data使用,simple-data可设置node-key=id;parent-key=pid,可将平级得数据结构处理成为数据格式1的数据格式。
node-key为节点唯一标识,值必须不相同,parent-key作为父节点标识,将数据处理到哪个节点底下。

2.2. defaultProps 默认属性

defaultProps: {
        children: 'children',
        label: 'name',
        // 根据页面关联数据定制icon,down、up均为class名称
        icon: (data, node) => {
          if (this.resPlatformFlag === '0') {
            return 'down'
          } else {
            return 'up'
          }
        },
        // 异步树使用
        isLeaf: function (data) {
          return !data.hasSubNode
        }
      },
// 更换自己的icon时注意,需要将样式写在icon内部,才能生效
.el-tree-node__icon{
      margin: 4px 0;
      line-height: 1 !important;
      &.up{
        display: inline-block;
        width: 18px;
        height: 16px;
        vertical-align: middle;
        background:url('../../assets/images/treeIcons/gb-org-cascade.png') no-repeat left center;
      }
      &.down{
        display: inline-block;
        width: 18px;
        height: 16px;
        vertical-align: middle;
        background:url('../../assets/images/treeIcons/control-unit.png') no-repeat left center;
      }
    }

:render-content="highlightRender" 和搜索框配合使用将搜索关键字高亮

// 关键字高亮
    highlightRender (h, { node }) {
      const name = node.label
      // filterText为搜索框绑定的值
      if (this.filterText) {
        // 支持大小写模糊搜索
        // specialCharts:特殊字符集合,这些字符不能直接塞进正则里,需要先转译
        const specialCharts = [
          '(', ')', "'", '\\', '$',
          '*', '+', '[', ']', '?',
          '^', '{', '}', '|', '.'
        ]
        let wordStr = ''
        for (let i = 0, len = this.filterText.length; i < len; i++) {
          if (specialCharts.includes(this.filterText[i])) {
            wordStr += '\\' + this.filterText[i]
          } else {
            wordStr += this.filterText[i]
          }
        }
        const wordReg = new RegExp(wordStr, 'ig')
        const keyWordArr = name.match(wordReg)
        const vNodeArr = name.split(wordReg).reduce((all, item, index, arr) => {
          item && all.push(h('span', {}, item))
          if (index !== arr.length - 1) {
            all.push(
              h(
                'span',
                {
                  class: 'el-tree-node_highlight'
                },
                keyWordArr.shift()
              )
            )
          }
          return all
        }, [])
        // 无需自定义样式使用
	// return h('span', { class: 'el-tree-node__label' }, vNodeArr)
        // 使用h函数自定义样式,需要将class写入到el-tree-node__label下,否则不生效
        return h(
          'span',
          {
            style: 'vertical-align: middle;'
          },
          [
            h('span',
		{
		      class: 'el-tree-node__label'
		},
	      vNodeArr),
            node.data.pushStatus
              ? h('span',
		{
		      class: 'pushFlag'
		},
		'已共享')
              : ''
          ]
        )
      } else {
        return h('span', { class: 'el-tree-node__label' }, name)
      }
    }