document.addEventListener('DOMContentLoaded', () => { let fuse = null; let isLoading = false; // 新增:用于跟踪加载状态,防止重复请求 // 1. 动态注入所有需要的 CSS 样式 (代码无变化) const styles = ` #custom-search-fab { position: fixed; bottom: 25px; right: 25px; width: 56px; height: 56px; background-color: #2c3e50; border-radius: 50%; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); display: flex; justify-content: center; align-items: center; cursor: pointer; z-index: 9999; transition: transform 0.2s ease-in-out; } #custom-search-fab:hover { transform: scale(1.1); } #custom-search-fab svg { width: 24px; height: 24px; fill: #ecf0f1; } #custom-search-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 10000; display: none; justify-content: center; align-items: flex-start; padding-top: 10vh; } #custom-search-modal { width: 90%; max-width: 700px; background-color: #2c3e50; border-radius: 8px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; } .search-modal-header { padding: 16px; border-bottom: 1px solid #34495e; display: flex; align-items: center; } #custom-search-input { width: 100%; border: none; outline: none; font-size: 1.2em; padding: 8px; background-color: #34495e; color: #ecf0f1; } #custom-search-results { max-height: 60vh; overflow-y: auto; padding: 8px 0; } .search-result-item { display: block; padding: 12px 24px; text-decoration: none; color: #ecf0f1; border-bottom: 1px solid #34495e; } .search-result-item:hover { background-color: #34495e; } .result-title { font-weight: bold; font-size: 1.1em; color: #3498db; } .result-breadcrumbs { font-size: 0.9em; color: #bdc3c7; margin-top: 4px; } .result-snippet { font-size: 0.9em; color: #ecf0f1; margin-top: 6px; } .search-modal-footer { padding: 8px; text-align: right; font-size: 0.8em; color: #95a5a6; border-top: 1px solid #34495e; } `; const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = styles; document.head.appendChild(styleSheet); // 2. 动态创建 HTML 元素 (代码无变化) const fab = document.createElement('div'); fab.id = 'custom-search-fab'; fab.innerHTML = ''; document.body.appendChild(fab); const modalOverlay = document.createElement('div'); modalOverlay.id = 'custom-search-modal-overlay'; modalOverlay.innerHTML = `
输入内容以开始搜索
`; document.body.appendChild(modalOverlay); const searchInput = document.getElementById('custom-search-input'); const resultsContainer = document.getElementById('custom-search-results'); const modalDiv = document.getElementById('custom-search-modal'); // 3. 设置事件监听 // 点击悬浮按钮,显示模态框并按需加载数据 fab.addEventListener('click', () => { modalOverlay.style.display = 'flex'; searchInput.focus(); // **修改部分**: 只有在 fuse 未初始化且未在加载中时,才去加载数据 if (!fuse && !isLoading) { isLoading = true; // 显示加载提示 resultsContainer.innerHTML = '
正在加载搜索数据...
'; // 4. 加载数据并初始化 Fuse.js (逻辑移动到此处) fetch('static/search-data.json') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { const options = { keys: [ {name: 'mainTitle', weight: 2}, {name: 'headings', weight: 1.5}, {name: 'content', weight: 1}, {name: 'breadcrumbs', weight: 0.8} ], includeScore: true, includeMatches: true, minMatchCharLength: 2, threshold: 0.4, ignoreLocation: true, }; fuse = new Fuse(data, options); console.log('独立的 Fuse.js 搜索服务已在首次点击时初始化。'); // 初始化完成后,根据输入框当前内容决定显示什么 if(searchInput.value.length < 2) { resultsContainer.innerHTML = '
请输入至少2个字符
'; } else { // 如果用户在加载时已经输入了内容,则立即触发一次搜索 searchInput.dispatchEvent(new Event('input')); } }) .catch(error => { console.error('加载搜索数据失败:', error); // 显示错误提示 resultsContainer.innerHTML = '
加载搜索数据失败,请刷新页面重试。
'; }) .finally(() => { isLoading = false; // 无论成功或失败,都结束加载状态 }); } }); // 点击模态框背景,关闭模态框 (代码无变化) modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { modalOverlay.style.display = 'none'; } }); // 按下 Escape 键关闭模态框 (代码无变化) document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modalOverlay.style.display === 'flex') { modalOverlay.style.display = 'none'; } }); // 5. 实现搜索逻辑和结果渲染 (代码无变化) searchInput.addEventListener('input', (e) => { // 如果正在加载中,则不执行搜索 if (!fuse || isLoading) return; const query = e.target.value; resultsContainer.innerHTML = ''; if (query.length < 2) { resultsContainer.innerHTML = '
请输入至少2个字符
'; return; } const results = fuse.search(query, {limit: 20}); if (results.length > 0) { results.forEach(({item, score, matches}) => { const resultEl = document.createElement('a'); resultEl.className = 'search-result-item'; resultEl.href = item.url; resultEl.addEventListener('click', () => { modalOverlay.style.display = 'none'; }); let snippet = ''; const match = matches.find(m => m.key === 'content' || m.key === 'headings'); if (match) { const {value, indices} = match; const start = Math.max(0, indices[0][0] - 30); const end = Math.min(value.length, indices[0][1] + 30); snippet = `...${value.substring(start, end)}...`; } else { snippet = item.content.substring(0, 100) + '...'; } resultEl.innerHTML = `
${item.mainTitle}
${item.breadcrumbs.replace(/\/\/\//g, ' › ')}
${snippet.replace(//g, ">")}
`; resultsContainer.appendChild(resultEl); }); } else { resultsContainer.innerHTML = `
未找到与 "${query}" 相关的结果
`; } }); });