fix(navigation): 纯DOM注入方案,彻底避开Vue渲染问题
- installNavigation 改为直接创建/插入导航DOM - 自动从 Vue 实例提取 isAdmin、currentPage、redirectToPage - 不再依赖 Vue createElement 或组件渲染 - 支持移动端/PC端自适应
This commit is contained in:
@@ -1,143 +1,116 @@
|
|||||||
// 最简导航组件 - 原生DOM操作(无h函数)
|
// 纯DOM导航 - 通过 installNavigation 直接插入
|
||||||
(function() {
|
(function() {
|
||||||
console.log('[Nav] 脚本加载');
|
console.log('[Nav] 脚本加载');
|
||||||
|
|
||||||
function injectStyles() {
|
function injectStyles() {
|
||||||
if (document.getElementById('navigation-styles')) return;
|
if (document.getElementById('navigation-styles')) return;
|
||||||
const styles = `
|
const styles = `
|
||||||
.navigation-wrapper { position: relative; }
|
.navigation-wrapper .sidebar { position: fixed; top: 60px; left: 0; bottom: 0; width: 180px; background: #fff; padding: 12px; box-shadow: 2px 0 8px rgba(0,0,0,0.1); overflow-y: auto; z-index: 99999; border-right: 1px solid #ebeef5; }
|
||||||
.navigation-wrapper .sidebar {
|
|
||||||
position: fixed; top: 60px; left: 0; bottom: 0; width: 180px;
|
|
||||||
background: #ffffff; padding: 12px; box-shadow: 2px 0 8px rgba(0,0,0,0.1);
|
|
||||||
overflow-y: auto; z-index: 99999; border-right: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
.navigation-wrapper .sidebar-header { padding: 12px 8px 16px; border-bottom: 1px solid #ebeef5; margin-bottom: 12px; }
|
.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-header h3 { margin: 0; font-size: 16px; font-weight: 600; color: #303133; }
|
||||||
.navigation-wrapper .sidebar-btn {
|
.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; }
|
||||||
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:hover { background: #f5f7fa; color: #409eff; }
|
||||||
.navigation-wrapper .sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600 !important; }
|
.navigation-wrapper .sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600 !important; }
|
||||||
.navigation-wrapper .mobile-nav {
|
.navigation-wrapper .mobile-nav { display: none; position: fixed; bottom: 0; left: 0; right: 0; background: #1890ff; box-shadow: 0 -2px 8px rgba(0,0,0,0.2); padding: 8px 0; z-index: 99999; justify-content: space-around; }
|
||||||
display: none; position: fixed; bottom: 0; left: 0; right: 0; background: #1890ff;
|
.navigation-wrapper .mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 10px 4px; text-align: center; font-size: 12px; color: white !important; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 2px; }
|
||||||
box-shadow: 0 -2px 8px rgba(0,0,0,0.2); padding: 8px 0; z-index: 99999; justify-content: space-around;
|
.navigation-wrapper .mobile-nav-btn:hover { background: rgba(255,255,255,0.2); }
|
||||||
}
|
|
||||||
.navigation-wrapper .mobile-nav-btn {
|
|
||||||
flex: 1; border: none; background: transparent; padding: 10px 4px; text-align: center;
|
|
||||||
font-size: 12px; color: white !important; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 2px;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
.navigation-wrapper .mobile-nav-btn:hover { background: rgba(255,255,255,0.2); color: white !important; }
|
|
||||||
.navigation-wrapper .mobile-nav-btn.active { background: rgba(255,255,255,0.3); font-weight: 600 !important; }
|
.navigation-wrapper .mobile-nav-btn.active { background: rgba(255,255,255,0.3); font-weight: 600 !important; }
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) { .navigation-wrapper .sidebar { display: none !important; } .navigation-wrapper .mobile-nav { display: flex !important; } }
|
||||||
.navigation-wrapper .sidebar { display: none !important; }
|
|
||||||
.navigation-wrapper .mobile-nav { display: flex !important; }
|
|
||||||
}
|
|
||||||
@media (min-width: 769px) { .navigation-wrapper .mobile-nav { display: none !important; } }
|
|
||||||
body > #app > .main-content { margin-left: 180px !important; padding-top: 60px !important; }
|
body > #app > .main-content { margin-left: 180px !important; padding-top: 60px !important; }
|
||||||
@media (max-width: 768px) { body > #app > .main-content { margin-left: 0 !important; padding-bottom: 60px !important; } }
|
@media (max-width: 768px) { body > #app > .main-content { margin-left: 0 !important; padding-bottom: 60px !important; } }
|
||||||
`;
|
`;
|
||||||
const styleEl = document.createElement('style');
|
document.head.appendChild(Object.assign(document.createElement('style'), { id: 'navigation-styles', textContent: styles }));
|
||||||
styleEl.id = 'navigation-styles';
|
|
||||||
styleEl.textContent = styles;
|
|
||||||
document.head.appendChild(styleEl);
|
|
||||||
console.log('[Nav] 样式已注入');
|
console.log('[Nav] 样式已注入');
|
||||||
}
|
}
|
||||||
|
|
||||||
const installNavigation = (app) => {
|
function createNavigation(currentPage, isAdmin, onNavigate) {
|
||||||
console.log('[Nav] installNavigation called');
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'navigation-wrapper';
|
||||||
|
|
||||||
|
// 侧边栏
|
||||||
|
const sidebar = document.createElement('aside');
|
||||||
|
sidebar.className = 'sidebar';
|
||||||
|
sidebar.innerHTML = `
|
||||||
|
<div class="sidebar-header"><h3>宇之然平台</h3></div>
|
||||||
|
<nav class="sidebar-nav">
|
||||||
|
<button class="sidebar-btn ${currentPage==='dashboard'?'active':''}" data-page="/">📊 系统概览</button>
|
||||||
|
<button class="sidebar-btn ${currentPage==='topics'?'active':''}" data-page="topics.html">📋 选题管理</button>
|
||||||
|
<button class="sidebar-btn ${currentPage==='logs'?'active':''}" data-page="logs.html">📄 系统日志</button>
|
||||||
|
${isAdmin ? `<button class="sidebar-btn ${currentPage==='users'?'active':''}" data-page="users.html">👥 用户管理</button>` : ''}
|
||||||
|
${isAdmin ? `<button class="sidebar-btn ${currentPage==='admin'?'active':''}" data-page="admin.html">⚙️ 系统管理</button>` : ''}
|
||||||
|
</nav>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 移动端底部导航
|
||||||
|
const mobileNav = document.createElement('nav');
|
||||||
|
mobileNav.className = 'mobile-nav';
|
||||||
|
mobileNav.innerHTML = `
|
||||||
|
<button class="mobile-nav-btn ${currentPage==='dashboard'?'active':''}" data-page="/">📊</button>
|
||||||
|
<button class="mobile-nav-btn ${currentPage==='topics'?'active':''}" data-page="topics.html">📋</button>
|
||||||
|
<button class="mobile-nav-btn ${currentPage==='logs'?'active':''}" data-page="logs.html">📄</button>
|
||||||
|
${isAdmin ? `<button class="mobile-nav-btn ${currentPage==='users'?'active':''}" data-page="users.html">👥</button>` : ''}
|
||||||
|
${isAdmin ? `<button class="mobile-nav-btn ${currentPage==='admin'?'active':''}" data-page="admin.html">⚙️</button>` : ''}
|
||||||
|
`;
|
||||||
|
|
||||||
|
wrapper.appendChild(sidebar);
|
||||||
|
wrapper.appendChild(mobileNav);
|
||||||
|
|
||||||
|
// 绑定点击事件
|
||||||
|
wrapper.querySelectorAll('button').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const page = btn.getAttribute('data-page');
|
||||||
|
console.log('[Nav] 导航到:', page);
|
||||||
|
if (onNavigate) onNavigate(page);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新的 installNavigation 接口:接收 app 对象(为了兼容),但实际不注册组件
|
||||||
|
window.installNavigation = function(app, options = {}) {
|
||||||
|
console.log('[Nav] installNavigation called (DOM mode)');
|
||||||
injectStyles();
|
injectStyles();
|
||||||
|
|
||||||
// 定义组件选项
|
// 延迟执行,确保DOM就绪
|
||||||
const component = {
|
const init = () => {
|
||||||
name: 'NavigationComponent',
|
const container = document.getElementById('app');
|
||||||
props: { currentPage: { type: String, required: true }, isAdmin: { type: Boolean, default: false } },
|
if (!container) {
|
||||||
mounted() {
|
console.warn('[Nav] #app 未找到,等待');
|
||||||
console.log('[NavigationComponent] 已挂载', this.currentPage, this.isAdmin);
|
setTimeout(init, 100);
|
||||||
|
return;
|
||||||
// 在组件挂载后立即渲染导航栏到DOM
|
|
||||||
setTimeout(() => {
|
|
||||||
const container = this.$el; // 组件根元素
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
// 渲染侧边栏
|
|
||||||
const sidebar = document.createElement('aside');
|
|
||||||
sidebar.className = 'sidebar';
|
|
||||||
|
|
||||||
const header = document.createElement('div');
|
|
||||||
header.className = 'sidebar-header';
|
|
||||||
const headerTitle = document.createElement('h3');
|
|
||||||
headerTitle.textContent = '宇之然平台';
|
|
||||||
header.appendChild(headerTitle);
|
|
||||||
|
|
||||||
const nav = document.createElement('nav');
|
|
||||||
nav.className = 'sidebar-nav';
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ icon: '📊', label: '系统概览', page: '/' },
|
|
||||||
{ icon: '📋', label: '选题管理', page: 'topics.html' },
|
|
||||||
{ icon: '📄', label: '系统日志', page: 'logs.html' },
|
|
||||||
...(this.isAdmin ? [{ icon: '👥', label: '用户管理', page: 'users.html' }, { icon: '⚙️', label: '系统管理', page: 'admin.html' }] : [])
|
|
||||||
];
|
|
||||||
|
|
||||||
items.forEach(item => {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.className = this.currentPage === (item.page === '/' ? 'dashboard' : item.page.replace('.html',''))
|
|
||||||
? 'sidebar-btn active' : 'sidebar-btn';
|
|
||||||
btn.textContent = item.icon + ' ' + item.label;
|
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
console.log('[NavigationComponent] 点击导航到:', item.page);
|
|
||||||
this.$emit('navigate', item.page);
|
|
||||||
});
|
|
||||||
|
|
||||||
nav.appendChild(btn);
|
|
||||||
});
|
|
||||||
|
|
||||||
sidebar.appendChild(header);
|
|
||||||
sidebar.appendChild(nav);
|
|
||||||
container.appendChild(sidebar);
|
|
||||||
|
|
||||||
// 渲染移动端底部导航
|
|
||||||
if (window.innerWidth <= 768) {
|
|
||||||
const mobileNav = document.createElement('nav');
|
|
||||||
mobileNav.className = 'mobile-nav';
|
|
||||||
|
|
||||||
const mobileItems = [
|
|
||||||
{ icon: '📊', label: '', page: '/' },
|
|
||||||
{ icon: '📋', label: '选题', page: 'topics.html' },
|
|
||||||
{ icon: '📄', label: '日志', page: 'logs.html' },
|
|
||||||
...(this.isAdmin ? [{ icon: '👥', label: '用户', page: 'users.html' }, { icon: '⚙️', label: '系统', page: 'admin.html' }] : [])
|
|
||||||
];
|
|
||||||
|
|
||||||
mobileItems.forEach(item => {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.className = this.currentPage === (item.page === '/' ? 'dashboard' : item.page.replace('.html',''))
|
|
||||||
? 'mobile-nav-btn active' : 'mobile-nav-btn';
|
|
||||||
|
|
||||||
const content = item.label ? (item.icon + ' ' + item.label) : item.icon;
|
|
||||||
btn.textContent = content;
|
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
console.log('[NavigationComponent] 点击导航到:', item.page);
|
|
||||||
this.$emit('navigate', item.page);
|
|
||||||
});
|
|
||||||
|
|
||||||
mobileNav.appendChild(btn);
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(mobileNav);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 尝试从 Vue 实例获取 isAdmin 和 currentPage
|
||||||
|
let isAdmin = false;
|
||||||
|
let currentPage = 'dashboard';
|
||||||
|
let redirectToPage = null;
|
||||||
|
|
||||||
|
// 尝试从 Vue 实例提取数据
|
||||||
|
if (app && app._instance && app._instance.proxy) {
|
||||||
|
const proxy = app._instance.proxy;
|
||||||
|
if (proxy.isAdmin !== undefined) isAdmin = proxy.isAdmin;
|
||||||
|
if (proxy.currentPage !== undefined) currentPage = proxy.currentPage;
|
||||||
|
if (proxy.redirectToPage) redirectToPage = proxy.redirectToPage;
|
||||||
|
} else if (window.Vue && Vue.app && Vue.app._instance && Vue.app._instance.proxy) {
|
||||||
|
const proxy = Vue.app._instance.proxy;
|
||||||
|
if (proxy.isAdmin !== undefined) isAdmin = proxy.isAdmin;
|
||||||
|
if (proxy.currentPage !== undefined) currentPage = proxy.currentPage;
|
||||||
|
if (proxy.redirectToPage) redirectToPage = proxy.redirectToPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除旧的导航容器(如果有)
|
||||||
|
const oldNav = container.querySelector('.navigation-wrapper');
|
||||||
|
if (oldNav) oldNav.remove();
|
||||||
|
|
||||||
|
const nav = createNavigation(currentPage, isAdmin, redirectToPage || ((page) => { window.location.href = page; }));
|
||||||
|
container.insertBefore(nav, container.firstChild);
|
||||||
|
console.log('[Nav] 导航已插入');
|
||||||
};
|
};
|
||||||
|
|
||||||
app.component('navigation-component', component);
|
init();
|
||||||
console.log('[Nav] 组件已注册');
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.installNavigation = installNavigation;
|
console.log('[Nav] 脚本已加载(纯DOM版)');
|
||||||
console.log('[Nav] 脚本已加载');
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user