This commit is contained in:
Zuoling Rong
2025-02-25 23:55:27 +08:00
parent b77a97c7b1
commit b830a03a28
4 changed files with 533 additions and 129 deletions

375
script.js
View File

@@ -20,6 +20,57 @@ document.addEventListener('DOMContentLoaded', () => {
let isInitialLoad = true;
let isSidebarOpen = false;
let isSearchOpen = false;
// 搜索索引,用于提高搜索效率
let searchIndex = {
initialized: false,
items: []
};
// 初始化搜索索引
function initSearchIndex() {
if (searchIndex.initialized) return;
searchIndex.items = [];
try {
// 为每个页面创建索引
pages.forEach(page => {
if (page.id === 'search-results') return;
const pageId = page.id;
page.querySelectorAll('.site-card').forEach(card => {
try {
const title = card.querySelector('h3')?.textContent?.toLowerCase() || '';
const description = card.querySelector('p')?.textContent?.toLowerCase() || '';
const url = card.href || card.getAttribute('href') || '#';
const icon = card.querySelector('i')?.className || '';
// 将卡片信息添加到索引中
searchIndex.items.push({
pageId,
title,
description,
url,
icon,
element: card,
// 预先计算搜索文本,提高搜索效率
searchText: (title + ' ' + description).toLowerCase()
});
} catch (cardError) {
console.error('Error processing card:', cardError);
}
});
});
searchIndex.initialized = true;
console.log('Search index initialized with', searchIndex.items.length, 'items');
} catch (error) {
console.error('Error initializing search index:', error);
searchIndex.initialized = true; // 防止反复尝试初始化
}
}
// 移动端菜单切换
function toggleSidebar() {
@@ -106,6 +157,11 @@ document.addEventListener('DOMContentLoaded', () => {
// 搜索功能
function performSearch(searchTerm) {
// 确保搜索索引已初始化
if (!searchIndex.initialized) {
initSearchIndex();
}
searchTerm = searchTerm.toLowerCase().trim();
// 如果搜索框为空,重置所有内容
@@ -118,67 +174,205 @@ document.addEventListener('DOMContentLoaded', () => {
isSearchActive = true;
}
let hasResults = false;
const searchResults = new Map();
try {
// 使用搜索索引进行搜索
const searchResults = new Map();
let hasResults = false;
// 使用更高效的搜索算法
const matchedItems = searchIndex.items.filter(item => {
return item.searchText.includes(searchTerm);
});
// 按页面分组结果
matchedItems.forEach(item => {
if (!searchResults.has(item.pageId)) {
searchResults.set(item.pageId, []);
}
// 克隆元素以避免修改原始DOM
searchResults.get(item.pageId).push(item.element.cloneNode(true));
hasResults = true;
});
// 收集所有匹配结果
pages.forEach(page => {
if (page.id === 'search-results') return;
// 使用requestAnimationFrame批量更新DOM减少重排重绘
requestAnimationFrame(() => {
try {
// 清空并隐藏所有搜索区域
searchSections.forEach(section => {
try {
const grid = section.querySelector('.sites-grid');
if (grid) {
grid.innerHTML = ''; // 使用innerHTML清空比removeChild更高效
}
section.style.display = 'none';
} catch (sectionError) {
console.error('Error clearing search section:', sectionError);
}
});
const pageId = page.id;
const matches = [];
// 使用DocumentFragment批量添加DOM元素减少重排
searchResults.forEach((matches, pageId) => {
const section = searchResultsPage.querySelector(`[data-section="${pageId}"]`);
if (section) {
try {
const grid = section.querySelector('.sites-grid');
if (grid) {
const fragment = document.createDocumentFragment();
matches.forEach(card => {
// 高亮匹配文本
highlightSearchTerm(card, searchTerm);
fragment.appendChild(card);
});
grid.appendChild(fragment);
section.style.display = 'block';
}
} catch (gridError) {
console.error(`Error updating search results for ${pageId}:`, gridError);
}
} else {
console.warn(`Search section for page "${pageId}" not found`);
}
});
page.querySelectorAll('.site-card').forEach(card => {
const title = card.querySelector('h3').textContent.toLowerCase();
const description = card.querySelector('p').textContent.toLowerCase();
if (title.includes(searchTerm) || description.includes(searchTerm)) {
matches.push(card.cloneNode(true));
hasResults = true;
// 更新搜索结果页面状态
const subtitle = searchResultsPage.querySelector('.subtitle');
if (subtitle) {
subtitle.textContent = hasResults
? `在所有页面中找到 ${matchedItems.length} 个匹配项`
: '未找到匹配的结果';
}
// 显示搜索结果页面
if (currentPageId !== 'search-results') {
currentPageId = 'search-results';
pages.forEach(page => {
page.classList.toggle('active', page.id === 'search-results');
});
}
// 更新搜索状态样式
searchBox.classList.toggle('has-results', hasResults);
searchBox.classList.toggle('no-results', !hasResults);
} catch (uiError) {
console.error('Error updating search UI:', uiError);
}
});
if (matches.length > 0) {
searchResults.set(pageId, matches);
}
});
// 批量更新DOM
requestAnimationFrame(() => {
// 清空并隐藏所有搜索区域
searchSections.forEach(section => {
const grid = section.querySelector('.sites-grid');
while (grid.firstChild) {
grid.removeChild(grid.firstChild);
} catch (searchError) {
console.error('Error performing search:', searchError);
}
}
// 高亮搜索匹配文本
function highlightSearchTerm(card, searchTerm) {
if (!card || !searchTerm) return;
try {
const title = card.querySelector('h3');
const description = card.querySelector('p');
if (!title || !description) return;
// 安全地高亮标题中的匹配文本
if (title.textContent.toLowerCase().includes(searchTerm)) {
const titleText = title.textContent;
const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi');
// 创建安全的DOM结构而不是直接使用innerHTML
const titleFragment = document.createDocumentFragment();
let lastIndex = 0;
let match;
// 使用正则表达式查找所有匹配项
const titleRegex = new RegExp(regex);
while ((match = titleRegex.exec(titleText)) !== null) {
// 添加匹配前的文本
if (match.index > lastIndex) {
titleFragment.appendChild(document.createTextNode(
titleText.substring(lastIndex, match.index)
));
}
// 添加高亮的匹配文本
const span = document.createElement('span');
span.className = 'highlight';
span.textContent = match[0];
titleFragment.appendChild(span);
lastIndex = match.index + match[0].length;
// 防止无限循环
if (titleRegex.lastIndex === 0) break;
}
section.style.display = 'none';
});
// 填充匹配结果
searchResults.forEach((matches, pageId) => {
const section = searchResultsPage.querySelector(`[data-section="${pageId}"]`);
const grid = section.querySelector('.sites-grid');
const fragment = document.createDocumentFragment();
matches.forEach(card => fragment.appendChild(card));
grid.appendChild(fragment);
section.style.display = 'block';
});
// 更新搜索结果页面状态
searchResultsPage.querySelector('.subtitle').textContent =
hasResults ? '在所有页面中找到的匹配项' : '未找到匹配的结果';
// 显示搜索结果页面
if (currentPageId !== 'search-results') {
currentPageId = 'search-results';
pages.forEach(page => {
page.classList.toggle('active', page.id === 'search-results');
});
// 添加剩余文本
if (lastIndex < titleText.length) {
titleFragment.appendChild(document.createTextNode(
titleText.substring(lastIndex)
));
}
// 清空原标题并添加新内容
while (title.firstChild) {
title.removeChild(title.firstChild);
}
title.appendChild(titleFragment);
}
// 更新搜索状态样式
searchBox.classList.toggle('has-results', hasResults);
searchBox.classList.toggle('no-results', !hasResults);
});
// 安全地高亮描述中的匹配文本
if (description.textContent.toLowerCase().includes(searchTerm)) {
const descText = description.textContent;
const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi');
// 创建安全的DOM结构而不是直接使用innerHTML
const descFragment = document.createDocumentFragment();
let lastIndex = 0;
let match;
// 使用正则表达式查找所有匹配项
const descRegex = new RegExp(regex);
while ((match = descRegex.exec(descText)) !== null) {
// 添加匹配前的文本
if (match.index > lastIndex) {
descFragment.appendChild(document.createTextNode(
descText.substring(lastIndex, match.index)
));
}
// 添加高亮的匹配文本
const span = document.createElement('span');
span.className = 'highlight';
span.textContent = match[0];
descFragment.appendChild(span);
lastIndex = match.index + match[0].length;
// 防止无限循环
if (descRegex.lastIndex === 0) break;
}
// 添加剩余文本
if (lastIndex < descText.length) {
descFragment.appendChild(document.createTextNode(
descText.substring(lastIndex)
));
}
// 清空原描述并添加新内容
while (description.firstChild) {
description.removeChild(description.firstChild);
}
description.appendChild(descFragment);
}
} catch (error) {
console.error('Error highlighting search term:', error);
}
}
// 转义正则表达式特殊字符
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// 重置搜索状态
@@ -187,30 +381,52 @@ document.addEventListener('DOMContentLoaded', () => {
isSearchActive = false;
requestAnimationFrame(() => {
// 清空搜索结果
searchSections.forEach(section => {
const grid = section.querySelector('.sites-grid');
while (grid.firstChild) {
grid.removeChild(grid.firstChild);
try {
requestAnimationFrame(() => {
try {
// 清空搜索结果
searchSections.forEach(section => {
try {
const grid = section.querySelector('.sites-grid');
if (grid) {
while (grid.firstChild) {
grid.removeChild(grid.firstChild);
}
}
section.style.display = 'none';
} catch (sectionError) {
console.error('Error clearing search section:', sectionError);
}
});
// 移除搜索状态样式
searchBox.classList.remove('has-results', 'no-results');
// 恢复到当前激活的页面
const currentActiveNav = document.querySelector('.nav-item.active');
if (currentActiveNav) {
const targetPageId = currentActiveNav.getAttribute('data-page');
if (targetPageId && currentPageId !== targetPageId) {
currentPageId = targetPageId;
pages.forEach(page => {
page.classList.toggle('active', page.id === targetPageId);
});
}
} else {
// 如果没有激活的导航项,默认显示首页
currentPageId = 'home';
pages.forEach(page => {
page.classList.toggle('active', page.id === 'home');
});
}
} catch (uiError) {
console.error('Error resetting search UI:', uiError);
}
section.style.display = 'none';
});
// 移除搜索状态样式
searchBox.classList.remove('has-results', 'no-results');
// 恢复到当前激活的页面
const currentActiveNav = document.querySelector('.nav-item.active');
const targetPageId = currentActiveNav.getAttribute('data-page');
if (currentPageId !== targetPageId) {
currentPageId = targetPageId;
pages.forEach(page => {
page.classList.toggle('active', page.id === targetPageId);
});
}
});
} catch (error) {
console.error('Error in resetSearch:', error);
}
}
// 搜索输入事件(使用防抖)
@@ -278,6 +494,13 @@ document.addEventListener('DOMContentLoaded', () => {
category.style.opacity = '1';
}, index * 100);
});
// 初始化搜索索引使用requestIdleCallback或setTimeout延迟初始化避免影响页面加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => initSearchIndex());
} else {
setTimeout(initSearchIndex, 1000);
}
});
});
});