添加侧边栏嵌套子菜单功能 and 侧边栏处理逻辑
This commit is contained in:
111
assets/style.css
111
assets/style.css
@@ -1476,3 +1476,114 @@ body .content.expanded {
|
|||||||
color: var(--accent-hover);
|
color: var(--accent-hover);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 导航项包装器 - 包含导航项和子菜单 */
|
||||||
|
.nav-item-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子菜单切换图标 */
|
||||||
|
.submenu-toggle {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子菜单展开状态图标旋转 */
|
||||||
|
.nav-item-wrapper.expanded .submenu-toggle {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子菜单容器 */
|
||||||
|
.submenu {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子菜单展开状态 */
|
||||||
|
.nav-item-wrapper.expanded .submenu {
|
||||||
|
max-height: 500px; /* 设置足够大的值以容纳所有可能的子菜单项 */
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子菜单项样式 */
|
||||||
|
.submenu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0.8rem;
|
||||||
|
color: var(--nav-item-color);
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin: 0.1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item i {
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item span {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item:hover {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
color: var(--text-bright);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item.active {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
color: var(--text-bright);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏折叠状态下子菜单样式 */
|
||||||
|
.sidebar.collapsed .submenu {
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--sidebar-bg);
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
box-shadow: 4px 0 10px var(--shadow-color);
|
||||||
|
margin-left: 0;
|
||||||
|
width: 180px;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .nav-item-wrapper:hover .submenu {
|
||||||
|
max-height: 500px;
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .submenu-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端样式调整 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar .submenu {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.active .submenu-item {
|
||||||
|
padding: 0.5rem 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -228,14 +228,84 @@ function loadConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 生成导航菜单
|
// 生成导航菜单
|
||||||
function generateNavigation(navigation) {
|
function generateNavigation(navigation, config) {
|
||||||
return navigation.map(nav => `
|
return navigation.map(nav => {
|
||||||
<a href="#" class="nav-item${nav.active ? ' active' : ''}" data-page="${escapeHtml(nav.id)}">
|
// 根据页面ID获取对应的子菜单项(分类)
|
||||||
<div class="icon-container">
|
let submenuItems = '';
|
||||||
<i class="${escapeHtml(nav.icon)}"></i>
|
|
||||||
</div>
|
// 首页页面添加子菜单(分类)
|
||||||
<span class="nav-text">${escapeHtml(nav.name)}</span>
|
if (nav.id === 'home' && Array.isArray(config.categories)) {
|
||||||
</a>`).join('\n');
|
submenuItems = `
|
||||||
|
<div class="submenu">
|
||||||
|
${config.categories.map(category => `
|
||||||
|
<a href="#${category.name}" class="submenu-item" data-page="${nav.id}" data-category="${category.name}">
|
||||||
|
<i class="${escapeHtml(category.icon)}"></i>
|
||||||
|
<span>${escapeHtml(category.name)}</span>
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
// 书签页面添加子菜单(分类)
|
||||||
|
else if (nav.id === 'bookmarks' && config.bookmarks && Array.isArray(config.bookmarks.categories)) {
|
||||||
|
submenuItems = `
|
||||||
|
<div class="submenu">
|
||||||
|
${config.bookmarks.categories.map(category => `
|
||||||
|
<a href="#${category.name}" class="submenu-item" data-page="${nav.id}" data-category="${category.name}">
|
||||||
|
<i class="${escapeHtml(category.icon)}"></i>
|
||||||
|
<span>${escapeHtml(category.name)}</span>
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
// 项目页面添加子菜单
|
||||||
|
else if (nav.id === 'projects' && config.projects && Array.isArray(config.projects.categories)) {
|
||||||
|
submenuItems = `
|
||||||
|
<div class="submenu">
|
||||||
|
${config.projects.categories.map(category => `
|
||||||
|
<a href="#${category.name}" class="submenu-item" data-page="${nav.id}" data-category="${category.name}">
|
||||||
|
<i class="${escapeHtml(category.icon)}"></i>
|
||||||
|
<span>${escapeHtml(category.name)}</span>
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
// 文章页面添加子菜单
|
||||||
|
else if (nav.id === 'articles' && config.articles && Array.isArray(config.articles.categories)) {
|
||||||
|
submenuItems = `
|
||||||
|
<div class="submenu">
|
||||||
|
${config.articles.categories.map(category => `
|
||||||
|
<a href="#${category.name}" class="submenu-item" data-page="${nav.id}" data-category="${category.name}">
|
||||||
|
<i class="${escapeHtml(category.icon)}"></i>
|
||||||
|
<span>${escapeHtml(category.name)}</span>
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
// 友链页面添加子菜单
|
||||||
|
else if (nav.id === 'friends' && config.friends && Array.isArray(config.friends.categories)) {
|
||||||
|
submenuItems = `
|
||||||
|
<div class="submenu">
|
||||||
|
${config.friends.categories.map(category => `
|
||||||
|
<a href="#${category.name}" class="submenu-item" data-page="${nav.id}" data-category="${category.name}">
|
||||||
|
<i class="${escapeHtml(category.icon)}"></i>
|
||||||
|
<span>${escapeHtml(category.name)}</span>
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="nav-item-wrapper">
|
||||||
|
<a href="#" class="nav-item${nav.active ? ' active' : ''}" data-page="${escapeHtml(nav.id)}">
|
||||||
|
<div class="icon-container">
|
||||||
|
<i class="${escapeHtml(nav.icon)}"></i>
|
||||||
|
</div>
|
||||||
|
<span class="nav-text">${escapeHtml(nav.name)}</span>
|
||||||
|
${submenuItems ? '<i class="fas fa-chevron-down submenu-toggle"></i>' : ''}
|
||||||
|
</a>
|
||||||
|
${submenuItems}
|
||||||
|
</div>`;
|
||||||
|
}).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成网站卡片HTML
|
// 生成网站卡片HTML
|
||||||
@@ -447,7 +517,7 @@ ${content}
|
|||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<div class="nav-section">
|
<div class="nav-section">
|
||||||
${generateNavigation(config.navigation)}
|
${generateNavigation(config.navigation, config)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-section">
|
<div class="nav-section">
|
||||||
@@ -545,7 +615,7 @@ function processTemplate(template, config) {
|
|||||||
'{{SITE_LOGO_TEXT}}': escapeHtml(config.site.logo_text || '导航站'), // 从配置中获取,如果不存在则使用默认值
|
'{{SITE_LOGO_TEXT}}': escapeHtml(config.site.logo_text || '导航站'), // 从配置中获取,如果不存在则使用默认值
|
||||||
'{{GOOGLE_FONTS}}': googleFontsLink,
|
'{{GOOGLE_FONTS}}': googleFontsLink,
|
||||||
'{{{FONT_VARIABLES}}}': fontVariables,
|
'{{{FONT_VARIABLES}}}': fontVariables,
|
||||||
'{{NAVIGATION}}': generateNavigation(config.navigation),
|
'{{NAVIGATION}}': generateNavigation(config.navigation, config),
|
||||||
'{{SOCIAL_LINKS}}': generateSocialLinks(config.social),
|
'{{SOCIAL_LINKS}}': generateSocialLinks(config.social),
|
||||||
'{{CURRENT_YEAR}}': currentYear,
|
'{{CURRENT_YEAR}}': currentYear,
|
||||||
'{{HOME_CONTENT}}': generateHomeContent(config),
|
'{{HOME_CONTENT}}': generateHomeContent(config),
|
||||||
|
|||||||
171
src/script.js
171
src/script.js
@@ -14,12 +14,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取DOM元素
|
// 获取DOM元素 - 基本元素
|
||||||
const searchInput = document.getElementById('search');
|
const searchInput = document.getElementById('search');
|
||||||
const siteCards = document.querySelectorAll('.site-card');
|
|
||||||
const categories = document.querySelectorAll('.category');
|
|
||||||
const navItems = document.querySelectorAll('.nav-item');
|
|
||||||
const pages = document.querySelectorAll('.page');
|
|
||||||
const searchBox = document.querySelector('.search-box');
|
const searchBox = document.querySelector('.search-box');
|
||||||
const searchResultsPage = document.getElementById('search-results');
|
const searchResultsPage = document.getElementById('search-results');
|
||||||
const searchSections = searchResultsPage.querySelectorAll('.search-section');
|
const searchSections = searchResultsPage.querySelectorAll('.search-section');
|
||||||
@@ -139,6 +135,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 为每个页面创建索引
|
// 为每个页面创建索引
|
||||||
|
const pages = document.querySelectorAll('.page');
|
||||||
pages.forEach(page => {
|
pages.forEach(page => {
|
||||||
if (page.id === 'search-results') return;
|
if (page.id === 'search-results') return;
|
||||||
|
|
||||||
@@ -242,6 +239,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// 使用 RAF 确保动画流畅
|
// 使用 RAF 确保动画流畅
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
const pages = document.querySelectorAll('.page');
|
||||||
pages.forEach(page => {
|
pages.forEach(page => {
|
||||||
const shouldBeActive = page.id === pageId;
|
const shouldBeActive = page.id === pageId;
|
||||||
if (shouldBeActive !== page.classList.contains('active')) {
|
if (shouldBeActive !== page.classList.contains('active')) {
|
||||||
@@ -576,30 +574,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 导航项点击效果
|
|
||||||
navItems.forEach(item => {
|
|
||||||
item.addEventListener('click', (e) => {
|
|
||||||
if (item.getAttribute('target') === '_blank') return;
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
navItems.forEach(nav => {
|
|
||||||
nav.classList.toggle('active', nav === item);
|
|
||||||
});
|
|
||||||
|
|
||||||
const pageId = item.getAttribute('data-page');
|
|
||||||
if (pageId) {
|
|
||||||
showPage(pageId);
|
|
||||||
|
|
||||||
// 在移动端视图下点击导航项后自动收起侧边栏
|
|
||||||
if (isMobile() && isSidebarOpen) {
|
|
||||||
closeAllPanels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
// 获取可能在HTML生成后才存在的DOM元素
|
||||||
|
const siteCards = document.querySelectorAll('.site-card');
|
||||||
|
const categories = document.querySelectorAll('.category');
|
||||||
|
const navItems = document.querySelectorAll('.nav-item');
|
||||||
|
const navItemWrappers = document.querySelectorAll('.nav-item-wrapper');
|
||||||
|
const submenuItems = document.querySelectorAll('.submenu-item');
|
||||||
|
const pages = document.querySelectorAll('.page');
|
||||||
|
|
||||||
// 初始化主题
|
// 初始化主题
|
||||||
initTheme();
|
initTheme();
|
||||||
|
|
||||||
@@ -617,6 +601,139 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}, index * 100);
|
}, index * 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始展开当前页面的子菜单
|
||||||
|
const activeNavItem = document.querySelector('.nav-item.active');
|
||||||
|
if (activeNavItem) {
|
||||||
|
const activeWrapper = activeNavItem.closest('.nav-item-wrapper');
|
||||||
|
if (activeWrapper) {
|
||||||
|
activeWrapper.classList.add('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航项点击效果
|
||||||
|
navItems.forEach(item => {
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
if (item.getAttribute('target') === '_blank') return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 获取当前项的父级wrapper
|
||||||
|
const wrapper = item.closest('.nav-item-wrapper');
|
||||||
|
const hasSubmenu = wrapper && wrapper.querySelector('.submenu');
|
||||||
|
|
||||||
|
// 处理子菜单展开/折叠
|
||||||
|
if (hasSubmenu) {
|
||||||
|
// 如果点击的导航项已经激活且有子菜单,则切换子菜单展开状态
|
||||||
|
if (item.classList.contains('active')) {
|
||||||
|
wrapper.classList.toggle('expanded');
|
||||||
|
} else {
|
||||||
|
// 关闭所有已展开的子菜单
|
||||||
|
navItemWrappers.forEach(navWrapper => {
|
||||||
|
if (navWrapper !== wrapper) {
|
||||||
|
navWrapper.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 展开当前子菜单
|
||||||
|
wrapper.classList.add('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活导航项
|
||||||
|
navItems.forEach(nav => {
|
||||||
|
nav.classList.toggle('active', nav === item);
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageId = item.getAttribute('data-page');
|
||||||
|
if (pageId) {
|
||||||
|
showPage(pageId);
|
||||||
|
|
||||||
|
// 在移动端视图下点击导航项后自动收起侧边栏
|
||||||
|
if (isMobile() && isSidebarOpen && !hasSubmenu) {
|
||||||
|
closeAllPanels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 子菜单项点击效果
|
||||||
|
submenuItems.forEach(item => {
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 获取页面ID和分类名称
|
||||||
|
const pageId = item.getAttribute('data-page');
|
||||||
|
const categoryName = item.getAttribute('data-category');
|
||||||
|
|
||||||
|
if (pageId) {
|
||||||
|
// 清除所有子菜单项的激活状态
|
||||||
|
submenuItems.forEach(subItem => {
|
||||||
|
subItem.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 激活当前子菜单项
|
||||||
|
item.classList.add('active');
|
||||||
|
|
||||||
|
// 激活相应的导航项
|
||||||
|
navItems.forEach(nav => {
|
||||||
|
nav.classList.toggle('active', nav.getAttribute('data-page') === pageId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示对应页面
|
||||||
|
showPage(pageId);
|
||||||
|
|
||||||
|
// 等待页面切换完成后滚动到对应分类
|
||||||
|
setTimeout(() => {
|
||||||
|
// 查找目标分类元素
|
||||||
|
const targetPage = document.getElementById(pageId);
|
||||||
|
if (targetPage) {
|
||||||
|
const targetCategory = Array.from(targetPage.querySelectorAll('.category h2')).find(
|
||||||
|
heading => heading.textContent.trim().includes(categoryName)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetCategory) {
|
||||||
|
// 优化的滚动实现:滚动到使目标分类位于视口1/4处(更靠近顶部位置)
|
||||||
|
try {
|
||||||
|
// 直接获取所需元素和属性,减少重复查询
|
||||||
|
const contentElement = document.querySelector('.content');
|
||||||
|
|
||||||
|
if (contentElement && contentElement.scrollHeight > contentElement.clientHeight) {
|
||||||
|
// 获取目标元素相对于内容区域的位置
|
||||||
|
const rect = targetCategory.getBoundingClientRect();
|
||||||
|
const containerRect = contentElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 计算目标应该在视口中的位置(视口高度的1/4处)
|
||||||
|
const desiredPosition = containerRect.height / 4;
|
||||||
|
|
||||||
|
// 计算需要滚动的位置
|
||||||
|
const scrollPosition = contentElement.scrollTop + rect.top - containerRect.top - desiredPosition;
|
||||||
|
|
||||||
|
// 执行滚动
|
||||||
|
contentElement.scrollTo({
|
||||||
|
top: scrollPosition,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 回退到基本滚动方式
|
||||||
|
targetCategory.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during scroll:', error);
|
||||||
|
// 回退到基本滚动方式
|
||||||
|
targetCategory.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 25); // 延迟时间
|
||||||
|
|
||||||
|
// 在移动端视图下点击子菜单项后自动收起侧边栏
|
||||||
|
if (isMobile() && isSidebarOpen) {
|
||||||
|
closeAllPanels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 初始化搜索索引(使用requestIdleCallback或setTimeout延迟初始化,避免影响页面加载)
|
// 初始化搜索索引(使用requestIdleCallback或setTimeout延迟初始化,避免影响页面加载)
|
||||||
if ('requestIdleCallback' in window) {
|
if ('requestIdleCallback' in window) {
|
||||||
requestIdleCallback(() => initSearchIndex());
|
requestIdleCallback(() => initSearchIndex());
|
||||||
|
|||||||
Reference in New Issue
Block a user