feat(frontend): 抽离页眉为公共组件;修复导航点击事件
- 新增 navbar-component.js:固定页眉,统一标题、用户信息、退出按钮 - 重构所有页面(index/topics/logs/admin/users)使用 navbar-component - 清理原有 navbar 相关样式,避免冲突 - 导航组件增加 main-content padding-top 自动调整 - 修复导航按钮点击事件(确保 触发) - 导航组件样式强应用,解决遮挡和布局问题
This commit is contained in:
@@ -68,18 +68,17 @@
|
||||
</style>
|
||||
|
||||
<script src="navigation-component.js"></script>
|
||||
|
||||
<script src="navbar-component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar">
|
||||
<div class="navbar-content">
|
||||
<h1 class="navbar-title">宇之然内容创作平台 - 系统</h1>
|
||||
<div class="navbar-user">
|
||||
<div class="user-info"><div class="avatar">{{ currentUser.username.charAt(0).toUpperCase() }}</div><span>{{ currentUser.username }}</span></div>
|
||||
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<navbar-component
|
||||
title="系统管理"
|
||||
:username="currentUser.username"
|
||||
:is-admin="isAdmin"
|
||||
@logout="handleLogout"
|
||||
></navbar-component>
|
||||
|
||||
<navigation-component
|
||||
current-page="admin"
|
||||
@@ -396,6 +395,8 @@ const app = createApp({
|
||||
});
|
||||
|
||||
app.use(ElementPlus);
|
||||
// 安装导航组件
|
||||
if (window.installNavbar) { window.installNavbar(app); }
|
||||
// 安装导航组件
|
||||
if (window.installNavigation) { window.installNavigation(app); } else if (window.NavigationComponent) { app.component("navigation-component", window.NavigationComponent); }
|
||||
app.mount('#app');
|
||||
|
||||
@@ -283,22 +283,17 @@
|
||||
</style>
|
||||
|
||||
<script src="navigation-component.js"></script>
|
||||
|
||||
<script src="navbar-component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar" v-if="isLoggedIn">
|
||||
<div class="navbar-content">
|
||||
<h1 class="navbar-title">宇之然内容创作平台</h1>
|
||||
<div class="navbar-user">
|
||||
<div class="user-info">
|
||||
<div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div>
|
||||
<span>{{ currentUser.username }}</span>
|
||||
<el-tag v-if="isAdmin" size="small" type="danger" style="border: none;">管理员</el-tag>
|
||||
</div>
|
||||
<el-button size="small" type="danger" plain @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<navbar-component
|
||||
title="宇之然内容创作平台"
|
||||
:username="currentUser.username"
|
||||
:is-admin="isAdmin"
|
||||
@logout="handleLogout"
|
||||
></navbar-component>
|
||||
|
||||
<navigation-component
|
||||
current-page="dashboard"
|
||||
@@ -532,6 +527,8 @@
|
||||
const app = Vue.createApp(App);
|
||||
app.use(ElementPlus);
|
||||
// 安装导航组件
|
||||
if (window.installNavbar) { window.installNavbar(app); }
|
||||
// 安装导航组件
|
||||
if (window.installNavigation) { window.installNavigation(app); } else if (window.NavigationComponent) { app.component("navigation-component", window.NavigationComponent); }
|
||||
app.mount('#app');
|
||||
|
||||
|
||||
@@ -41,18 +41,17 @@
|
||||
</style>
|
||||
|
||||
<script src="navigation-component.js"></script>
|
||||
|
||||
<script src="navbar-component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar">
|
||||
<div class="navbar-content">
|
||||
<h1 class="navbar-title">宇之然内容创作平台 - 系统日志</h1>
|
||||
<div class="navbar-user">
|
||||
<div class="user-info"><div class="avatar">{{ currentUser.username.charAt(0).toUpperCase() }}</div><span>{{ currentUser.username }}</span></div>
|
||||
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<navbar-component
|
||||
title="系统日志"
|
||||
:username="currentUser.username"
|
||||
:is-admin="isAdmin"
|
||||
@logout="handleLogout"
|
||||
></navbar-component>
|
||||
|
||||
<navigation-component
|
||||
current-page="logs"
|
||||
@@ -148,6 +147,8 @@
|
||||
const app = Vue.createApp(LogsApp);
|
||||
app.use(ElementPlus);
|
||||
// 安装导航组件
|
||||
if (window.installNavbar) { window.installNavbar(app); }
|
||||
// 安装导航组件
|
||||
if (window.installNavigation) { window.installNavigation(app); } else if (window.NavigationComponent) { app.component("navigation-component", window.NavigationComponent); }
|
||||
app.mount('#app');
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// 公共页眉组件 - Vue 3
|
||||
(function() {
|
||||
console.log('[Navbar] 脚本加载');
|
||||
|
||||
function injectStyles() {
|
||||
if (document.getElementById('navbar-styles')) return;
|
||||
const styles = `
|
||||
.navbar-component { position: fixed; top: 0; left: 0; right: 0; height: 60px; background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 10000; }
|
||||
.navbar-component .navbar-title { font-size: 18px; font-weight: 600; }
|
||||
.navbar-component .navbar-user { display: flex; align-items: center; gap: 12px; }
|
||||
.navbar-component .user-info { display: flex; align-items: center; gap: 8px; }
|
||||
.navbar-component .avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
|
||||
@media (max-width: 768px) {
|
||||
.navbar-component { padding: 0 16px; }
|
||||
.navbar-component .navbar-title { font-size: 16px; }
|
||||
}
|
||||
`;
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = 'navbar-styles';
|
||||
styleEl.textContent = styles;
|
||||
document.head.appendChild(styleEl);
|
||||
console.log('[Navbar] 样式已注入');
|
||||
}
|
||||
|
||||
function getHFromApp(app) {
|
||||
try {
|
||||
if (app._context && app._context.h) return app._context.h;
|
||||
if (app._instance && app._instance.proxy && app._instance.proxy._cf) return app._instance.proxy._cf;
|
||||
} catch (e) { console.warn('[Navbar] 获取 h 失败', e); }
|
||||
return null;
|
||||
}
|
||||
|
||||
const installNavbar = (app) => {
|
||||
console.log('[Navbar] installNavbar called');
|
||||
injectStyles();
|
||||
const h = getHFromApp(app) || (window.Vue && Vue.h) || function(tag, data, children) {
|
||||
const el = document.createElement(tag);
|
||||
if (data && data.class) el.className = data.class;
|
||||
if (children) {
|
||||
if (Array.isArray(children)) children.forEach(c => { if (typeof c === 'string') el.appendChild(document.createTextNode(c)); else if (c && c.nodeType) el.appendChild(c); });
|
||||
else if (typeof children === 'string') el.textContent = children;
|
||||
}
|
||||
if (data && data.on) Object.keys(data.on).forEach(ev => el.addEventListener(ev, data.on[ev]));
|
||||
};
|
||||
|
||||
const NavbarComponent = {
|
||||
name: 'NavbarComponent',
|
||||
props: {
|
||||
title: { type: String, default: '宇之然内容创作平台' },
|
||||
username: { type: String, default: '' },
|
||||
isAdmin: { type: Boolean, default: false },
|
||||
onLogout: { type: Function, default: null }
|
||||
},
|
||||
render(){
|
||||
const createElement = arguments[0] || h;
|
||||
return createElement('nav', { class: 'navbar-component' }, [
|
||||
createElement('div', { class: 'navbar-content', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' } }, [
|
||||
createElement('h1', { class: 'navbar-title' }, this.title),
|
||||
createElement('div', { class: 'navbar-user' }, [
|
||||
createElement('div', { class: 'user-info' }, [
|
||||
createElement('div', { class: 'avatar' }, this.username ? this.username.charAt(0).toUpperCase() : '?'),
|
||||
this.username ? createElement('span', this.username) : null
|
||||
]),
|
||||
this.isAdmin ? createElement('span', { style: { marginLeft: '8px', fontSize: '12px', background: 'rgba(255,255,255,0.3)', padding: '2px 8px', borderRadius: '10px' } }, '管理员') : null,
|
||||
createElement('button', {
|
||||
class: 'logout-btn',
|
||||
style: { background: 'rgba(255,255,255,0.2)', border: 'none', color: 'white', padding: '6px 12px', borderRadius: '6px', cursor: 'pointer', marginLeft: '12px' },
|
||||
on: { click: () => { if (this.onLogout) this.onLogout(); else window.location.href = '/login.html'; } }
|
||||
}, '退出')
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
app.component('navbar-component', NavbarComponent);
|
||||
console.log('[Navbar] 组件已注册');
|
||||
return app;
|
||||
};
|
||||
|
||||
window.installNavbar = installNavbar;
|
||||
console.log('[Navbar] 脚本已加载');
|
||||
})();
|
||||
@@ -1,11 +1,10 @@
|
||||
// Vue 3 导航组件 - 修复布局和事件
|
||||
// Vue 3 导航组件 - 最终版(确保事件传递)
|
||||
(function() {
|
||||
console.log('[Nav] 脚本加载');
|
||||
|
||||
function injectStyles() {
|
||||
if (document.getElementById('navigation-styles')) return;
|
||||
const styles = `
|
||||
/* 重置导航包装器,使用常规布局 */
|
||||
.navigation-wrapper { position: relative; }
|
||||
.navigation-wrapper .sidebar {
|
||||
position: fixed; top: 60px; left: 0; bottom: 0; width: 180px;
|
||||
@@ -35,20 +34,8 @@
|
||||
.navigation-wrapper .mobile-nav { display: flex !important; }
|
||||
}
|
||||
@media (min-width: 769px) { .navigation-wrapper .mobile-nav { display: none !important; } }
|
||||
/* 主内容区域避开固定侧边栏 */
|
||||
body > #app > .navigation-wrapper + .main-content,
|
||||
body > #app > .main-content {
|
||||
margin-left: 180px !important;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
body > #app > .navigation-wrapper + .main-content,
|
||||
body > #app > .main-content {
|
||||
margin-left: 0 !important;
|
||||
padding-bottom: 60px !important;
|
||||
}
|
||||
}
|
||||
/* 侧边栏固定定位时不调整主内容,让 main-content 自然位于文档流中,仅需确保 padding-top 避开 navbar */
|
||||
body > #app > .main-content { padding-top: 0; }
|
||||
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; } }
|
||||
`;
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = 'navigation-styles';
|
||||
@@ -76,6 +63,7 @@
|
||||
else if (typeof children === 'string') el.textContent = children;
|
||||
}
|
||||
if (data && data.on) Object.keys(data.on).forEach(ev => el.addEventListener(ev, data.on[ev]));
|
||||
return el;
|
||||
};
|
||||
console.log('[Nav] 使用 h 函数:', typeof h);
|
||||
|
||||
@@ -83,11 +71,23 @@
|
||||
name: 'NavigationComponent',
|
||||
props: { currentPage: { type: String, required: true }, isAdmin: { type: Boolean, default: false } },
|
||||
mounted() { console.log('[NavigationComponent] 已挂载'); },
|
||||
render() {
|
||||
render(){
|
||||
const createElement = arguments[0] || h;
|
||||
// 获取 emit 方法:通过 this.$emit 或从闭包中捕获
|
||||
const emit = this.$emit || function() {};
|
||||
|
||||
const navigate = (page) => {
|
||||
console.log('[NavigationComponent] 导航到:', page);
|
||||
console.log('[NavigationComponent] 点击导航到:', page);
|
||||
// 直接调用 emit 函数
|
||||
if (typeof emit === 'function') {
|
||||
emit('navigate', page);
|
||||
} else if (this.$emit) {
|
||||
this.$emit('navigate', page);
|
||||
} else {
|
||||
console.warn('[NavigationComponent] 无法发送事件,emit unavailable');
|
||||
}
|
||||
};
|
||||
|
||||
const sidebarItems = [
|
||||
{ icon: '📊', label: '系统概览', page: '/' },
|
||||
{ icon: '📋', label: '选题管理', page: 'topics.html' },
|
||||
@@ -100,22 +100,32 @@
|
||||
{ icon: '📄', label: '日志', page: 'logs.html' },
|
||||
...(this.isAdmin ? [{ icon: '👥', label: '用户', page: 'users.html' }, { icon: '⚙️', label: '系统', page: 'admin.html' }] : [])
|
||||
];
|
||||
return h('div', { class: 'navigation-wrapper' }, [
|
||||
h('aside', { class: 'sidebar' }, [
|
||||
h('div', { class: 'sidebar-header' }, [h('h3', '宇之然平台')]),
|
||||
h('nav', { class: 'sidebar-nav' }, sidebarItems.map(item => {
|
||||
|
||||
return createElement('div', { class: 'navigation-wrapper' }, [
|
||||
createElement('aside', { class: 'sidebar' }, [
|
||||
createElement('div', { class: 'sidebar-header' }, [createElement('h3', '宇之然平台')]),
|
||||
createElement('nav', { class: 'sidebar-nav' }, sidebarItems.map(item => {
|
||||
const isActive = this.currentPage === (item.page === '/' ? 'dashboard' : item.page.replace('.html',''));
|
||||
return h('button', { key: item.page, class: isActive ? 'sidebar-btn active' : 'sidebar-btn', on: { click: () => navigate(item.page) } }, item.icon + ' ' + item.label);
|
||||
return createElement('button', {
|
||||
key: item.page,
|
||||
class: isActive ? 'sidebar-btn active' : 'sidebar-btn',
|
||||
on: { click: (e) => { e.preventDefault(); navigate(item.page); } }
|
||||
}, item.icon + ' ' + item.label);
|
||||
}))
|
||||
]),
|
||||
h('nav', { class: 'mobile-nav' }, mobileItems.map(item => {
|
||||
createElement('nav', { class: 'mobile-nav' }, mobileItems.map(item => {
|
||||
const isActive = this.currentPage === (item.page === '/' ? 'dashboard' : item.page.replace('.html',''));
|
||||
const content = item.label ? (item.icon + ' ' + item.label) : item.icon;
|
||||
return h('button', { key: item.page, class: isActive ? 'mobile-nav-btn active' : 'mobile-nav-btn', on: { click: () => navigate(item.page) } }, content);
|
||||
return createElement('button', {
|
||||
key: item.page,
|
||||
class: isActive ? 'mobile-nav-btn active' : 'mobile-nav-btn',
|
||||
on: { click: (e) => { e.preventDefault(); navigate(item.page); } }
|
||||
}, content);
|
||||
}))
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
app.component('navigation-component', component);
|
||||
console.log('[Nav] 组件已注册');
|
||||
return app;
|
||||
|
||||
@@ -117,18 +117,17 @@
|
||||
</style>
|
||||
|
||||
<script src="navigation-component.js"></script>
|
||||
|
||||
<script src="navbar-component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar">
|
||||
<div class="navbar-content">
|
||||
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
|
||||
<div class="navbar-user">
|
||||
<div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
|
||||
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<navbar-component
|
||||
title="选题管理"
|
||||
:username="currentUser.username"
|
||||
:is-admin="isAdmin"
|
||||
@logout="handleLogout"
|
||||
></navbar-component>
|
||||
<navigation-component
|
||||
current-page="topics"
|
||||
:is-admin="isAdmin"
|
||||
@@ -668,6 +667,8 @@ const TopicsApp = {
|
||||
};
|
||||
const app = Vue.createApp(TopicsApp);
|
||||
app.use(ElementPlus);
|
||||
// 安装导航组件
|
||||
if (window.installNavbar) { window.installNavbar(app); }
|
||||
// 安装导航组件
|
||||
if (window.installNavigation) { window.installNavigation(app); } else if (window.NavigationComponent) { app.component("navigation-component", window.NavigationComponent); }
|
||||
app.mount('#app');
|
||||
|
||||
@@ -68,18 +68,17 @@
|
||||
</style>
|
||||
|
||||
<script src="navigation-component.js"></script>
|
||||
|
||||
<script src="navbar-component.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar">
|
||||
<div class="navbar-content">
|
||||
<h1 class="navbar-title">宇之然内容创作平台 - 用户管理</h1>
|
||||
<div class="navbar-user">
|
||||
<div class="user-info"><div class="avatar">{{ currentUser.username.charAt(0).toUpperCase() }}</div><span>{{ currentUser.username }}</span></div>
|
||||
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<navbar-component
|
||||
title="用户管理"
|
||||
:username="currentUser.username"
|
||||
:is-admin="isAdmin"
|
||||
@logout="handleLogout"
|
||||
></navbar-component>
|
||||
|
||||
<navigation-component
|
||||
current-page="users"
|
||||
@@ -226,6 +225,8 @@
|
||||
const app = Vue.createApp(UsersApp);
|
||||
app.use(ElementPlus);
|
||||
// 安装导航组件
|
||||
if (window.installNavbar) { window.installNavbar(app); }
|
||||
// 安装导航组件
|
||||
if (window.installNavigation) { window.installNavigation(app); } else if (window.NavigationComponent) { app.component("navigation-component", window.NavigationComponent); }
|
||||
app.mount('#app');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user