客户体验提升的一小步(select组件支持拼音搜索)

324 阅读1分钟

问题

我们在项目经常会用到element的select选择组件,这个组件本身是支持本地搜索的,只需要配置属性filterable即可。但是存在的问题是select组件本身的本地搜索只支持字符匹配,也就是搜索的关键字需要出现在选项中才可以匹配上,但是如果选项是中文的那么我们需要确切的输入对应的中文才可以匹配,不是很方便。所以现在想要只输入对应的拼音即可匹配中文。



解决方案

思路:

1.首先我们需要一个全局的解决方案,而不是每个select组件实例自行写逻辑

2.可以通过动态修改select组件的默认逻辑达到上述的需求

3.具体技术可以通过反射(Reflect),搭配操作原生dom解决



代码实现:

这里用到pinyin.js用于将中文转换为拼音字母

function setSelectQueryForPinyin() {
  function pinyinMatch(label, query) {
    const pinyinStr = pinyin(label, {
      style: "normal",
    }).join("");
    const condition1 =
      pinyinStr.toLowerCase().indexOf(query.toLowerCase()) >= 0;
    const condition2 = label.toLowerCase().indexOf(query.toLowerCase()) >= 0;
    return condition1 || condition2;
  }

  const vue = require("vue");
  const isVue2 = vue.version.startsWith("2");

  try {
    if (isVue2) {
      const element = require("element-ui");
      const Select = element.Select;
      const oldUpdated = Select.updated;

      Select.updated = function () {
        const oldOptions = this.options;
        if (!this.filterMethod) {
          this.filterMethod = (query) => {
            this.options = oldOptions.filter((item) => {
              return pinyinMatch(item.currentLabel, query);
            });
            oldOptions.forEach((item) => {
              const condition = pinyinMatch(item.currentLabel, query);
              item.$el.style = `display: ${condition ? "block" : "none"}`;
            });
          };
        }
        oldUpdated && Reflect.apply(oldUpdated, this);
      };
    } else {
      const element = require("element-plus");
      const ElSelect = element.ElSelect;
      const oldUpdated = ElSelect.updated;
      ElSelect.updated = function () {
        setTimeout(() => {
          const firstOption = this.options.get(0);
          if (this.filterable && !this.filterMethod && firstOption) {
            Reflect.defineProperty(firstOption.select.queryChange, "query", {
              set: (query) => {
                this.options.forEach((item) => {
                  const condition = pinyinMatch(item.currentLabel, query);
                  item.$el.style = `display: ${condition ? "block" : "none"}`;
                });
                const ulElement = firstOption.$el.parentElement;
                const liArr = [...ulElement.querySelectorAll("li")];
                const isEmpty = !liArr.filter((li) => {
                  const liStyle = getComputedStyle(li);
                  return liStyle.display !== "none";
                }).length;

                const wrap =
                  firstOption.$el.parentElement.parentElement.parentElement;

                if (isEmpty) {
                  const emptyElement = wrap.querySelector("p");
                  if (!emptyElement) {
                    const tempDiv = document.createElement("div");
                    tempDiv.innerHTML =
                      '<p class="el-select-dropdown__empty">无匹配数据</p>';
                    const emptyElement = tempDiv.querySelector("p");
                    wrap.appendChild(emptyElement);
                  }
                } else {
                  const emptyElement = wrap.querySelector("p");
                  emptyElement && wrap.removeChild(emptyElement);
                }
              },
            });
          }
        }, 0);
        oldUpdated && Reflect.apply(oldUpdated, this);
      };
    }
  } catch (e) {}
}