实现无需提交的动态搜索 修复Node.js+EJS搜索功能问题
实时搜索结果更新问题解决方案
问题概述
需要实现搜索输入时无需回车或点击按钮,动态更新EJS页面的搜索结果,但当前实现存在两个核心问题:
- 仅能正常显示前几次输入结果,之后必须重启服务器
search-resultsdiv中的结果无法实时更新
后端修改(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 }
修改要点
- 全局缓存用户数据和MiniSearch实例,避免重复查询数据库和创建实例
- 严格释放数据库连接,防止连接池耗尽
- 拆分页面渲染与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>
修改要点
- 移除表单自动提交逻辑,避免打断AJAX请求
- 添加防抖函数,降低服务器请求频率
- 使用
encodeURIComponent处理URL参数,避免特殊字符导致的请求错误 - 完善错误处理,请求失败时显示友好提示
- 提前获取结果容器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
相关产品推荐
相关产品推荐

