在一个完美的世界中,用户无需查看第一个搜索结果就能找到他们想要的内容。但在实践中,通常需要创建某种分页界面来浏览冗长的结果列表。 在本指南中,我们讨论 Meilisearch 支持的两种不同分页方法:一种使用 offsetlimit,另一种使用 hitsPerPagepage

选择正确的分页 UI

有许多 UI 模式可以帮助用户浏览搜索结果。Meilisearch 中一种常见且高效的解决方案是使用 offsetlimit 创建围绕“上一页”和“下一页”按钮的界面。 其他解决方案,例如创建页面选择器,允许用户跳转到任何搜索结果页面,它们利用 hitsPerPagepage 获取匹配文档的详尽总数。这些解决方案的效率往往较低,并可能导致性能下降。 无论您选择哪种 UI 模式,Meilisearch 为任何给定查询返回的搜索结果最大数量都是有限的。您可以使用maxTotalHits 索引设置来配置此限制,但请注意,更高的限制将对搜索性能产生负面影响。
maxTotalHits 设置为高于默认值的值将对搜索性能产生负面影响。将 maxTotalHits 设置为高于 20000 的值可能会导致查询需要数秒才能完成。

“上一页”和“下一页”按钮

使用“上一页”和“下一页”按钮进行分页意味着用户可以轻松浏览结果,但无法跳转到任意结果页面。这是 Meilisearch 在创建分页界面时推荐的解决方案。 虽然这种方法不如全面的页面选择器精确,但它不需要知道确切的搜索结果数量。由于计算与查询匹配的详尽文档数量是一个资源密集型过程,因此此类界面可能会提供更好的性能。

实施

要在网站或应用程序中实现此界面,我们使用 limitoffset 搜索参数进行查询。响应正文将包含一个 estimatedTotalHits 字段,其中包含搜索结果的部分计数。这是 Meilisearch 的默认行为。
{
  "hits": [

  ],
  "query": "",
  "processingTimeMs": 15,
  "limit": 10,
  "offset": 0,
  "estimatedTotalHits": 471
}

limitoffset

“上一页”和“下一页”按钮可以使用limitoffset搜索参数来实现。 limit 设置页面大小。如果将 limit 设置为 10,Meilisearch 的响应将最多包含 10 个搜索结果。offset 跳过一定数量的搜索结果。如果将 offset 设置为 20,Meilisearch 的响应将跳过前 20 个搜索结果。 例如,您可以使用 Meilisearch 的 JavaScript SDK 获取电影数据库中的前十部电影:
const results = await index.search("tarkovsky", { limit: 10, offset: 0 });
您可以将这两个参数一起使用来创建搜索页面。

搜索页面和计算 offset

如果您将 limit 设置为 20,将 offset 设置为 0,则会得到前二十个搜索结果。我们可以将其称为第一页。
const results = await index.search("tarkovsky", { limit: 20, offset: 0 });
同样,如果您将 limit 设置为 20,将 offset 设置为 40,则会跳过前 40 个搜索结果,并获得从 40 到 59 的文档。我们可以将其称为第三个结果页面。
const results = await index.search("tarkovsky", { limit: 20, offset: 40 });
您可以使用此公式计算页面的偏移值:offset = limit * (目标页码 - 1)。在前面的示例中,计算将如下所示:offset = 20 * (3 - 1)。这会给我们带来 40 的结果:offset = 20 * 2 = 40 一旦查询返回的 hits 少于您配置的 limit,您就已到达最后一个结果页面。

跟踪当前页码

即使这种 UI 模式不允许用户跳转到特定页面,跟踪当前页码仍然很有用。 以下 JavaScript 代码片段将页码存储在 HTML 元素 .pagination 中,并在用户每次移动到不同的搜索结果页面时更新它:
function updatePageNumber(elem) {
  const directionBtn = elem.id
  // Get the page number stored in the pagination element
  let pageNumber = parseInt(document.querySelector('.pagination').dataset.pageNumber)

  // Update page number
  if (directionBtn === 'previous_button') {
    pageNumber = pageNumber - 1
  } else if (directionBtn === 'next_button') {
    pageNumber = pageNumber + 1
  }

  // Store new page number in the pagination element
  document.querySelector('.pagination').dataset.pageNumber = pageNumber
}

// Add data to our HTML element stating the user is on the first page
document.querySelector('.pagination').dataset.pageNumber = 0
// Each time a user clicks on the previous or next buttons, update the page number
document.querySelector('#previous_button').onclick = function () { updatePageNumber(this) }
document.querySelector('#next_button').onclick = function () { updatePageNumber(this) }

禁用首页和末页的导航按钮

