Files
hzdyjs/web/src/views/index.vue
T

1293 lines
31 KiB
Vue
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.
<template>
<div class="home">
<!-- 上半部分:校园风景图片轮播 -->
<div class="carousel-section">
<el-carousel :interval="4000" type="card" height="200px" indicator-position="outside" arrow="always" class="campus-carousel">
<el-carousel-item v-for="(image, index) in campusImages" :key="index">
<div class="carousel-item">
<img :src="image.url" :alt="image.title" class="carousel-image" />
<div class="carousel-overlay">
<h3 class="carousel-title">{{ image.title }}</h3>
</div>
</div>
</el-carousel-item>
</el-carousel>
</div>
<!-- 菜单功能区域 -->
<div class="menu-modules-section">
<h2 class="section-title">
<div class="section-icon">
<img src="/src/assets/logo-img/category.svg" alt="系统功能" class="section-icon-img" />
</div>
系统功能
<span class="function-count" v-if="allMenuActions.length > 0"> ({{ menuModules.length }}个模块,{{ allMenuActions.length }}个功能) </span>
</h2>
<!-- 按模块分组显示菜单 -->
<div v-for="module in menuModules" :key="module.id" class="module-group">
<div class="module-header">
<h3 class="module-title" style="color: #000000">
<div class="module-icon-wrapper">
<img src="/src/assets/logo-img/zj.svg" alt="模块图标" class="module-icon-img" />
</div>
{{ module.title }}
<el-tag type="info" size="small" class="module-count-tag"> {{ module.menus.length }}个功能 </el-tag>
</h3>
<!-- <div class="module-description">{{ module.description }}</div> -->
</div>
<!-- 使用弹性布局替换el-row和el-col -->
<div class="module-content-flex">
<div v-for="menu in module.menus" :key="menu.id" class="menu-card-wrapper">
<el-card shadow="hover" :class="['menu-action-card', { 'disabled': !menu.available }]" @click="handleQuickAction(menu)">
<div class="menu-action">
<!-- 使用随机logo图片替换图标 -->
<div class="menu-logo-wrapper">
<img :src="menu.logoUrl" :alt="menu.title" class="menu-logo" />
</div>
<span class="action-title" :class="{ 'disabled': !menu.available }">{{ menu.title }}</span>
<!-- <span class="action-desc" :class="{ 'disabled': !menu.available }">{{ menu.description }}</span> -->
<!-- 移除层级路径显示 -->
<el-tag v-if="!menu.available" type="info" size="small" class="status-tag">开发中</el-tag>
</div>
</el-card>
</div>
</div>
</div>
<!-- 如果没有菜单数据,显示加载状态 -->
<div v-if="menuModules.length === 0" class="loading-state">
<el-empty description="正在加载菜单数据..." :image-size="100">
<template #description>
<p>正在从系统获取菜单权限数据...</p>
<p class="loading-tips">如果长时间未加载,请检查网络连接或刷新页面</p>
</template>
</el-empty>
</div>
</div>
</div>
</template>
<script setup name="Index" lang="ts">
import { ref, onMounted, computed } from 'vue';
import {
User,
Tools,
Calendar,
Service,
Phone,
Message,
Document,
DataAnalysis,
Bell,
Setting,
Monitor,
Files,
Folder
} from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus/es/components/message/index';
import { useRouter } from 'vue-router';
import { getCurrentInstance, ComponentInternalInstance } from 'vue';
// 导入权限store来获取菜单数据
import { usePermissionStore } from '@/store/modules/permission';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const permissionStore = usePermissionStore();
// 校园风景图片数据
const campusImages = [
{
url: '/assets/images/01.jpg',
title: '西溪校园正门'
},
{
url: '/assets/images/02.jpg',
title: '临安校区'
},
{
url: '/assets/images/03.jpg',
title: '临安校区'
}
];
// 菜单模块数据
interface MenuModule {
id: string;
title: string;
description: string;
color: string;
menus: MenuAction[];
}
interface MenuAction {
id: string;
title: string;
description: string;
icon: any;
color: string;
route: string;
available: boolean;
parentName?: string;
fullPath?: string; // 保留但不再显示
menuType?: string;
visible?: string;
status?: string;
moduleOrder?: number; // 新增:用于模块排序
menuOrder?: number; // 新增:用于菜单项排序
orderNum?: number; // 新增:用于存储菜单的原始排序号
logoUrl?: string; // 新增:用于存储随机logo图片路径
isExternal?: boolean; // 是否外链
}
// 菜单模块配置
const menuModules = ref<MenuModule[]>([]);
const allMenuActions = ref<MenuAction[]>([]);
// 根据icon名称获取对应的图标路径
const getIconPath = (iconName: string): string => {
// 定义可用的图标文件列表(基于logo-img目录中的实际文件)
const availableSvgs = [
'workflow',
'tree',
'tree-table',
'tag',
'system',
'study',
'row',
'rate',
'radio',
'process-definition',
'people',
'peoples',
'pdf',
// 'other',
'Notice',
'nested',
'my-task',
'my-copy',
'money',
'logininfor',
'log',
'lock',
'list',
'international',
'eye',
'empty',
'finish',
'email',
'education',
'edit',
'druid',
'download',
'date-range',
'dashboard',
'component',
'company',
'color',
'clipboard',
'checkbox',
'chart',
'category',
'button',
'build',
'404'
];
const availablePngs = [
'table',
'shopping',
'redis-list',
'post',
'my-task',
'model',
'link',
'language',
'job',
'input',
'icon',
'guide',
'form',
'finish',
'eye-open',
'experience',
'exit-fullscreen1',
'exit-fullscreen',
'excel',
'example',
'drag',
'documentation',
'dict',
'date',
'code',
'cascader',
'caret-forward',
'caret-back',
'bug',
'other',
'password'
];
// 先尝试匹配.svg文件
if (availableSvgs.includes(iconName)) {
return `/src/assets/logo-img/${iconName}.svg`;
}
// 如果.svg不存在,尝试匹配.png文件
if (availablePngs.includes(iconName)) {
return `/src/assets/logo-img/${iconName}.png`;
}
// 如果都不存在,使用默认图标
return `/src/assets/logo-img/other.png`;
};
// 图标映射
const iconMap: { [key: string]: any } = {
'user': User,
'peoples': User,
'system': Setting,
'monitor': Monitor,
'tool': Tools,
'guide': Document,
'build': Tools,
'international': Files,
'bug': Tools,
'chart': DataAnalysis,
'dashboard': Monitor,
'form': Document,
'table': Files,
'tree': Files,
'tree-table': Files,
'example': Document,
'edit': Document,
'dict': Files,
'config': Setting,
'logininfor': Monitor,
'online': Monitor,
'druid': Monitor,
'server': Monitor,
'cache': Monitor,
'gen': Tools,
'swagger': Document,
'job': Tools,
'client': Service,
'tenant': User,
'package': Files,
'post': Service,
'role': Service,
'menu': Setting,
'dept': Files,
'notice': Bell,
'log': Monitor
};
// 模块颜色配置
const moduleColors = [
'#409EFF',
'#67C23A',
'#E6A23C',
'#F56C6C',
'#909399',
'#9966CC',
'#FF6B6B',
'#4ECDC4',
'#45B7D1',
'#96CEB4',
'#FFEAA7',
'#DDA0DD'
];
// 菜单数据扁平化处理函数 - 重写以正确识别多级目录结构
const flattenMenus = (menus: any[], parentChain: string[] = [], parentPath = '', level = 0): MenuAction[] => {
const result: MenuAction[] = [];
menus.forEach((menu, menuIndex) => {
// 跳过隐藏的菜单
if (menu.hidden === true) {
console.log(`跳过隐藏菜单: ${menu.meta?.title || menu.name}`);
return;
}
// 添加调试信息,特别关注工作流菜单
if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
console.log('=== 工作流菜单处理开始 ===');
console.log('工作流菜单原始数据:', menu);
console.log('工作流菜单组件类型:', menu.component);
console.log('工作流菜单是否有子菜单:', !!menu.children);
console.log('工作流菜单子菜单数量:', menu.children?.length || 0);
console.log('工作流菜单层级:', level);
console.log('工作流菜单父路径:', parentPath);
}
// 识别外链菜单
const rawLink = menu.meta?.link || menu.path;
const isExternalLink = typeof rawLink === 'string' && /^https?:\/\//i.test(rawLink);
// 构建完整路径 - 外链直接使用原始链接,内部路由按层级拼接
let fullPath = menu.path;
if (isExternalLink) {
fullPath = rawLink;
} else {
// 如果当前路径不是以 '/' 开头,需要拼接父路径
if (!menu.path.startsWith('/')) {
if (parentPath) {
// 确保父路径以 '/' 开头
const normalizedParentPath = parentPath.startsWith('/') ? parentPath : '/' + parentPath;
fullPath = normalizedParentPath + '/' + menu.path;
} else {
// 如果没有父路径但不是绝对路径,添加 '/' 前缀
fullPath = '/' + menu.path;
}
}
// 清理路径中的双斜杠
fullPath = fullPath.replace(/\/+/g, '/');
}
// 处理有meta信息且有标题的菜单项
if (menu.meta && menu.meta.title) {
// 获取图标
const iconKey = menu.meta.icon || 'document';
const icon = iconMap[iconKey] || Document;
// 获取颜色(循环使用预定义颜色)
const colorIndex = result.length % moduleColors.length;
const color = moduleColors[colorIndex];
// 构建当前菜单的层级链
const currentChain = [...parentChain];
if (menu.meta.title) {
currentChain.push(menu.meta.title);
}
// 确定一级目录名称 - 改进的层级识别逻辑
let firstLevelModuleName = '';
let menuPath = ''; // 用于显示层级路径
let moduleOrder = 0; // 一级目录的顺序
if (level === 0) {
// 当前就是一级目录
firstLevelModuleName = menu.meta.title;
menuPath = menu.meta.title;
moduleOrder = menuIndex; // 使用一级目录在API中的原始索引
} else {
// 从层级链中取第一个作为一级目录
firstLevelModuleName = currentChain[0] || '其他功能';
// 构建完整的层级路径(一级 > 二级 > 菜单)
menuPath = currentChain.join(' > ');
// 对于子菜单,使用父级的模块顺序
moduleOrder = Math.floor(menuIndex / 1000); // 简单的模块顺序推算
}
// 检查是否有可见的子菜单
const hasVisibleChildren = menu.children && menu.children.some((child: any) => !child.hidden);
// 检查是否为目录类型菜单
// 1. 组件为Layout或ParentView字符串
// 2. 组件为Vue对象且没有子菜单(可能是目录)
// 3. menuType为'M'(目录类型)
const isDirectoryMenu =
!isExternalLink &&
(menu.component === 'Layout' ||
menu.component === 'ParentView' ||
(typeof menu.component === 'object' && !hasVisibleChildren) ||
menu.menuType === 'M');
// 添加工作流菜单的详细调试信息
if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
console.log('=== 工作流菜单判断过程 ===');
console.log('hasVisibleChildren:', hasVisibleChildren);
console.log('isDirectoryMenu:', isDirectoryMenu);
console.log('menu.component:', menu.component);
console.log('menu.component类型:', typeof menu.component);
console.log('menu.menuType:', menu.menuType);
console.log('menu.children:', menu.children);
console.log('fullPath:', fullPath);
console.log('level:', level);
console.log('是否应该创建菜单项:', !hasVisibleChildren && !isDirectoryMenu && fullPath && fullPath !== '/');
console.log('完整菜单对象:', JSON.stringify(menu, null, 2));
}
// 目录菜单处理逻辑:如果是目录菜单且没有子菜单,直接跳过
if (isDirectoryMenu && !hasVisibleChildren) {
if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
console.log('=== 工作流菜单被跳过 ===');
console.log('原因: 是目录菜单且没有子菜单');
}
// 不创建菜单项,直接跳过
}
// 只有叶子节点(没有子菜单或子菜单都是隐藏的)且不是目录菜单才作为可点击的菜单项
else if ((!hasVisibleChildren && !isDirectoryMenu && fullPath && fullPath !== '/') || (isExternalLink && fullPath)) {
const menuAction: MenuAction = {
id: `menu-${menu.name || fullPath}`,
title: menu.meta.title,
description: menu.meta.title + '功能',
icon: icon,
color: color,
route: fullPath,
available: true,
parentName: firstLevelModuleName, // 使用一级目录名称进行分组
fullPath: menuPath, // 保存完整的层级路径用于显示
menuType: menu.menuType || 'C',
visible: menu.visible || '0',
status: menu.status || '0',
moduleOrder: moduleOrder, // 添加模块顺序
menuOrder: menuIndex, // 添加菜单在当前层级的顺序
orderNum: menu.orderNum || 0, // 添加菜单的原始排序号
logoUrl: getIconPath(iconKey), // 根据icon名称获取对应图标
isExternal: isExternalLink
};
// 添加调试日志来验证路径构建和排序
console.log(
`菜单项路径构建: ${menu.meta.title} - 原始path: ${menu.path}, 构建后fullPath: ${fullPath}, orderNum: ${menu.orderNum}, parentPath: ${parentPath}, level: ${level}`
);
result.push(menuAction);
} else if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
console.log('=== 工作流菜单被过滤掉 ===');
console.log('原因: 是目录菜单或没有有效路径');
}
}
// 递归处理子菜单 - 传递完整的层级链
if (menu.children && menu.children.length > 0) {
const newChain = [...parentChain];
if (menu.meta?.title) {
newChain.push(menu.meta.title);
}
// 构建子菜单的父路径 - 使用已经构建好的完整路径
let childParentPath = fullPath;
// 如果当前菜单是目录(没有组件或组件为Layout/ParentView),则传递fullPath作为父路径
// 否则传递当前菜单的路径
if (!menu.component || menu.component === 'Layout' || menu.component === 'ParentView') {
childParentPath = fullPath;
} else {
// 对于有具体组件的菜单,子菜单路径应该基于当前路径构建
childParentPath = fullPath;
}
// 递归处理,传递层级链和增加的层级
const childMenus = flattenMenus(menu.children, newChain, childParentPath, level + 1);
result.push(...childMenus);
}
});
return result;
};
// 按一级目录分组菜单 - 保持API原始顺序
const groupMenusByModule = (menus: MenuAction[]): MenuModule[] => {
const moduleMap = new Map<string, { menus: MenuAction[]; order: number }>();
menus.forEach((menu) => {
const moduleName = menu.parentName || '其他功能';
if (!moduleMap.has(moduleName)) {
moduleMap.set(moduleName, {
menus: [],
order: menu.moduleOrder || 999 // 使用菜单的模块顺序,默认排在最后
});
}
moduleMap.get(moduleName)!.menus.push(menu);
});
const modules: MenuModule[] = [];
let colorIndex = 0;
// 按模块的原始顺序排序,而不是按名称排序
const sortedModuleEntries = Array.from(moduleMap.entries()).sort((a, b) => {
return a[1].order - b[1].order;
});
sortedModuleEntries.forEach(([moduleName, moduleData]) => {
const moduleMenus = moduleData.menus;
// 过滤掉无效的模块
if (moduleMenus.length === 0) {
return;
}
// 按orderNum排序前的日志
console.log(
`${moduleName}模块排序前:`,
moduleMenus.map((m) => `${m.title}(${m.orderNum})`)
);
const sortedMenus = moduleMenus.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));
// 按orderNum排序后的日志
console.log(
`${moduleName}模块排序后:`,
sortedMenus.map((m) => `${m.title}(${m.orderNum})`)
);
const module: MenuModule = {
id: `module-${moduleName}`,
title: moduleName,
description: `${moduleName}模块包含${moduleMenus.length}个功能`,
color: moduleColors[colorIndex % moduleColors.length],
// 菜单项按原始orderNum排序,确保显示顺序正确
menus: sortedMenus
};
modules.push(module);
colorIndex++;
});
return modules;
};
// 获取菜单数据 - 使用permissionStore
const loadMenuData = async () => {
try {
// 从权限store获取顶部栏菜单数据(包含更完整的结构)
const topbarRoutes = permissionStore.getTopbarRoutes();
console.log('获取到的顶部栏路由数据:', topbarRoutes);
// 如果顶部栏数据为空,尝试获取侧边栏数据
const routesToProcess = topbarRoutes && topbarRoutes.length > 0 ? topbarRoutes : permissionStore.getSidebarRoutes();
// 过滤掉系统路由
const dynamicRoutes = routesToProcess.filter(
(route) =>
route.path !== '/' && route.path !== '/index' && route.path !== '/user' && !route.path.startsWith('/redirect') && route.hidden !== true
);
console.log('处理的动态路由数据:', dynamicRoutes);
// 扁平化菜单数据
const flatMenus = flattenMenus(dynamicRoutes);
allMenuActions.value = flatMenus;
// 按模块分组
const modules = groupMenusByModule(flatMenus);
menuModules.value = modules;
console.log('菜单数据处理完成', { flatMenus, modules });
} catch (error) {
console.error('获取菜单数据失败:', error);
ElMessage.error('获取菜单数据失败');
}
};
// 处理快捷功能点击
const handleQuickAction = (action: any) => {
// 检查功能是否可用
if (!action.available) {
ElMessage.warning(`${action.title}功能正在开发中,敬请期待!`);
return;
}
console.log('action', action);
// 外链菜单:在新标签页直接打开原始地址,不拼接
if (action.isExternal) {
window.open(action.route, '_blank');
ElMessage.success(`正在打开${action.title}...`);
return;
}
try {
// 使用路由跳转,系统会自动在tab页中打开
router
.push(action.route)
.then(() => {
ElMessage.success(`正在打开${action.title}...`);
})
.catch((error) => {
console.error('路由跳转失败:', error);
// 如果路由不存在,显示提示信息
if (error.name === 'NavigationFailure') {
ElMessage.warning(`${action.title}功能正在开发中,敬请期待!`);
} else {
ElMessage.error(`打开${action.title}失败,请稍后重试`);
}
});
} catch (error) {
console.error('快捷功能点击处理失败:', error);
ElMessage.error(`打开${action.title}失败,请稍后重试`);
}
};
// 组件挂载后加载数据
onMounted(async () => {
// 等待权限数据加载完成,如果还没有数据则等待一段时间
const checkAndLoadMenuData = async () => {
let retries = 0;
const maxRetries = 10;
while (retries < maxRetries) {
const sidebarRoutes = permissionStore.getSidebarRoutes();
if (sidebarRoutes && sidebarRoutes.length > 0) {
// 权限数据已加载,可以处理菜单
await loadMenuData();
console.log('首页菜单数据加载完成');
return;
}
// 等待100ms后重试
await new Promise((resolve) => setTimeout(resolve, 100));
retries++;
}
console.warn('权限数据加载超时,使用空菜单');
menuModules.value = [];
};
await checkAndLoadMenuData();
});
</script>
<style lang="scss" scoped>
.home {
// min-height: calc(100vh - 200px);
// background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
// 上半部分:轮播图样式
.carousel-section {
padding: 0 10px;
margin-bottom: 20px;
margin-top: 20px;
// background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
}
.campus-carousel {
// max-width: 1200px;
margin: 0 auto;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
:deep(.el-carousel__item) {
border-radius: 16px;
}
}
.carousel-item {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 16px;
}
.carousel-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.carousel-item:hover .carousel-image {
transform: scale(1.05);
}
.carousel-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
// background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
color: white;
padding: 40px 30px 30px;
text-align: left;
}
.carousel-title {
font-size: 24px;
font-weight: 600;
margin: 0 0 10px 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.carousel-description {
font-size: 16px;
margin: 0;
opacity: 0.9;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
line-height: 1.5;
}
// 下半部分:原有样式
.construction-container {
width: 100%;
max-width: 800px;
padding: 40px;
margin: 0 auto;
}
.construction-content {
text-align: center;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 60px 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.system-title {
font-size: 28px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 40px;
letter-spacing: 1px;
}
.construction-icon {
margin-bottom: 30px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.construction-text {
margin-bottom: 40px;
h2 {
font-size: 24px;
font-weight: 500;
color: #409eff;
margin-bottom: 15px;
}
.description {
font-size: 16px;
color: #606266;
margin-bottom: 10px;
line-height: 1.6;
}
.sub-description {
font-size: 14px;
color: #909399;
line-height: 1.5;
}
}
.progress-section {
margin-bottom: 30px;
.progress-label {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
font-weight: 500;
}
.progress-bar {
max-width: 200px;
margin: 0 auto;
}
}
.completion-info {
margin-bottom: 40px;
.el-tag {
padding: 12px 20px;
font-size: 14px;
.tag-icon {
margin-right: 8px;
}
}
}
.contact-section {
max-width: 200px;
margin: 0 auto;
}
.contact-card {
border-radius: 12px;
border: none;
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 500;
color: #409eff;
}
}
.contact-content {
p {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
font-size: 14px;
color: #606266;
&:last-child {
margin-bottom: 0;
}
.el-icon {
color: #409eff;
}
}
}
// 菜单功能区域样式
.menu-modules-section {
padding: 20px 10px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
margin-left: 10px;
margin-right: 10px;
}
.section-title {
font-size: 24px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 12px;
padding-bottom: 15px;
border-bottom: 2px solid #f1f2f6;
.section-icon {
display: flex;
align-items: center;
justify-content: center;
}
.section-icon-img {
width: 28px;
height: 28px;
object-fit: contain;
}
.function-count {
font-size: 14px;
font-weight: 400;
color: #909399;
margin-left: 10px;
}
}
.module-group {
margin-bottom: 35px;
padding: 20px;
background: rgba(255, 255, 255, 0.8);
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
&:last-child {
margin-bottom: 0;
}
}
.module-header {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.module-title {
font-size: 18px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
.module-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.module-icon-img {
width: 20px;
height: 20px;
object-fit: contain;
}
.module-count-tag {
margin-left: 10px;
font-weight: 400;
}
}
.module-description {
font-size: 14px;
color: #606266;
line-height: 1.5;
padding-left: 28px; // 与图标对齐
}
.module-content {
margin-top: 15px;
}
// 新增弹性布局样式
.module-content-flex {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 15px;
justify-content: flex-start;
}
.menu-card-wrapper {
flex: 0 0 calc((100% - 140px) / 8); // 每行8个,精确计算:(100% - 7个间隔*20px) / 8
min-width: 100px;
margin-bottom: 20px;
@media (max-width: 1600px) {
flex: 0 0 calc((100% - 120px) / 7); // 中大屏幕每行7个
}
@media (max-width: 1400px) {
flex: 0 0 calc((100% - 100px) / 6); // 中等屏幕每行6个
}
@media (max-width: 1200px) {
flex: 0 0 calc((100% - 80px) / 5); // 小屏幕每行5个
}
@media (max-width: 992px) {
flex: 0 0 calc((100% - 60px) / 4); // 平板每行4个
}
@media (max-width: 768px) {
flex: 0 0 calc((100% - 40px) / 3); // 移动端每行3个
}
@media (max-width: 600px) {
flex: 0 0 calc((100% - 20px) / 2); // 小手机每行2个
}
@media (max-width: 480px) {
flex: 0 0 calc(100% - 20px); // 超小屏幕每行1个
}
}
.menu-action-card {
border-radius: 12px;
border: none;
transition: all 0.3s ease;
cursor: pointer;
height: 110px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
&.disabled {
cursor: not-allowed;
opacity: 0.6;
&:hover {
transform: none;
box-shadow: none;
}
}
}
.menu-action {
text-align: center;
padding: 10px 8px;
width: 100%;
}
// 原图标样式(保留以防需要)
.menu-icon {
font-size: 36px;
margin-bottom: 12px;
display: block;
}
// 新增logo图片样式
.menu-logo-wrapper {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 8px;
height: 36px;
}
.menu-logo {
width: 36px;
height: 36px;
object-fit: contain;
border-radius: 6px;
transition: all 0.3s ease;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
&:hover {
transform: scale(1.1);
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15));
}
}
.loading-state {
text-align: center;
padding: 60px 20px;
.loading-tips {
font-size: 12px;
color: #909399;
margin-top: 8px;
}
}
.action-title {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
display: block;
line-height: 1.3;
}
.action-desc {
font-size: 11px;
color: #666;
display: block;
line-height: 1.4;
&.disabled {
color: #c0c4cc;
}
}
// .action-path 样式已移除,因为不再显示层级路径
.action-title.disabled {
color: #c0c4cc;
}
.status-tag {
margin-top: 8px;
display: inline-block;
}
// 响应式设计
@media (max-width: 768px) {
.carousel-section {
padding: 20px 10px;
}
.campus-carousel {
height: 300px !important;
}
.carousel-overlay {
padding: 30px 20px 20px;
}
.carousel-title {
font-size: 20px;
}
.carousel-description {
font-size: 14px;
}
.menu-modules-section {
padding: 15px 10px;
margin: 10px;
}
.section-title {
font-size: 20px;
margin-bottom: 20px;
flex-direction: column;
gap: 8px;
text-align: center;
.function-count {
margin-left: 0;
}
}
.module-group {
padding: 15px;
margin-bottom: 25px;
}
.module-header {
margin-bottom: 15px;
}
.module-title {
font-size: 16px;
justify-content: center;
text-align: center;
}
.module-description {
text-align: center;
padding-left: 0;
font-size: 13px;
}
.module-content-flex {
gap: 15px;
}
.menu-card-wrapper {
margin-bottom: 15px;
}
.menu-action-card {
height: 100px;
}
.menu-action {
padding: 8px 5px;
}
.menu-logo-wrapper {
height: 30px;
margin-bottom: 6px;
}
.menu-logo {
width: 30px;
height: 30px;
}
.action-title {
font-size: 13px;
}
.action-desc {
font-size: 10px;
}
.loading-state {
padding: 40px 10px;
}
}
// 深色主题适配
html.dark {
.carousel-section {
background: rgba(0, 0, 0, 0.1);
}
.menu-modules-section {
background: rgba(30, 30, 30, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.section-title {
color: #e5eaf3;
border-bottom: 2px solid #404040;
}
.module-group {
background: rgba(40, 40, 40, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
&:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
}
.module-header {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.module-title {
color: #000000 !important;
}
.module-description {
color: #a3a6ad;
}
.menu-action-card {
background: rgba(50, 50, 50, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
&:hover {
background: rgba(60, 60, 60, 0.9);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.3);
}
}
.action-title {
color: #e5eaf3;
&.disabled {
color: #6c6e72;
}
}
.action-desc {
color: #a3a6ad;
&.disabled {
color: #6c6e72;
}
}
.construction-content {
background: rgba(30, 30, 30, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.system-title {
color: #e5eaf3;
}
.construction-text {
.description {
color: #a3a6ad;
}
.sub-description {
color: #6c6e72;
}
}
.progress-section .progress-label {
color: #a3a6ad;
}
.contact-content p {
color: #a3a6ad;
}
}
</style>