9

使用Fuse.js我正在尝试在 JS 对象中进行“多个单词”搜索,以获取包含所查找的每个单词的记录。

我的数据结构如下(来自fuse.js):

[{
    title: "The Lost Symbol",
    author: {
      firstName: "Dan",
      lastName: "Brown"
    }
 }, ...]

我的问题是我的设置适用于单字搜索(Brown例如),但不适用于更多(Dan BrownDan Brown Vinci)。

保险丝选项:

{
    shouldSort: true,
    matchAllTokens: true,
    findAllMatches: true,
    includeScore: true,
    threshold: 0,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [
        "title",
        "author.firstName",
        "author.lastName"
    ]
}

new Vue({
    el: "#app",
    data: {
        Fuse: null,
        searchText: '',
        result : [],
        fuseOptions: {
            shouldSort: true,
            matchAllTokens: true,
            findAllMatches: true,
            includeScore: true,
            threshold: 0,
            location: 0,
            distance: 100,
            maxPatternLength: 32,
            minMatchCharLength: 1,
            keys: [
                "title",
                "author.firstName",
                "author.lastName"
            ]
        },
        list: [{
                title: "Old Man's War",
                author: {
                    firstName: "John",
                    lastName: "Scalzi"
                }
            },
            {
                title: "The Lock Artist",
                author: {
                    firstName: "Steve",
                    lastName: "Hamilton"
                }
            },
            {
                title: "HTML5",
                author: {
                    firstName: "Remy",
                    lastName: "Sharp"
                }
            },
            {
                title: "Right Ho Jeeves",
                author: {
                    firstName: "P.D",
                    lastName: "Woodhouse"
                }
            },
            {
                title: "The Code of the Wooster",
                author: {
                    firstName: "P.D",
                    lastName: "Woodhouse"
                }
            },
            {
                title: "Thank You Jeeves",
                author: {
                    firstName: "P.D",
                    lastName: "Woodhouse"
                }
            },
            {
                title: "The DaVinci Code",
                author: {
                    firstName: "Dan",
                    lastName: "Brown"
                }
            },
            {
                title: "Angels & Demons",
                author: {
                    firstName: "Dan",
                    lastName: "Brown"
                }
            },
            {
                title: "The Silmarillion",
                author: {
                    firstName: "J.R.R",
                    lastName: "Tolkien"
                }
            },
            {
                title: "Syrup",
                author: {
                    firstName: "Max",
                    lastName: "Barry"
                }
            },
            {
                title: "The Lost Symbol",
                author: {
                    firstName: "Dan",
                    lastName: "Brown"
                }
            },
            {
                title: "The Book of Lies",
                author: {
                    firstName: "Brad",
                    lastName: "Meltzer"
                }
            },
            {
                title: "Lamb",
                author: {
                    firstName: "Christopher",
                    lastName: "Moore"
                }
            },
            {
                title: "Fool",
                author: {
                    firstName: "Christopher",
                    lastName: "Moore"
                }
            },
            {
                title: "Incompetence",
                author: {
                    firstName: "Rob",
                    lastName: "Grant"
                }
            },
            {
                title: "Fat",
                author: {
                    firstName: "Rob",
                    lastName: "Grant"
                }
            },
            {
                title: "Colony",
                author: {
                    firstName: "Rob",
                    lastName: "Grant"
                }
            },
            {
                title: "Backwards, Red Dwarf",
                author: {
                    firstName: "Rob",
                    lastName: "Grant"
                }
            },
            {
                title: "The Grand Design",
                author: {
                    firstName: "Stephen",
                    lastName: "Hawking"
                }
            },
            {
                title: "The Book of Samson",
                author: {
                    firstName: "David",
                    lastName: "Maine"
                }
            },
            {
                title: "The Preservationist",
                author: {
                    firstName: "David",
                    lastName: "Maine"
                }
            },
            {
                title: "Fallen",
                author: {
                    firstName: "David",
                    lastName: "Maine"
                }
            },
            {
                title: "Monster 1959",
                author: {
                    firstName: "David",
                    lastName: "Maine"
                }
            }
        ]

    },
    methods: {
        fuseSearch: function() {
            let self = this;
            
            this.result = this.Fuse.search(self.searchText)
        }
    },

    mounted() {
    		let self = this
        this.Fuse = new window.Fuse(self.list, self.fuseOptions);
        

    }
})
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

table {
  width: 100%;
  margin-top:20px
}

