You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现无需提交的动态搜索 修复Node.js+EJS搜索功能问题

实时搜索结果更新问题解决方案

问题概述

需要实现搜索输入时无需回车或点击按钮,动态更新EJS页面的搜索结果,但当前实现存在两个核心问题:

  • 仅能正常显示前几次输入结果,之后必须重启服务器
  • search-results div中的结果无法实时更新

后端修改(user.controllers.js)

问题根源

每次请求重复查询数据库且未释放连接,导致连接池耗尽;同时重复创建MiniSearch实例,造成资源浪费和状态异常。

修改后的完整代码:

const {pool} = require('../config/database.config');
const MiniSearch = require('minisearch');
const userModels = require('../models/user.models');

// 全局缓存用户数据与MiniSearch实例,避免重复操作
let cachedUsers = [];
let miniSearchInstance = null;

// 初始化/刷新缓存
const initCache = async () => {
  const client = await pool.connect();
  try {
    const result = await client.query(`select * from "users"`);
    cachedUsers = result.rows;
    
    miniSearchInstance = new MiniSearch({
      fields: ['id','name', 'description', 'dept', 'year', 'fromDate', 'toDate'],
      storeFields: ['id','name', 'description', 'dept', 'year', 'fromDate', 'toDate'],
    });
    miniSearchInstance.addAll(cachedUsers);
  } catch (err) {
    console.error(err);
  } finally {
    client.release(); // 强制释放连接回池
  }
};

// 启动时初始化缓存
initCache();

// 页面渲染接口
const getHome = async(req, res) => {
  const searchedValue = req.query.searchedValue || '';
  const deptFilter = req.query.deptFilter || '';
  const yearFilter = req.query.yearFilter || '';
  const fromDate = req.query.fromDate || '';
  const toDate = req.query.toDate || ''; 

  // 过滤逻辑
  const filterCriteria = (result) => {
    if (deptFilter && result.dept !== deptFilter) return false;
    if (yearFilter && result.year !== yearFilter) return false;
    
    if (fromDate) {
      const formattedDate = userModels.formatDate(result.fromDate);
      if (fromDate > formattedDate) return false;
    }
    
    if (toDate) {
      const formattedDate = userModels.formatDate(result.toDate);
      if (toDate < formattedDate) return false;
    }
    
    return true;
  };

  // 获取结果
  let results = searchedValue 
    ? miniSearchInstance.search(searchedValue, { prefix:true, fuzzy: 0.4, filter: filterCriteria })
    : cachedUsers.filter(filterCriteria);

  const search_results = results.map(result => ({
    id: result.id,
    name: result.name,
    description: result.description,
  }));

  res.render('../Views/user.ejs', { search_results });
}

// AJAX实时搜索接口
const getSearchResult = async(req, res) => {
  const searchedValue = req.query.searchedValue || '';
  const deptFilter = req.query.deptFilter || '';
  const yearFilter = req.query.yearFilter || '';
  const fromDate = req.query.fromDate || '';
  const toDate = req.query.toDate || ''; 

  const filterCriteria = (result) => {
    if (deptFilter && result.dept !== deptFilter) return false;
    if (yearFilter && result.year !== yearFilter) return false;
    
    if (fromDate) {
      const formattedDate = userModels.formatDate(result.fromDate);
      if (fromDate > formattedDate) return false;
    }
    
    if (toDate) {
      const formattedDate = userModels.formatDate(result.toDate);
      if (toDate < formattedDate) return false;
    }
    
    return true;
  };

  let results = searchedValue 
    ? miniSearchInstance.search(searchedValue, { prefix:true, fuzzy: 0.4, filter: filterCriteria })
    : cachedUsers.filter(filterCriteria);

  const search_results = results.map(result => ({
    id: result.id,
    name: result.name,
    description: result.description,
  }));

  res.json({ search_results });
}

module.exports = {
  getHome,
  getSearchResult
}

修改要点

  1. 全局缓存用户数据和MiniSearch实例,避免重复查询数据库和创建实例
  2. 严格释放数据库连接,防止连接池耗尽
  3. 拆分页面渲染与AJAX接口,分别处理首次加载和实时搜索请求

前端修改(user.ejs)

问题根源

表单自动提交与AJAX请求冲突,且未处理参数编码、请求防抖和错误情况。

