Vue:使输入的匹配部分加粗,包括特殊连字符

Vue: Make matching part of input bold, including special hyphens

我用 search/filter 系统在 Vue 中制作了一个简单的 select 组件。根据用户输入,我展示了一些比利时城市建议。

工作示例:https://codesandbox.io/s/wandering-lake-lecok?file=/src/components/Select.vue(有时在 Codesandbox 中会出现错误消息。在浏览器中刷新构建,它应该可以工作)

我想在 UX 上更进一步,将用户输入的匹配部分显示为粗体并加下划线。因此我有一个有效的 makeBold 函数。通过将建议字符串拆分为多个部分,我可以添加粗体和下划线标记以及 return 建议。

computed: {
    results() {
        return this.options.filter((option) =>
        option.display_name
            .replaceAll("-'", "")
            .toLowerCase()
            .includes(this.searchInput.replaceAll("-'", "").toLowerCase())
        );
    },
},
methods: {
    makeBold(str, query) {
        const n = str.toUpperCase();
        const q = query.toUpperCase();
        const x = n.indexOf(q);

        if (!q || x === -1) {
            return str;
        }

        const l = q.length;

        return (
            str.substr(0, x) + "<b><u>" + str.substr(x, l) + "</u></b>" + str.substr(x + l)
        );
    },
}

一个问题,比利时的很多城市都使用破折号 and/or 撇号。在建议功能中,我删除了这些字符,这样用户就不需要输入它们了。但是在 makeBold 函数中,我想让这个字符加粗并加下划线。

例如:

当输入为 'sint j'、'sintj' 或 'Sint-j' 时,我希望建议看起来像 'Sint-Jans-Molenbeek ' 和 'Sint-Job in't Goor'

有没有人可以告诉我如何实现这个目标?

您可以创建一个函数来删除查询和城市字符串中的所有 spaces-。如果城市包含查询,则在最后一个字母上拆分查询字符串并获取该字母在查询中的出现次数。计算切片的长度和 return 原始城市字符串的匹配部分。

const findMatch = (q, c) => {
  const query = q.toLowerCase().replace(/[\s-]/g, "");
  const city = c.toLowerCase().replace(/[\s-]/g, "");
  
  if (city.includes(query)) {
    const last = query.charAt(query.length - 1); // last letter
    const occ = query.split(last).length - 1; // get occurences
    // calculate slice length
    const len = c.toLowerCase().split(last, occ).join(" ").length + 1;
    return c.slice(0, len);
  }

  return "No matching city found."
}

const city = "Sint-Jan Test";
console.log(findMatch("sint j", city));
console.log(findMatch("sintj", city));
console.log(findMatch("Sint Jan t", city));
console.log(findMatch("sint-j", city));
console.log(findMatch("Sint-J", city));
console.log(findMatch("SintJan te", city));

一种方法是将搜索字符串转换为 RegExp 对象并使用 replace(regexp, replacerFunction) 重载字符串来实现此目的。
例如搜索字符串是 "sintg"

new RegExp(this.searchInput.split("").join("-?"), "i");

变成/s-?i-?n-?t-?g/gi
-?表示可选的-字符和
"i"最后是RegExp不区分大小写标志

应用于codesandbox代码你得到这个

computed: {
  results() {
    const regex = new RegExp(this.searchInput.split("").join("-?"), "i");
    return this.options.filter((option) => option.display_name.match(regex));
  },
},
methods: {
  makeBold(str, query) {
    const regex = new RegExp(query.split("").join("-?"), "i");
    return str.replace(regex, (match) => "<b><u>" + match + "</u></b>");
  },
},

给出这个结果

但是有一个警告:如果用户在搜索框中输入 RegExp 特殊符号,将会抛出错误
为避免这种情况,初始搜索输入文本需要应用 RegExp 转义。
如:

new RegExp(escapeRegExp(this.searchInput).split("").join("-?"), "i");

但是没有原生的escapeRegExp方法。
您可以在 Escape string for use in Javascript regex
中找到一个 lodash 库中还有一个 escapeRegExp 函数,如果它已经在您的依赖项列表中(避免您添加另一个函数)

我建议使用掩码来保存城市名称结构,找到城市名称中子字符串的开始和结束索引后,从掩码中恢复原始字符串,在开始和结束处插入适当的标签使用替换函数的索引。这样你就不会担心任何其他非单词字符或其他意外的用户输入。

这里是 makeBold 函数:

makeBold(str, query) {
      // mask all word characters in city name
      const city_mask = str.replace(/\w/g, "#");
      // strip city and query string from any non-word character
      let query_stripped = query.toLowerCase().replace(/\W/g, "");
      let string_stripped = str.replace(/\W/g, "");
      // find the index of querystring in city name
      let index = string_stripped.toLowerCase().indexOf(query_stripped);

      if (index > -1 && query_stripped.length) {
        // find the end position of substring in stripped city name
        let end_index = index + query_stripped.length - 1;

        // replacer function for each masked character.
        // it will add to the start and end character of substring the corresponding tags,
        // replacing all masked characters with the original one.
        function replacer(i) {
          let repl = string_stripped[i];
          if (i === index) {
            repl = "<b><u>" + repl;
          }
          if (i === end_index) {
            repl = repl + "</u></b>";
          }
          return repl;
        }
        let i = -1;
        // restore masked string
        return city_mask.replace(/#/g, (_) => {
          i++;
          return replacer(i);
        });
      }

      return str;
    }

here 是工作沙箱。我对您的计算 results 进行了一些更改,以去除所有非单词字符。