当用户无法移动到“下一页”或“上一页”时,禁用导航按钮通常很有帮助。 当您的 offset0 时,“上一页”按钮应禁用,因为这表示您的用户在第一个结果页面。 要知道何时禁用“下一页”按钮,我们建议将查询的 limit 设置为您希望每页显示的加一的结果数。该额外的 hit 不应显示给用户。其目的是指示下一页至少还有一份文档要显示。 以下 JavaScript 代码片段在用户每次导航到另一个搜索结果页面时运行检查是否应禁用按钮:
function updatePageNumber() {
  const pageNumber = parseInt(document.querySelector('.pagination').dataset.pageNumber)

  const offset = pageNumber * 20
  const results = await index.search('x', { limit: 21, offset })

  // If offset equals 0, we're on the first results page
  if (offset === 0 ) {
    document.querySelector('#previous_button').disabled = true;
  }

  // If offset is bigger than 0, we're not on the first results page
  if (offset > 0 ) {
    document.querySelector('#previous_button').disabled = false;
  }

  // If Meilisearch returns 20 items or fewer,
  // we are on the last page
  if (results.hits.length < 21 ) {
    document.querySelector('#next_button').disabled = true;
  }

  // If Meilisearch returns exactly 21 results
  // and our page can only show 20 items at a time,
  // we have at least one more page with one result in it
  if (results.hits.length === 21 ) {
    document.querySelector('#next_button').disabled = false;
  }
}

document.querySelector('#previous_button').onclick = function () { updatePageNumber(this) }
document.querySelector('#next_button').onclick = function () { updatePageNumber(this) }

带编号的页面选择器

这种分页类型由一个带编号的页面列表以及“下一页”和“上一页”按钮组成。这是一种常见的 UI 模式,为用户在导航结果时提供了显着程度的精确性。 计算查询的搜索结果总数是一个资源密集型过程。带编号的页面选择器可能会导致性能问题,特别是如果您将 maxTotalHits 增加到其默认值以上。

实施

默认情况下,Meilisearch 查询仅返回 estimatedTotalHits。此值可能会随着用户浏览搜索结果而变化,不应用于创建计算搜索结果页数。 当您的查询包含 hitsPerPagepage 或两者兼而有之的搜索参数时,Meilisearch 会返回 totalHitstotalPages,而不是 estimatedTotalHitstotalHits 包含该查询的详尽结果数,totalPages 包含相同查询的详尽搜索结果页数:
{
  "hits": [

  ],
  "query": "",
  "processingTimeMs": 35,
  "hitsPerPage": 20,
  "page": 1,
  "totalPages": 4,
  "totalHits": 100
}

使用 hitsPerPagepage 的搜索页面

hitsPerPage 定义了页面上搜索结果的最大数量。 由于 hitsPerPage 定义了页面上的结果数量,因此它直接影响查询的总页数。例如,如果查询返回 100 个结果,将 hitsPerPage 设置为 25 意味着您将有四个搜索结果页面。相反,将 hitsPerPage 设置为 50 意味着您将只有两个搜索结果页面。 以下示例返回查询的前 25 个搜索结果:
const results = await index.search(
  "tarkovsky",
  {
    hitsPerPage: 25,
  }
);
要浏览搜索结果页面,请使用 page 搜索参数。如果您将 hitsPerPage 设置为 25,并且 totalPages4,则 page 1 包含文档 1 到 25。将 page 设置为 2 则返回文档 26 到 50。
const results = await index.search(
  "tarkovsky",
  {
    hitsPerPage: 25,
    page: 2
  }
);
hitsPerPagepage 优先于 offsetlimit。如果查询包含 hitsPerPagepage,则传递给 offsetlimit 的任何值都将被忽略。

创建带编号的页面列表

响应中包含的 totalPages 字段包含基于查询 hitsPerPage 的搜索结果页面的详尽计数。使用它来创建带编号的页面列表。 为了便于使用,带有 hitsPerPagepage 的查询始终返回当前页码。这意味着您无需手动跟踪您正在显示的页面。 在以下示例中,我们动态创建页面按钮列表并突出显示当前页面:
const pageNavigation = document.querySelector('#page-navigation');
const listContainer = pageNavigation.querySelector('#page-list');
const results = await index.search(
  "tarkovsky",
  {
    hitsPerPage: 25,
    page: 1
  }
);

const totalPages = results.totalPages;
const currentPage = results.page;

for (let i = 0; i < totalPages; i += 1) {
  const listItem = document.createElement('li');
  const pageButton = document.createElement('button');

  pageButton.innerHTML = i;

  if (currentPage === i) {
    listItem.classList.add("current-page");
  }

  listItem.append(pageButton);
  listContainer.append(listItem);
}

添加导航按钮

您的用户可能对当前搜索结果页面之前或之后的页面更感兴趣。因此,在页面列表中添加“下一页”和“上一页”按钮通常很有帮助。 在此示例中,我们将这些按钮作为页面导航组件的第一个和最后一个元素添加:
const pageNavigation = document.querySelector('#page-navigation');

const buttonNext = document.createElement('button');
buttonNext.innerHTML = 'Next';

const buttonPrevious = document.createElement('button');
buttonPrevious.innerHTML = 'Previous';

pageNavigation.prepend(buttonPrevious);
pageNavigation.append(buttonNext);
我们还可以在搜索结果的第一页或最后一页根据需要禁用它们。
buttonNext.disabled = results.page === results.totalPages;
buttonPrevious.disabled = results.page === 1;
© . This site is unofficial and not affiliated with Meilisearch.