修改后的完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User</title>
</head>
<body>
    <!-- 移除表单自动提交逻辑,避免与AJAX冲突 -->
    <div>
        <h1>SEARCH HERE</h1>
        <input type="search" name="searchedValue" id="searchedValue" placeholder="输入关键词搜索">
        <br>
        <label for="filter">Select a filter:</label>
        <select id="deptFilter" name="deptFilter">
          <option value="">Department</option>
          <option value="CSE">CSE</option>
          <option value="EEE">EEE</option>
          <option value="ME">ME</option>
        </select>
        <select id="yearFilter" name="yearFilter">
          <option value="">Year</option>
          <option value="first">First Year</option>
          <option value="second">Second Year</option>
          <option value="third">Third Year</option>
          <option value="fourth">Fourth Year</option>
        </select>
        <br>
        <label for="fromDate"> From </label>
        <input type="date" id="fromDate" name="fromDate">
        <label for="toDate"> To </label>
        <input type="date" id="toDate" name="toDate">
    </div> 
      
    <script>
      const searchedValueInput = document.querySelector('#searchedValue');
      const deptFilterInput = document.querySelector('#deptFilter');
      const yearFilterInput = document.querySelector('#yearFilter');
      const fromDateInput = document.querySelector('#fromDate');
      const toDateInput = document.querySelector('#toDate');
      const searchResultsContainer = document.querySelector('#search-results');

      // 防抖函数:减少频繁输入时的请求次数
      function debounce(func, delay) {
        let timeoutId;
        return function() {
          clearTimeout(timeoutId);
          timeoutId = setTimeout(() => func.apply(this, arguments), delay);
        };
      }

      const debouncedUpdate = debounce(updateSearchResults, 300);

      // 绑定事件监听
      searchedValueInput.addEventListener('input', debouncedUpdate);
      deptFilterInput.addEventListener('change', debouncedUpdate);
      yearFilterInput.addEventListener('change', debouncedUpdate);
      fromDateInput.addEventListener('change', debouncedUpdate);
      toDateInput.addEventListener('change', debouncedUpdate);

      async function updateSearchResults() {
        const searchedValue = searchedValueInput.value.trim();
        const deptFilter = deptFilterInput.value;
        const yearFilter = yearFilterInput.value;
        const fromDate = fromDateInput.value;
        const toDate = toDateInput.value;

        try {
          // 编码参数避免特殊字符错误
          const url = `/search?searchedValue=${encodeURIComponent(searchedValue)}&deptFilter=${encodeURIComponent(deptFilter)}&yearFilter=${encodeURIComponent(yearFilter)}&fromDate=${encodeURIComponent(fromDate)}&toDate=${encodeURIComponent(toDate)}`;
          const response = await fetch(url);
          
          if (!response.ok) throw new Error('请求失败');
          const data = await response.json();
          
          // 更新结果容器
          searchResultsContainer.innerHTML = '';
          if (data.search_results.length > 0) {
            data.search_results.forEach(result => {
              const resultElement = document.createElement('div');
              resultElement.innerHTML = `
                <h1>${result.id}</h1>
                <h2>${result.name}</h2>
                <h3>${result.description}</h3>
              `;
              searchResultsContainer.appendChild(resultElement);
            });
          } else {
            searchResultsContainer.innerHTML = '<h1>No results found</h1>';
          }
        } catch (err) {
          console.error(err);
          searchResultsContainer.innerHTML = '<h1>搜索出错,请稍后重试</h1>';
        }
      }
    </script>

    <div id="search-results">
      <% if(search_results.length > 0) { %>
        <% search_results.forEach((result) => { %>
          <div>
            <h1><%= result.id %></h1>
            <h2><%= result.name %></h2>
            <h3><%= result.description %></h3>
          </div>
        <% }) %>
      <% } else { %>
        <h1>No results found</h1>
      <% } %> 
    </div>
    
</body>
</html>

修改要点

  1. 移除表单自动提交逻辑,避免打断AJAX请求
  2. 添加防抖函数,降低服务器请求频率
  3. 使用encodeURIComponent处理URL参数,避免特殊字符导致的请求错误
  4. 完善错误处理,请求失败时显示友好提示
  5. 提前获取结果容器DOM元素,优化性能

路由配置补充

确保在路由文件中添加/search的GET路由:

const express = require('express');
const router = express.Router();
const userControllers = require('./path/to/user.controllers');

router.get('/', userControllers.getHome);
router.get('/search', userControllers.getSearchResult);

module.exports = router;

内容的提问来源于stack exchange,提问作者Ryuzaki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 20:37:04