Files
yu-zhi-ran/platform/frontend/navigation-component.js
T
lt ef168e3d90 feat(frontend): 公共导航组件完整实现
- 组件改为render函数,避免模板编译器依赖
- 自动注入样式,无需页面额外CSS
- 自适应布局:PC固定侧边栏,H5底部导航
- 所有页面统一使用,admin权限动态控制
- 自动调整main-content边距,防止遮挡
2026-05-08 11:03:33 +08:00

169 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 公共导航组件定义(使用 render 函数,兼容 prod 版 Vue
const NavigationComponent = {
name: 'NavigationComponent',
props: {
currentPage: {
type: String,
required: true,
validator: (value) => ['dashboard', 'topics', 'logs', 'users', 'admin'].includes(value)
},
isAdmin: {
type: Boolean,
default: false
}
},
render() {
const h = this.$createElement;
const navigate = (page) => this.$emit('navigate', page === 'dashboard' ? '/' : page + '.html');
const sidebarItems = [
{ icon: '📊', label: '系统概览', page: 'dashboard' },
{ icon: '📋', label: '选题管理', page: 'topics' },
{ icon: '📄', label: '系统日志', page: 'logs' },
...(this.isAdmin ? [
{ icon: '👥', label: '用户管理', page: 'users' },
{ icon: '⚙️', label: '系统管理', page: 'admin' }
] : [])
];
const mobileItems = [
{ icon: '📊', label: '', page: 'dashboard' },
{ icon: '📋', label: '选题', page: 'topics' },
{ icon: '📄', label: '日志', page: 'logs' },
...(this.isAdmin ? [
{ icon: '👥', label: '用户', page: 'users' },
{ icon: '⚙️', label: '系统', page: 'admin' }
] : [])
];
const createSidebarBtn = (item) => {
const isActive = this.currentPage === item.page;
return h('button', {
class: isActive ? 'sidebar-btn active' : 'sidebar-btn',
on: { click: () => navigate(item.page) }
}, [item.icon, ' ', item.label].join(''));
};
const createMobileBtn = (item) => {
const isActive = this.currentPage === item.page;
const content = item.label ? [item.icon, item.label].join('') : item.icon;
return h('button', {
class: isActive ? 'mobile-nav-btn active' : 'mobile-nav-btn',
on: { click: () => navigate(item.page) }
}, content);
};
return h('div', { class: 'navigation-wrapper' }, [
h('aside', { class: 'sidebar' }, [
h('div', { class: 'sidebar-header' }, [h('h3', '宇之然平台')]),
h('nav', { class: 'sidebar-nav' }, sidebarItems.map(createSidebarBtn))
]),
h('nav', { class: 'mobile-nav' }, mobileItems.map(createMobileBtn))
]);
}
};
function injectNavigationStyles() {
if (document.getElementById('navigation-styles')) return;
const styles = `
.navigation-wrapper { position: relative; }
.navigation-wrapper .sidebar {
width: 180px; background: white; padding: 12px; box-shadow: 2px 0 8px rgba(0,0,0,0.05);
position: fixed; left: 0; top: 60px; bottom: 0; overflow-y: auto; z-index: 9999;
}
.navigation-wrapper .sidebar-header { padding: 12px 8px 16px; border-bottom: 1px solid #ebeef5; margin-bottom: 12px; }
.navigation-wrapper .sidebar-header h3 { margin: 0; font-size: 16px; font-weight: 600; color: #303133; }
.navigation-wrapper .sidebar-btn {
width: 100%; text-align: left; padding: 10px 12px; border: none; background: transparent; border-radius: 8px;
margin-bottom: 6px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; display: flex; align-items: center; gap: 6px;
}
.navigation-wrapper .sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.navigation-wrapper .sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.navigation-wrapper .mobile-nav {
display: none; position: fixed; bottom: 0; left: 0; right: 0; background: white;
box-shadow: 0 -2px 8px rgba(0,0,0,0.1); padding: 8px 0; z-index: 1000; justify-content: space-around;
}
.navigation-wrapper .mobile-nav-btn {
flex: 1; border: none; background: transparent; padding: 10px 4px; text-align: center;
font-size: 11px; color: #606266; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.navigation-wrapper .mobile-nav-btn:hover { color: #409eff; }
.navigation-wrapper .mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.navigation-wrapper .sidebar { display: none !important; }
.navigation-wrapper .mobile-nav { display: flex !important; }
}
undefined
body > #app > .navigation-wrapper + .main-content { margin-left: 180px !important; }
@media (max-width: 768px) {
body > #app > .navigation-wrapper + .main-content { margin-left: 0 !important; padding-bottom: 60px !important; }
}
`;
const styleEl = document.createElement('style');
styleEl.id = 'navigation-styles';
styleEl.textContent = styles;
document.head.appendChild(styleEl);
}
function installNavigation(app) {
injectNavigationStyles();
app.component('navigation-component', NavigationComponent);
app.config.globalProperties.$redirectToPage = (path) => { window.location.href = path; };
return app;
}
if (typeof window !== 'undefined') {
window.NavigationComponent = NavigationComponent;
window.installNavigation = installNavigation;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { NavigationComponent, installNavigation };
}
// 自动调整 main-content 边距
(function adjustLayout() {
// 等待DOM加载
const check = () => {
const main = document.querySelector('.main-content');
if (main) {
// 检测是否有导航组件
const hasSidebar = document.querySelector('.navigation-wrapper .sidebar') !== null;
if (hasSidebar) {
main.style.marginLeft = '180px';
} else {
main.style.marginLeft = '0';
}
}
// 移动端调整
const mobileNav = document.querySelector('.navigation-wrapper .mobile-nav');
if (mobileNav) {
main.style.paddingBottom = '60px';
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', check);
} else {
check();
}
// Vue 挂载后再次调整(通过全局API)
if (window.Vue) {
window.Vue.mixin({
mounted() {
setTimeout(check, 0);
}
});
}
})();
// 运行时检查(仅开发环境)
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
setTimeout(() => {
const nav = document.querySelector('.navigation-wrapper');
if (!nav) {
console.warn('[NavigationComponent] 未找到 .navigation-wrapper 元素,可能未正确渲染');
} else {
console.log('[NavigationComponent] 已渲染');
}
}, 100);
});
}