路由更新
This commit is contained in:
@@ -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
|
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()),
|
('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, 'BasicLayout', '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, 'BasicLayout', '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, 'BasicLayout', '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, 'BasicLayout', '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, 'BasicLayout', '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, 'BasicLayout', '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()),
|
('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, 'BasicLayout', '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, 'BasicLayout', '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()),
|
('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, 'BasicLayout', '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, 'BasicLayout', '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, 'BasicLayout', '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()),
|
('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, 'BasicLayout', '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, 'BasicLayout', '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
|
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
|
||||||
|
|||||||
357
schoolNewsWeb/src/components/base/FloatingSidebar.vue
Normal file
357
schoolNewsWeb/src/components/base/FloatingSidebar.vue
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
<template>
|
||||||
|
<aside class="floating-sidebar" :class="{ collapsed: collapsed, [type]: true }">
|
||||||
|
<!-- 折叠按钮 -->
|
||||||
|
<div class="sidebar-toggle-btn" @click="$emit('toggle')">
|
||||||
|
<i class="toggle-icon">{{ collapsed ? '▶' : '◀' }}</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 侧边栏内容 -->
|
||||||
|
<div class="sidebar-content" v-if="!collapsed">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<div class="sidebar-header" v-if="title">
|
||||||
|
<h3 class="sidebar-title">{{ title }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 菜单列表 -->
|
||||||
|
<nav class="sidebar-nav">
|
||||||
|
<div
|
||||||
|
v-for="menu in menus"
|
||||||
|
:key="menu.menuID"
|
||||||
|
class="sidebar-item"
|
||||||
|
:class="{ active: isActive(menu), 'has-children': hasChildren(menu) }"
|
||||||
|
>
|
||||||
|
<div class="sidebar-link" @click="handleClick(menu)">
|
||||||
|
<span class="link-text">{{ menu.name }}</span>
|
||||||
|
<i v-if="hasChildren(menu)" class="arrow-icon">▼</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 子菜单 -->
|
||||||
|
<div v-if="hasChildren(menu) && isExpanded(menu)" class="sidebar-submenu">
|
||||||
|
<div
|
||||||
|
v-for="child in menu.children"
|
||||||
|
:key="child.menuID"
|
||||||
|
class="submenu-item"
|
||||||
|
:class="{ active: isActive(child) }"
|
||||||
|
@click="handleClick(child)"
|
||||||
|
>
|
||||||
|
<span class="submenu-text">{{ child.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 折叠状态的图标 -->
|
||||||
|
<div class="sidebar-icons" v-else>
|
||||||
|
<div
|
||||||
|
v-for="menu in menus"
|
||||||
|
:key="menu.menuID"
|
||||||
|
class="icon-item"
|
||||||
|
:class="{ active: isActive(menu) }"
|
||||||
|
:title="menu.name"
|
||||||
|
@click="handleClick(menu)"
|
||||||
|
>
|
||||||
|
<span class="icon-text">{{ menu.name?.charAt(0) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import type { SysMenu } from '@/types';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
menus: SysMenu[];
|
||||||
|
collapsed?: boolean;
|
||||||
|
title?: string;
|
||||||
|
type?: 'nav' | 'sidebar';
|
||||||
|
activePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
collapsed: false,
|
||||||
|
type: 'sidebar'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'toggle': [];
|
||||||
|
'menu-click': [menu: SysMenu];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 展开的菜单ID列表
|
||||||
|
const expandedMenus = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// 检查菜单是否有子菜单
|
||||||
|
function hasChildren(menu: SysMenu): boolean {
|
||||||
|
return !!(menu.children && menu.children.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查菜单是否激活
|
||||||
|
function isActive(menu: SysMenu): boolean {
|
||||||
|
if (!menu.url) return false;
|
||||||
|
return props.activePath === menu.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查菜单是否展开
|
||||||
|
function isExpanded(menu: SysMenu): boolean {
|
||||||
|
return menu.menuID ? expandedMenus.value.has(menu.menuID) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理点击
|
||||||
|
function handleClick(menu: SysMenu) {
|
||||||
|
if (hasChildren(menu)) {
|
||||||
|
// 切换展开状态
|
||||||
|
if (menu.menuID) {
|
||||||
|
if (expandedMenus.value.has(menu.menuID)) {
|
||||||
|
expandedMenus.value.delete(menu.menuID);
|
||||||
|
} else {
|
||||||
|
expandedMenus.value.add(menu.menuID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发点击事件
|
||||||
|
emit('menu-click', menu);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.floating-sidebar {
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.sidebar {
|
||||||
|
width: 260px;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nav {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 48px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f0f2f5;
|
||||||
|
border-color: #C62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
padding: 20px 16px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.sidebar-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #141F38;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
/* 滚动条样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-item {
|
||||||
|
margin: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #fff1f0;
|
||||||
|
|
||||||
|
.sidebar-link {
|
||||||
|
color: #C62828;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.link-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #C62828;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-submenu {
|
||||||
|
background: #fafafa;
|
||||||
|
margin: 0 8px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
padding: 10px 16px 10px 32px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #C62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #fff1f0;
|
||||||
|
color: #C62828;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-icons {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 8px;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-item {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #C62828;
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.floating-sidebar {
|
||||||
|
&.sidebar {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nav {
|
||||||
|
width: 180px;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
<!-- Logo区域 -->
|
<!-- Logo区域 -->
|
||||||
<div class="nav-logo">
|
<div class="nav-logo">
|
||||||
<img src="@/assets/imgs/logo-icon.svg" alt="Logo" class="logo-icon" />
|
<img src="../../assets/imgs/logo-icon.svg" alt="Logo" class="logo-icon" />
|
||||||
<span class="logo-text">红色思政学习平台</span>
|
<span class="logo-text">红色思政学习平台</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -23,13 +23,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 下拉菜单 -->
|
<!-- 下拉菜单 -->
|
||||||
<Teleport to="body">
|
<Teleport to="body" v-if="hasNavigationChildren(menu)">
|
||||||
<div
|
<div
|
||||||
v-if="hasNavigationChildren(menu)"
|
|
||||||
class="dropdown-menu"
|
class="dropdown-menu"
|
||||||
:class="{ show: activeDropdown === menu.menuID }"
|
:class="{ show: activeDropdown === menu.menuID }"
|
||||||
:style="getDropdownPosition(menu)"
|
:style="getDropdownPosition(menu)"
|
||||||
@mouseenter="handleMouseEnter(menu)"
|
@mouseenter="() => { if (menu.menuID) activeDropdown = menu.menuID }"
|
||||||
@mouseleave="handleMouseLeave"
|
@mouseleave="handleMouseLeave"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -58,7 +57,7 @@
|
|||||||
@keyup.enter="handleSearch"
|
@keyup.enter="handleSearch"
|
||||||
/>
|
/>
|
||||||
<div class="search-icon">
|
<div class="search-icon">
|
||||||
<img src="@/assets/imgs/search-icon.svg" alt="搜索" />
|
<img src="../../assets/imgs/search-icon.svg" alt="搜索" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,9 +151,11 @@ function handleMouseEnter(menu: SysMenu, event?: MouseEvent) {
|
|||||||
activeDropdown.value = menu.menuID || null;
|
activeDropdown.value = menu.menuID || null;
|
||||||
|
|
||||||
// 计算下拉菜单位置
|
// 计算下拉菜单位置
|
||||||
const target = event?.currentTarget as HTMLElement;
|
if (event && menu.menuID) {
|
||||||
if (target && menu.menuID) {
|
const target = event.currentTarget as HTMLElement;
|
||||||
const rect = target.getBoundingClientRect();
|
const navLink = target.querySelector('.nav-link') as HTMLElement;
|
||||||
|
const rect = (navLink || target).getBoundingClientRect();
|
||||||
|
|
||||||
dropdownPositions.value[menu.menuID] = {
|
dropdownPositions.value[menu.menuID] = {
|
||||||
left: rect.left,
|
left: rect.left,
|
||||||
top: rect.bottom,
|
top: rect.bottom,
|
||||||
@@ -167,11 +168,15 @@ function handleMouseEnter(menu: SysMenu, event?: MouseEvent) {
|
|||||||
// 获取下拉菜单位置样式
|
// 获取下拉菜单位置样式
|
||||||
function getDropdownPosition(menu: SysMenu) {
|
function getDropdownPosition(menu: SysMenu) {
|
||||||
const menuID = menu.menuID;
|
const menuID = menu.menuID;
|
||||||
if (!menuID || !dropdownPositions.value[menuID]) {
|
const pos = menuID && dropdownPositions.value[menuID];
|
||||||
return {};
|
|
||||||
|
if (!pos) {
|
||||||
|
return {
|
||||||
|
position: 'fixed' as const,
|
||||||
|
visibility: 'hidden' as const
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const pos = dropdownPositions.value[menuID];
|
|
||||||
return {
|
return {
|
||||||
position: 'fixed' as const,
|
position: 'fixed' as const,
|
||||||
left: `${pos.left}px`,
|
left: `${pos.left}px`,
|
||||||
@@ -356,15 +361,16 @@ function handleLogout() {
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transform: translateY(-10px);
|
transform: scaleY(0);
|
||||||
transition: all 0.3s;
|
transform-origin: top center;
|
||||||
|
transition: opacity 0.2s, transform 0.2s, visibility 0.2s;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&.show {
|
&.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
transform: translateY(0);
|
transform: scaleY(1);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6
schoolNewsWeb/src/components/base/index.ts
Normal file
6
schoolNewsWeb/src/components/base/index.ts
Normal file
@@ -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';
|
||||||
3
schoolNewsWeb/src/components/index.ts
Normal file
3
schoolNewsWeb/src/components/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// 导出 base 基础组件
|
||||||
|
export * from './base';
|
||||||
|
|
||||||
@@ -57,12 +57,7 @@ import { useRoute, useRouter } from "vue-router";
|
|||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import type { SysMenu } from "@/types";
|
import type { SysMenu } from "@/types";
|
||||||
import { getMenuPath } from "@/utils/route-generator";
|
import { getMenuPath } from "@/utils/route-generator";
|
||||||
// @ts-ignore - Vue 3.5 defineOptions支持
|
import { MenuNav, Breadcrumb, UserDropdown } from "@/components/base";
|
||||||
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";
|
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const sidebarCollapsed = ref(false);
|
const sidebarCollapsed = ref(false);
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
<div class="layout-content">
|
<div class="layout-content">
|
||||||
<!-- 面包屑 -->
|
<!-- 面包屑 -->
|
||||||
<div class="breadcrumb-wrapper" v-if="breadcrumbItems.length > 0">
|
<!-- <div class="breadcrumb-wrapper" v-if="breadcrumbItems.length > 0">
|
||||||
<Breadcrumb :items="breadcrumbItems" />
|
<Breadcrumb :items="breadcrumbItems" />
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 侧边栏和内容 -->
|
<!-- 侧边栏和内容 -->
|
||||||
<div class="content-wrapper" v-if="hasSidebarMenus">
|
<div class="content-wrapper" v-if="hasSidebarMenus">
|
||||||
@@ -50,12 +50,7 @@ import { useStore } from 'vuex';
|
|||||||
import type { SysMenu } from '@/types';
|
import type { SysMenu } from '@/types';
|
||||||
import { MenuType } from '@/types/enums';
|
import { MenuType } from '@/types/enums';
|
||||||
import { getMenuPath } from '@/utils/route-generator';
|
import { getMenuPath } from '@/utils/route-generator';
|
||||||
// @ts-ignore - Vue 3.5 组件导入兼容性
|
import { TopNavigation, MenuNav, Breadcrumb } from '@/components';
|
||||||
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';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -57,23 +57,23 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 首页(显示在导航栏)
|
// 首页(显示在导航栏)
|
||||||
{
|
// {
|
||||||
path: "/home",
|
// path: "/home",
|
||||||
component: () => import("@/layouts/NavigationLayout.vue"),
|
// component: () => import("@/layouts/NavigationLayout.vue"),
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
path: "",
|
// path: "",
|
||||||
name: "Home",
|
// name: "Home",
|
||||||
component: () => import("@/views/HomeView.vue"),
|
// component: () => import("@/views/HomeView.vue"),
|
||||||
meta: {
|
// meta: {
|
||||||
title: "首页",
|
// title: "首页",
|
||||||
requiresAuth: false,
|
// requiresAuth: false,
|
||||||
menuType: 1, // NAVIGATION 类型,显示在顶部导航栏
|
// menuType: 1, // NAVIGATION 类型,显示在顶部导航栏
|
||||||
orderNum: -1, // 排在动态路由之前
|
// orderNum: -1, // 排在动态路由之前
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
// 错误页面
|
// 错误页面
|
||||||
{
|
{
|
||||||
path: "/403",
|
path: "/403",
|
||||||
|
|||||||
@@ -81,16 +81,16 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果有子菜单,使用布局组件
|
// 检查是否指定了布局(只有顶层菜单才使用布局)
|
||||||
if (menu.children && menu.children.length > 0) {
|
const layout = isTopLevel ? (menu as any).layout : null;
|
||||||
// 根据layout字段选择布局
|
const hasChildren = menu.children && menu.children.length > 0;
|
||||||
const layout = (menu as any).layout || menu.component;
|
|
||||||
|
|
||||||
if (layout) {
|
// 确定路由组件
|
||||||
// 如果指定了layout,使用指定的布局
|
if (layout && LAYOUT_MAP[layout]) {
|
||||||
|
// 如果指定了布局,使用指定的布局
|
||||||
route.component = getComponent(layout);
|
route.component = getComponent(layout);
|
||||||
} else {
|
} else if (hasChildren && isTopLevel) {
|
||||||
// 根据菜单类型选择默认布局
|
// 如果有子菜单但没有指定布局,根据菜单类型选择默认布局
|
||||||
if (isTopLevel && menu.type === MenuType.NAVIGATION) {
|
if (isTopLevel && menu.type === MenuType.NAVIGATION) {
|
||||||
route.component = getComponent('NavigationLayout');
|
route.component = getComponent('NavigationLayout');
|
||||||
} else if (menu.type === MenuType.SIDEBAR) {
|
} else if (menu.type === MenuType.SIDEBAR) {
|
||||||
@@ -98,21 +98,29 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw
|
|||||||
} else {
|
} else {
|
||||||
route.component = getComponent('BasicLayout');
|
route.component = getComponent('BasicLayout');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 没有子菜单,使用具体的页面组件
|
// 没有子菜单,也没有指定布局,使用具体的页面组件
|
||||||
if (menu.component) {
|
if (menu.component) {
|
||||||
route.component = getComponent(menu.component);
|
route.component = getComponent(menu.component);
|
||||||
} else {
|
} else {
|
||||||
// 如果没有指定组件,使用BlankLayout作为默认
|
// 非顶层菜单没有组件时,使用简单的占位组件
|
||||||
route.component = getComponent('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 = [];
|
route.children = [];
|
||||||
menu.children.forEach(child => {
|
menu.children!.forEach(child => {
|
||||||
const childRoute = generateRouteFromMenu(child, false);
|
const childRoute = generateRouteFromMenu(child, false);
|
||||||
if (childRoute) {
|
if (childRoute) {
|
||||||
route.children!.push(childRoute);
|
route.children!.push(childRoute);
|
||||||
@@ -121,7 +129,7 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw
|
|||||||
|
|
||||||
// 如果没有设置重定向,自动重定向到第一个有URL的子菜单
|
// 如果没有设置重定向,自动重定向到第一个有URL的子菜单
|
||||||
if (!route.redirect && route.children.length > 0) {
|
if (!route.redirect && route.children.length > 0) {
|
||||||
const firstChildWithUrl = findFirstMenuWithUrl(menu.children);
|
const firstChildWithUrl = findFirstMenuWithUrl(menu.children!);
|
||||||
if (firstChildWithUrl?.url) {
|
if (firstChildWithUrl?.url) {
|
||||||
route.redirect = firstChildWithUrl.url;
|
route.redirect = firstChildWithUrl.url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,472 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="home-page">
|
|
||||||
|
|
||||||
<!-- 主横幅 -->
|
|
||||||
<section class="hero-section">
|
|
||||||
<div class="container">
|
|
||||||
<div class="hero-content">
|
|
||||||
<h1 class="hero-title">校园新闻管理系统</h1>
|
|
||||||
<p class="hero-subtitle">及时发布、高效管理、便捷浏览</p>
|
|
||||||
<div class="hero-actions">
|
|
||||||
<el-button type="primary" size="large" @click="exploreNews">
|
|
||||||
浏览新闻
|
|
||||||
</el-button>
|
|
||||||
<el-button size="large" @click="goToLogin" v-if="!isLoggedIn">
|
|
||||||
开始使用
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 功能特性 -->
|
|
||||||
<section class="features-section">
|
|
||||||
<div class="container">
|
|
||||||
<h2 class="section-title">核心功能</h2>
|
|
||||||
<div class="features-grid">
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">📰</div>
|
|
||||||
<h3>新闻发布</h3>
|
|
||||||
<p>快速发布校园新闻,支持富文本编辑,图文并茂</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">🔐</div>
|
|
||||||
<h3>权限管理</h3>
|
|
||||||
<p>细粒度权限控制,安全可靠的用户管理体系</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">📊</div>
|
|
||||||
<h3>数据统计</h3>
|
|
||||||
<p>实时统计新闻浏览量,数据可视化展示</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">💬</div>
|
|
||||||
<h3>评论互动</h3>
|
|
||||||
<p>支持新闻评论,增强师生互动交流</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">🔍</div>
|
|
||||||
<h3>智能搜索</h3>
|
|
||||||
<p>全文搜索,快速定位所需新闻内容</p>
|
|
||||||
</div>
|
|
||||||
<div class="feature-card">
|
|
||||||
<div class="feature-icon">📱</div>
|
|
||||||
<h3>响应式设计</h3>
|
|
||||||
<p>完美适配各种设备,随时随地浏览</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 最新新闻 -->
|
|
||||||
<section class="news-section">
|
|
||||||
<div class="container">
|
|
||||||
<h2 class="section-title">最新新闻</h2>
|
|
||||||
<div class="news-grid">
|
|
||||||
<div class="news-card" v-for="item in latestNews" :key="item.id">
|
|
||||||
<div class="news-image">
|
|
||||||
<img :src="item.image" :alt="item.title" />
|
|
||||||
</div>
|
|
||||||
<div class="news-content">
|
|
||||||
<span class="news-category">{{ item.category }}</span>
|
|
||||||
<h3 class="news-title">{{ item.title }}</h3>
|
|
||||||
<p class="news-excerpt">{{ item.excerpt }}</p>
|
|
||||||
<div class="news-meta">
|
|
||||||
<span class="news-date">{{ item.date }}</span>
|
|
||||||
<span class="news-views">👁️ {{ item.views }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="news-more">
|
|
||||||
<el-button @click="exploreNews">查看更多新闻</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 页脚 -->
|
|
||||||
<footer class="home-footer">
|
|
||||||
<div class="container">
|
|
||||||
<div class="footer-content">
|
|
||||||
<div class="footer-section">
|
|
||||||
<h4>关于我们</h4>
|
|
||||||
<p>校园新闻管理系统致力于为学校提供高效、便捷的新闻发布和管理平台。</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-section">
|
|
||||||
<h4>快速链接</h4>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#home">首页</a></li>
|
|
||||||
<li><a href="#news">新闻中心</a></li>
|
|
||||||
<li><a href="#about">关于我们</a></li>
|
|
||||||
<li><a href="#contact">联系我们</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="footer-section">
|
|
||||||
<h4>联系方式</h4>
|
|
||||||
<ul>
|
|
||||||
<li>📧 Email: info@school-news.com</li>
|
|
||||||
<li>📞 电话: 123-456-7890</li>
|
|
||||||
<li>📍 地址: XX市XX区XX路XX号</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="footer-bottom">
|
|
||||||
<p>© 2025 校园新闻管理系统. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useStore } from 'vuex';
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
// 计算属性
|
|
||||||
const isLoggedIn = computed(() => store.getters['auth/isAuthenticated']);
|
|
||||||
const userName = computed(() => {
|
|
||||||
const userInfo = store.getters['auth/userInfo'];
|
|
||||||
return userInfo?.userName || userInfo?.nickName || '用户';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最新新闻数据(示例)
|
|
||||||
const latestNews = ref([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: '我校在全国大学生创新创业大赛中获得金奖',
|
|
||||||
excerpt: '在刚刚结束的第十届全国大学生创新创业大赛中,我校代表队凭借优异的表现,荣获金奖...',
|
|
||||||
category: '校园动态',
|
|
||||||
image: 'https://picsum.photos/400/250?random=1',
|
|
||||||
date: '2025-10-08',
|
|
||||||
views: 1523
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: '校园文化艺术节圆满落幕',
|
|
||||||
excerpt: '历时一周的校园文化艺术节于昨日圆满落幕,本次艺术节共举办了20余场精彩活动...',
|
|
||||||
category: '文化活动',
|
|
||||||
image: 'https://picsum.photos/400/250?random=2',
|
|
||||||
date: '2025-10-07',
|
|
||||||
views: 2341
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: '学校图书馆新增电子资源数据库',
|
|
||||||
excerpt: '为了更好地服务师生科研和学习,学校图书馆新增了多个电子资源数据库...',
|
|
||||||
category: '通知公告',
|
|
||||||
image: 'https://picsum.photos/400/250?random=3',
|
|
||||||
date: '2025-10-06',
|
|
||||||
views: 987
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
function goToLogin() {
|
|
||||||
router.push('/login');
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToRegister() {
|
|
||||||
router.push('/register');
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToDashboard() {
|
|
||||||
router.push('/dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleLogout() {
|
|
||||||
try {
|
|
||||||
await store.dispatch('auth/logout');
|
|
||||||
ElMessage.success('已退出登录');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('退出失败:', error);
|
|
||||||
ElMessage.error('退出失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function exploreNews() {
|
|
||||||
// TODO: 跳转到新闻列表页
|
|
||||||
ElMessage.info('新闻列表功能开发中...');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载
|
|
||||||
onMounted(() => {
|
|
||||||
// 可以在这里加载真实的新闻数据
|
|
||||||
console.log('Home page mounted');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.home-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主横幅 */
|
|
||||||
.hero-section {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 100px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 0 0 40px 0;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 功能特性 */
|
|
||||||
.features-section {
|
|
||||||
padding: 80px 0;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 36px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0 0 60px 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.features-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card {
|
|
||||||
text-align: center;
|
|
||||||
padding: 32px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: transform 0.3s, box-shadow 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-8px);
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-icon {
|
|
||||||
font-size: 48px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 20px;
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 最新新闻 */
|
|
||||||
.news-section {
|
|
||||||
padding: 80px 0;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
||||||
gap: 32px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: transform 0.3s, box-shadow 0.3s;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover img {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-category {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px 12px;
|
|
||||||
background: #e6f7ff;
|
|
||||||
color: #1890ff;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-excerpt {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-meta {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-more {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 页脚 */
|
|
||||||
.home-footer {
|
|
||||||
background: #001529;
|
|
||||||
color: white;
|
|
||||||
padding: 60px 0 20px 0;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 40px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-section {
|
|
||||||
h4 {
|
|
||||||
font-size: 18px;
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-bottom {
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式设计 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.nav-menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.features-grid,
|
|
||||||
.news-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user