diff --git a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql
index ccc13f8..7efb893 100644
--- a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql
+++ b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql
@@ -60,33 +60,34 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
-- 插入前端菜单数据
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
+('100', 'menu_home', '首页', NULL, '/home', 'home/HomeView', 'el-icon-house', 1, 1, 'NavigationLayout', '1', now()),
-- 资源中心
-('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'resource-center/ResourceCenterView', 'el-icon-folder-opened', 2, 1, 'BasicLayout', '1', now()),
-('201', 'menu_party_history', '党史学习', 'menu_resource_center', '/resource-center/party-history', 'resource-center/PartyHistoryView', 'el-icon-trophy', 1, 1, 'BasicLayout', '1', now()),
-('202', 'menu_leader_speech', '领导讲话', 'menu_resource_center', '/resource-center/leader-speech', 'resource-center/LeaderSpeechView', 'el-icon-microphone', 2, 1, 'BasicLayout', '1', now()),
-('203', 'menu_policy_interpretation', '政策解读', 'menu_resource_center', '/resource-center/policy-interpretation', 'resource-center/PolicyInterpretationView', 'el-icon-document', 3, 1, 'BasicLayout', '1', now()),
-('204', 'menu_red_classic', '红色经典', 'menu_resource_center', '/resource-center/red-classic', 'resource-center/RedClassicView', 'el-icon-star-on', 4, 1, 'BasicLayout', '1', now()),
-('205', 'menu_special_report', '专题报告', 'menu_resource_center', '/resource-center/special-report', 'resource-center/SpecialReportView', 'el-icon-document-copy', 5, 1, 'BasicLayout', '1', now()),
-('206', 'menu_world_case', '思政案例', 'menu_resource_center', '/resource-center/world-case', 'resource-center/WorldCaseView', 'el-icon-collection', 6, 1, 'BasicLayout', '1', now()),
+('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'resource-center/ResourceCenterView', 'el-icon-folder-opened', 2, 1, 'NavigationLayout', '1', now()),
+('201', 'menu_party_history', '党史学习', 'menu_resource_center', '/resource-center/party-history', 'resource-center/PartyHistoryView', 'el-icon-trophy', 1, 1, 'NavigationLayout', '1', now()),
+('202', 'menu_leader_speech', '领导讲话', 'menu_resource_center', '/resource-center/leader-speech', 'resource-center/LeaderSpeechView', 'el-icon-microphone', 2, 1, 'NavigationLayout', '1', now()),
+('203', 'menu_policy_interpretation', '政策解读', 'menu_resource_center', '/resource-center/policy-interpretation', 'resource-center/PolicyInterpretationView', 'el-icon-document', 3, 1, 'NavigationLayout', '1', now()),
+('204', 'menu_red_classic', '红色经典', 'menu_resource_center', '/resource-center/red-classic', 'resource-center/RedClassicView', 'el-icon-star-on', 4, 1, 'NavigationLayout', '1', now()),
+('205', 'menu_special_report', '专题报告', 'menu_resource_center', '/resource-center/special-report', 'resource-center/SpecialReportView', 'el-icon-document-copy', 5, 1, 'NavigationLayout', '1', now()),
+('206', 'menu_world_case', '思政案例', 'menu_resource_center', '/resource-center/world-case', 'resource-center/WorldCaseView', 'el-icon-collection', 6, 1, 'NavigationLayout', '1', now()),
-- 学习计划
-('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'study-plan/StudyPlanView', 'el-icon-reading', 3, 1, 'BasicLayout', '1', now()),
-('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'study-plan/StudyTasksView', 'el-icon-s-order', 1, 1, 'BasicLayout', '1', now()),
-('302', 'menu_course_center', '课程中心', 'menu_study_plan', '/study-plan/course', 'study-plan/CourseCenterView', 'el-icon-video-play', 2, 1, 'BasicLayout', '1', now()),
+('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'study-plan/StudyPlanView', 'el-icon-reading', 3, 1, 'NavigationLayout', '1', now()),
+('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'study-plan/StudyTasksView', 'el-icon-s-order', 1, 1, 'NavigationLayout', '1', now()),
+('302', 'menu_course_center', '课程中心', 'menu_study_plan', '/study-plan/course', 'study-plan/CourseCenterView', 'el-icon-video-play', 2, 1, 'NavigationLayout', '1', now()),
-- 个人中心
-('400', 'menu_user_center', '个人中心', NULL, '/user-center', 'user-center/UserCenterView', 'el-icon-user', 4, 1, 'BasicLayout', '1', now()),
-('401', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user-center/LearningRecordsView', 'el-icon-document', 1, 1, 'BasicLayout', '1', now()),
-('402', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user-center/MyFavoritesView', 'el-icon-star-on', 2, 1, 'BasicLayout', '1', now()),
-('403', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user-center/MyAchievementsView', 'el-icon-trophy', 3, 1, 'BasicLayout', '1', now()),
+('400', 'menu_user_center', '个人中心', NULL, '/user-center', 'user-center/UserCenterView', 'el-icon-user', 4, 1, 'NavigationLayout', '1', now()),
+('401', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user-center/LearningRecordsView', 'el-icon-document', 1, 1, 'NavigationLayout', '1', now()),
+('402', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user-center/MyFavoritesView', 'el-icon-star-on', 2, 1, 'NavigationLayout', '1', now()),
+('403', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user-center/MyAchievementsView', 'el-icon-trophy', 3, 1, 'NavigationLayout', '1', now()),
-- 账号中心
-('500', 'menu_profile', '账号中心', NULL, '/profile', 'profile/ProfileView', 'el-icon-user-solid', 5, 1, 'BasicLayout', '1', now()),
-('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'profile/PersonalInfoView', 'el-icon-user', 1, 1, 'BasicLayout', '1', now()),
-('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'profile/AccountSettingsView', 'el-icon-setting', 2, 1, 'BasicLayout', '1', now()),
+('500', 'menu_profile', '账号中心', NULL, '/profile', 'profile/ProfileView', 'el-icon-user-solid', 5, 1, 'NavigationLayout', '1', now()),
+('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'profile/PersonalInfoView', 'el-icon-user', 1, 1, 'NavigationLayout', '1', now()),
+('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'profile/AccountSettingsView', 'el-icon-setting', 2, 1, 'NavigationLayout', '1', now()),
-- 智能体模块
-('600', 'menu_ai_assistant', '智能体模块', NULL, '/ai-assistant', 'ai-assistant/AIAssistantView', 'el-icon-cpu', 6, 1, 'BasicLayout', '1', now());
+('600', 'menu_ai_assistant', '智能体模块', NULL, '/ai-assistant', 'ai-assistant/AIAssistantView', 'el-icon-cpu', 6, 1, 'NavigationLayout', '1', now());
-- 插入后端管理菜单数据
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
diff --git a/schoolNewsWeb/src/components/Breadcrumb.vue b/schoolNewsWeb/src/components/base/Breadcrumb.vue
similarity index 100%
rename from schoolNewsWeb/src/components/Breadcrumb.vue
rename to schoolNewsWeb/src/components/base/Breadcrumb.vue
diff --git a/schoolNewsWeb/src/components/base/FloatingSidebar.vue b/schoolNewsWeb/src/components/base/FloatingSidebar.vue
new file mode 100644
index 0000000..dc03c42
--- /dev/null
+++ b/schoolNewsWeb/src/components/base/FloatingSidebar.vue
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+
diff --git a/schoolNewsWeb/src/components/MenuItem.vue b/schoolNewsWeb/src/components/base/MenuItem.vue
similarity index 100%
rename from schoolNewsWeb/src/components/MenuItem.vue
rename to schoolNewsWeb/src/components/base/MenuItem.vue
diff --git a/schoolNewsWeb/src/components/MenuNav.vue b/schoolNewsWeb/src/components/base/MenuNav.vue
similarity index 100%
rename from schoolNewsWeb/src/components/MenuNav.vue
rename to schoolNewsWeb/src/components/base/MenuNav.vue
diff --git a/schoolNewsWeb/src/components/TopNavigation.vue b/schoolNewsWeb/src/components/base/TopNavigation.vue
similarity index 92%
rename from schoolNewsWeb/src/components/TopNavigation.vue
rename to schoolNewsWeb/src/components/base/TopNavigation.vue
index a359745..8053452 100644
--- a/schoolNewsWeb/src/components/TopNavigation.vue
+++ b/schoolNewsWeb/src/components/base/TopNavigation.vue
@@ -3,7 +3,7 @@
-

+
红色思政学习平台
@@ -23,13 +23,12 @@
-
+
@@ -152,9 +151,11 @@ function handleMouseEnter(menu: SysMenu, event?: MouseEvent) {
activeDropdown.value = menu.menuID || null;
// 计算下拉菜单位置
- const target = event?.currentTarget as HTMLElement;
- if (target && menu.menuID) {
- const rect = target.getBoundingClientRect();
+ if (event && menu.menuID) {
+ const target = event.currentTarget as HTMLElement;
+ const navLink = target.querySelector('.nav-link') as HTMLElement;
+ const rect = (navLink || target).getBoundingClientRect();
+
dropdownPositions.value[menu.menuID] = {
left: rect.left,
top: rect.bottom,
@@ -167,11 +168,15 @@ function handleMouseEnter(menu: SysMenu, event?: MouseEvent) {
// 获取下拉菜单位置样式
function getDropdownPosition(menu: SysMenu) {
const menuID = menu.menuID;
- if (!menuID || !dropdownPositions.value[menuID]) {
- return {};
+ const pos = menuID && dropdownPositions.value[menuID];
+
+ if (!pos) {
+ return {
+ position: 'fixed' as const,
+ visibility: 'hidden' as const
+ };
}
- const pos = dropdownPositions.value[menuID];
return {
position: 'fixed' as const,
left: `${pos.left}px`,
@@ -356,15 +361,16 @@ function handleLogout() {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
opacity: 0;
visibility: hidden;
- transform: translateY(-10px);
- transition: all 0.3s;
+ transform: scaleY(0);
+ transform-origin: top center;
+ transition: opacity 0.2s, transform 0.2s, visibility 0.2s;
z-index: 10000;
pointer-events: none;
&.show {
opacity: 1;
visibility: visible;
- transform: translateY(0);
+ transform: scaleY(1);
pointer-events: auto;
}
}
diff --git a/schoolNewsWeb/src/components/UserDropdown.vue b/schoolNewsWeb/src/components/base/UserDropdown.vue
similarity index 100%
rename from schoolNewsWeb/src/components/UserDropdown.vue
rename to schoolNewsWeb/src/components/base/UserDropdown.vue
diff --git a/schoolNewsWeb/src/components/base/index.ts b/schoolNewsWeb/src/components/base/index.ts
new file mode 100644
index 0000000..d924712
--- /dev/null
+++ b/schoolNewsWeb/src/components/base/index.ts
@@ -0,0 +1,6 @@
+export { default as Breadcrumb } from './Breadcrumb.vue';
+export { default as FloatingSidebar } from './FloatingSidebar.vue';
+export { default as MenuItem } from './MenuItem.vue';
+export { default as MenuNav } from './MenuNav.vue';
+export { default as TopNavigation } from './TopNavigation.vue';
+export { default as UserDropdown } from './UserDropdown.vue';
\ No newline at end of file
diff --git a/schoolNewsWeb/src/components/index.ts b/schoolNewsWeb/src/components/index.ts
new file mode 100644
index 0000000..1b2ffa7
--- /dev/null
+++ b/schoolNewsWeb/src/components/index.ts
@@ -0,0 +1,3 @@
+// 导出 base 基础组件
+export * from './base';
+
diff --git a/schoolNewsWeb/src/layouts/BasicLayout.vue b/schoolNewsWeb/src/layouts/BasicLayout.vue
index 73f43eb..c97577c 100644
--- a/schoolNewsWeb/src/layouts/BasicLayout.vue
+++ b/schoolNewsWeb/src/layouts/BasicLayout.vue
@@ -57,12 +57,7 @@ import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import type { SysMenu } from "@/types";
import { getMenuPath } from "@/utils/route-generator";
-// @ts-ignore - Vue 3.5 defineOptions支持
-import MenuNav from "@/components/MenuNav.vue";
-// @ts-ignore - Vue 3.5 组件导入兼容性
-import Breadcrumb from "@/components/Breadcrumb.vue";
-// @ts-ignore - Vue 3.5 组件导入兼容性
-import UserDropdown from "@/components/UserDropdown.vue";
+import { MenuNav, Breadcrumb, UserDropdown } from "@/components/base";
// 响应式状态
const sidebarCollapsed = ref(false);
diff --git a/schoolNewsWeb/src/layouts/NavigationLayout.vue b/schoolNewsWeb/src/layouts/NavigationLayout.vue
index 7e74c5c..5418b0b 100644
--- a/schoolNewsWeb/src/layouts/NavigationLayout.vue
+++ b/schoolNewsWeb/src/layouts/NavigationLayout.vue
@@ -6,9 +6,9 @@
-
+
@@ -50,12 +50,7 @@ import { useStore } from 'vuex';
import type { SysMenu } from '@/types';
import { MenuType } from '@/types/enums';
import { getMenuPath } from '@/utils/route-generator';
-// @ts-ignore - Vue 3.5 组件导入兼容性
-import TopNavigation from '@/components/TopNavigation.vue';
-// @ts-ignore - Vue 3.5 组件导入兼容性
-import MenuNav from '@/components/MenuNav.vue';
-// @ts-ignore - Vue 3.5 组件导入兼容性
-import Breadcrumb from '@/components/Breadcrumb.vue';
+import { TopNavigation, MenuNav, Breadcrumb } from '@/components';
const route = useRoute();
const router = useRouter();
diff --git a/schoolNewsWeb/src/router/index.ts b/schoolNewsWeb/src/router/index.ts
index c8bc65b..e70bc39 100644
--- a/schoolNewsWeb/src/router/index.ts
+++ b/schoolNewsWeb/src/router/index.ts
@@ -57,23 +57,23 @@ export const routes: Array
= [
],
},
// 首页(显示在导航栏)
- {
- path: "/home",
- component: () => import("@/layouts/NavigationLayout.vue"),
- children: [
- {
- path: "",
- name: "Home",
- component: () => import("@/views/HomeView.vue"),
- meta: {
- title: "首页",
- requiresAuth: false,
- menuType: 1, // NAVIGATION 类型,显示在顶部导航栏
- orderNum: -1, // 排在动态路由之前
- },
- }
- ],
- },
+ // {
+ // path: "/home",
+ // component: () => import("@/layouts/NavigationLayout.vue"),
+ // children: [
+ // {
+ // path: "",
+ // name: "Home",
+ // component: () => import("@/views/HomeView.vue"),
+ // meta: {
+ // title: "首页",
+ // requiresAuth: false,
+ // menuType: 1, // NAVIGATION 类型,显示在顶部导航栏
+ // orderNum: -1, // 排在动态路由之前
+ // },
+ // }
+ // ],
+ // },
// 错误页面
{
path: "/403",
diff --git a/schoolNewsWeb/src/utils/route-generator.ts b/schoolNewsWeb/src/utils/route-generator.ts
index 96094fa..99cc845 100644
--- a/schoolNewsWeb/src/utils/route-generator.ts
+++ b/schoolNewsWeb/src/utils/route-generator.ts
@@ -81,38 +81,46 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw
}
};
- // 如果有子菜单,使用布局组件
- if (menu.children && menu.children.length > 0) {
- // 根据layout字段选择布局
- const layout = (menu as any).layout || menu.component;
-
- if (layout) {
- // 如果指定了layout,使用指定的布局
- route.component = getComponent(layout);
+ // 检查是否指定了布局(只有顶层菜单才使用布局)
+ const layout = isTopLevel ? (menu as any).layout : null;
+ const hasChildren = menu.children && menu.children.length > 0;
+
+ // 确定路由组件
+ if (layout && LAYOUT_MAP[layout]) {
+ // 如果指定了布局,使用指定的布局
+ route.component = getComponent(layout);
+ } else if (hasChildren && isTopLevel) {
+ // 如果有子菜单但没有指定布局,根据菜单类型选择默认布局
+ if (isTopLevel && menu.type === MenuType.NAVIGATION) {
+ route.component = getComponent('NavigationLayout');
+ } else if (menu.type === MenuType.SIDEBAR) {
+ route.component = getComponent('BlankLayout');
} else {
- // 根据菜单类型选择默认布局
- if (isTopLevel && menu.type === MenuType.NAVIGATION) {
- route.component = getComponent('NavigationLayout');
- } else if (menu.type === MenuType.SIDEBAR) {
- route.component = getComponent('BlankLayout');
- } else {
- route.component = getComponent('BasicLayout');
- }
+ route.component = getComponent('BasicLayout');
}
} else {
- // 没有子菜单,使用具体的页面组件
+ // 没有子菜单,也没有指定布局,使用具体的页面组件
if (menu.component) {
route.component = getComponent(menu.component);
} else {
- // 如果没有指定组件,使用BlankLayout作为默认
+ // 非顶层菜单没有组件时,使用简单的占位组件
route.component = getComponent('BlankLayout');
}
}
- // 处理子菜单
- if (menu.children && menu.children.length > 0) {
+ // 处理子路由
+ if (layout && LAYOUT_MAP[layout] && !hasChildren && menu.component && isTopLevel) {
+ // 如果指定了布局但没有子菜单,将页面组件作为子路由
+ route.children = [{
+ path: '',
+ name: `${menu.menuID}_page`,
+ component: getComponent(menu.component),
+ meta: route.meta
+ }];
+ } else if (hasChildren) {
+ // 处理有子菜单的情况
route.children = [];
- menu.children.forEach(child => {
+ menu.children!.forEach(child => {
const childRoute = generateRouteFromMenu(child, false);
if (childRoute) {
route.children!.push(childRoute);
@@ -121,7 +129,7 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw
// 如果没有设置重定向,自动重定向到第一个有URL的子菜单
if (!route.redirect && route.children.length > 0) {
- const firstChildWithUrl = findFirstMenuWithUrl(menu.children);
+ const firstChildWithUrl = findFirstMenuWithUrl(menu.children!);
if (firstChildWithUrl?.url) {
route.redirect = firstChildWithUrl.url;
}
diff --git a/schoolNewsWeb/src/views/HomeView.vue b/schoolNewsWeb/src/views/HomeView.vue
deleted file mode 100644
index 5dd7e6a..0000000
--- a/schoolNewsWeb/src/views/HomeView.vue
+++ /dev/null
@@ -1,472 +0,0 @@
-
-
-
-
-
-
-
-
校园新闻管理系统
-
及时发布、高效管理、便捷浏览
-
-
- 浏览新闻
-
-
- 开始使用
-
-
-
-
-
-
-
-
-
-
核心功能
-
-
-
📰
-
新闻发布
-
快速发布校园新闻,支持富文本编辑,图文并茂
-
-
-
🔐
-
权限管理
-
细粒度权限控制,安全可靠的用户管理体系
-
-
-
📊
-
数据统计
-
实时统计新闻浏览量,数据可视化展示
-
-
-
💬
-
评论互动
-
支持新闻评论,增强师生互动交流
-
-
-
🔍
-
智能搜索
-
全文搜索,快速定位所需新闻内容
-
-
-
📱
-
响应式设计
-
完美适配各种设备,随时随地浏览
-
-
-
-
-
-
-
-
-
最新新闻
-
-
-
-
![]()
-
-
-
{{ item.category }}
-
{{ item.title }}
-
{{ item.excerpt }}
-
- {{ item.date }}
- 👁️ {{ item.views }}
-
-
-
-
-
- 查看更多新闻
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/schoolNewsWeb/src/views/home/HomePage.vue b/schoolNewsWeb/src/views/home/HomeView.vue
similarity index 100%
rename from schoolNewsWeb/src/views/home/HomePage.vue
rename to schoolNewsWeb/src/views/home/HomeView.vue