table th{
  font-weight:bold
}
table td{
  padding-top:5px
}

input{
  height:30px;
  width:200px;
  font-size:14px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.1/fuse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<div id="app">
  <input type="text" v-model="searchText" @keyup="fuseSearch()" placeholder="search for text">
  
  
  <div v-if="result.length == 0" style="margin-top:10px">No matching results, here is the full list</div>
  <div v-else style="margin-top:10px">{{result.length}} records found</div>
  
  
  
  <table>
    <tr>
      <th>Title</th>
      <th>FistName</th>
      <th>LastName</th>
      <th>Score</th>
    </tr>
    
    <tr v-if="result.length >0" v-for="data in result" >
      <td>{{data.item.title}}</td>
      <td>{{data.item.author.firstName}}</td>
      <td>{{data.item.author.lastName}}</td>
      <td>{{Math.round(data.score*100,2)/100}}</td>
    </tr>
    
    <tr v-if="result.length == 0" v-for="data in list">
    
      <td>{{data.title}}</td>
      <td>{{data.author.firstName}}</td>
      <td>{{data.author.lastName}}</td>
      <td></td>
    </tr>
  </table>

</div>

4

2 回答 2

3

不幸的是,fuse.js 并没有查看所有字段,而是查看一个匹配的字段。我通过将所有字段放入一个带有字符串数组的字段来解决了这个问题。

例子:

[{
    title: "The Lost Symbol",
    author: {
      firstName: "Dan",
      lastName: "Brown"
    },
    keywords: ["The Lost Symbol", "Dan", "Brown"] //values of title, firstname & lastname
 }, ...]

并且只需指定关键字Fuse 选项的keys字段

{
    shouldSort: true,
    matchAllTokens: true,
    findAllMatches: true,
    includeScore: true,
    threshold: 0,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: ["keywords"]  //just put keywords alone
}

这对我有用。希望它也适合你。

于 2021-01-10T16:37:34.540 回答
1

我们也有类似的需求,最终解决如下:

(注意:我最初在https://github.com/krisk/Fuse/issues/235#issuecomment-850269634上分享了这个)


对于任何通过谷歌搜索或其他方式最终到达这里的人,我们最终在https://github.com/sparkletown/sparkle/pull/1460中采用了不同的方法(感谢@yarikoptic 的 > 令人敬畏的工作调试、探索和改进这个)

我们基本上使用正tokeniseStringWithQuotesBySpaces则表达式""

/**
 * Split the provided string by spaces (ignoring spaces within "quoted text") into an array of tokens.
 *
 * @param string
 *
 * @see https://stackoverflow.com/a/16261693/1265472
 *
 * @debt Depending on the outcome of https://github.com/github/codeql/issues/5964 we may end up needing to change
 *   this regex for performance reasons.
 */
export const tokeniseStringWithQuotesBySpaces = (string: string): string[] =>
  string.match(/("[^"]*?"|[^"\s]+)+(?=\s*|\s*$)/g) ?? [];

注意:请检查https://github.com/github/codeql/issues/5964,因为正则表达式可能存在 ReDoS 漏洞,但也可能只是 CodeQL 扫描仪中的误报)

使用我们的标准保险丝配置:

      new Fuse(filteredPosterVenues, {
        keys: [
          "name",
          "poster.title",
          "poster.authorName",
          "poster.categories",
        ],
        threshold: 0.2, // 0.1 seems to be exact, default 0.6: brings too distant if anyhow related hits
        ignoreLocation: true, // default False: True - to search ignoring location of the words.
        findAllMatches: true,
      }),

但是然后使用我们的tokeniseStringWithQuotesBySpaces标记器 + 自定义 Fuse 查询($and用于连接我们的每个标记,然后$or用于不同的字段)进行搜索:

const tokenisedSearchQuery = tokeniseStringWithQuotesBySpaces(
  normalizedSearchQuery
);

if (tokenisedSearchQuery.length === 0) return filteredPosterVenues;

return fuseVenues
  .search({
    $and: tokenisedSearchQuery.map((searchToken: string) => {
      const orFields: Fuse.Expression[] = [
        { name: searchToken },
        { "poster.title": searchToken },
        { "poster.authorName": searchToken },
        { "poster.categories": searchToken },
      ];

      return {
        $or: orFields,
      };
    }),
  })
  .map((fuseResult) => fuseResult.item);

从我今天的测试来看,这似乎非常有效地满足了我们的需求。>

于 2021-05-28T09:12:30.597 回答