fix 登录页和home页路由调整
This commit is contained in:
parent
862661c426
commit
f25f506ca8
@ -98,42 +98,46 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (to.meta.title) NProgress.start()
|
||||
const storesUseUserInfo = useUserInfo(pinia)
|
||||
const token = storesUseUserInfo.getToken()
|
||||
if (to.meta.isPublic && !token) {
|
||||
|
||||
// 如果是公共页面,直接通过,不需要检查token和路由列表
|
||||
if (to.meta.isPublic) {
|
||||
next()
|
||||
NProgress.done()
|
||||
return
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
next(`/login?redirect=${to.path}¶ms=${JSON.stringify(to.query ? to.query : to.params)}`)
|
||||
storesUseUserInfo.removeTokenInfo()
|
||||
Session.clear()
|
||||
NProgress.done()
|
||||
} else if (token && to.path === '/login') {
|
||||
console.log('🔄 [路由守卫] 已登录用户访问登录页面,重定向到系统入口页面')
|
||||
next('/system-entrance')
|
||||
NProgress.done()
|
||||
} else {
|
||||
if (!token) {
|
||||
next(`/login?redirect=${to.path}¶ms=${JSON.stringify(to.query ? to.query : to.params)}`)
|
||||
storesUseUserInfo.removeTokenInfo()
|
||||
Session.clear()
|
||||
NProgress.done()
|
||||
} else if (token && to.path === '/login') {
|
||||
next('/')
|
||||
NProgress.done()
|
||||
} else {
|
||||
const storesRoute = useRoute(pinia)
|
||||
storesRoute.setRouteTo({ to: { path: to.path, params: JSON.stringify(to.query ? to.query : to.params) } })
|
||||
const storesRoutesList = useRoutesList(pinia)
|
||||
const { routesList } = storeToRefs(storesRoutesList)
|
||||
if (routesList.value.length === 0) {
|
||||
if (isRequestRoutes) {
|
||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||
const isNoPower = await initBackEndControlRoutes()
|
||||
if (isNoPower) {
|
||||
ElMessage.warning('抱歉,您没有分配权限,请联系管理员')
|
||||
storesUseUserInfo.removeTokenInfo()
|
||||
Session.clear()
|
||||
}
|
||||
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
|
||||
// to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理
|
||||
next({ path: to.path, query: to.query })
|
||||
} else {
|
||||
await initFrontEndControlRoutes()
|
||||
next({ path: to.path, query: to.query })
|
||||
const storesRoute = useRoute(pinia)
|
||||
storesRoute.setRouteTo({ to: { path: to.path, params: JSON.stringify(to.query ? to.query : to.params) } })
|
||||
const storesRoutesList = useRoutesList(pinia)
|
||||
const { routesList } = storeToRefs(storesRoutesList)
|
||||
if (routesList.value.length === 0) {
|
||||
if (isRequestRoutes) {
|
||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||
const isNoPower = await initBackEndControlRoutes()
|
||||
if (isNoPower) {
|
||||
ElMessage.warning('抱歉,您没有分配权限,请联系管理员')
|
||||
storesUseUserInfo.removeTokenInfo()
|
||||
Session.clear()
|
||||
}
|
||||
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
|
||||
// to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理
|
||||
next({ path: to.path, query: to.query })
|
||||
} else {
|
||||
next()
|
||||
await initFrontEndControlRoutes()
|
||||
next({ path: to.path, query: to.query })
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -143,6 +143,15 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
isPublic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system-entrance',
|
||||
name: 'system-entrance',
|
||||
component: () => import('/@/views/system-entrance/index.vue'),
|
||||
meta: {
|
||||
title: '系统入口',
|
||||
isPublic: true,
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 提示:写在这里的为全屏界面,不建议写在这里
|
||||
* 请写在 `dynamicRoutes` 路由数组中
|
||||
|
@ -217,6 +217,10 @@ const onSignIn = async () => {
|
||||
}
|
||||
// 登录成功后的跳转
|
||||
const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
console.log('🚀 [登录成功] signInSuccess 被调用')
|
||||
console.log('🚀 [登录成功] isNoPower:', isNoPower)
|
||||
console.log('🚀 [登录成功] route.query:', route.query)
|
||||
|
||||
if (isNoPower) {
|
||||
ElMessage.warning('抱歉,您没有分配权限,请联系管理员')
|
||||
useUserInfo().removeTokenInfo()
|
||||
@ -224,15 +228,17 @@ const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
} else {
|
||||
// 初始化登录成功时间问候语
|
||||
let currentTimeInfo = currentTime.value
|
||||
// 登录成功,跳到转首页
|
||||
// 登录成功,跳到系统入口页面
|
||||
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
||||
if (route.query?.redirect) {
|
||||
if (route.query?.redirect && route.query.redirect !== '/') {
|
||||
console.log('🚀 [登录成功] 有redirect参数且不是首页,跳转到:', route.query.redirect)
|
||||
router.push({
|
||||
path: <string>route.query?.redirect,
|
||||
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
||||
})
|
||||
} else {
|
||||
router.push('/')
|
||||
console.log('🚀 [登录成功] 无redirect参数或redirect是首页,跳转到系统入口页面: /system-entrance')
|
||||
router.push('/system-entrance')
|
||||
}
|
||||
// 登录成功提示
|
||||
const signInText = t('message.signInText')
|
||||
|
@ -135,15 +135,15 @@ const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
} else {
|
||||
// 初始化登录成功时间问候语
|
||||
let currentTimeInfo = currentTime.value
|
||||
// 登录成功,跳到转首页
|
||||
// 登录成功,跳到系统入口页面
|
||||
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
||||
if (route.query?.redirect) {
|
||||
if (route.query?.redirect && route.query.redirect !== '/') {
|
||||
router.push({
|
||||
path: <string>route.query?.redirect,
|
||||
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
||||
})
|
||||
} else {
|
||||
router.push('/')
|
||||
router.push('/system-entrance')
|
||||
}
|
||||
// 登录成功提示
|
||||
const signInText = t('message.signInText')
|
||||
|
@ -138,15 +138,15 @@ const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
} else {
|
||||
// 初始化登录成功时间问候语
|
||||
let currentTimeInfo = currentTime.value
|
||||
// 登录成功,跳到转首页
|
||||
// 登录成功,跳到系统入口页面
|
||||
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
||||
if (route.query?.redirect) {
|
||||
if (route.query?.redirect && route.query.redirect !== '/') {
|
||||
router.push({
|
||||
path: <string>route.query?.redirect,
|
||||
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
||||
})
|
||||
} else {
|
||||
router.push('/')
|
||||
router.push('/system-entrance')
|
||||
}
|
||||
// 登录成功提示
|
||||
const signInText = t('message.signInText')
|
||||
|
@ -114,20 +114,21 @@ const onSignIn = async () => {
|
||||
// 登录成功后的跳转
|
||||
const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
if (isNoPower) {
|
||||
ElMessage.warning('抱歉,您没有登录权限')
|
||||
ElMessage.warning('抱歉,您没有分配权限,请联系管理员')
|
||||
useUserInfo().removeTokenInfo()
|
||||
Session.clear()
|
||||
} else {
|
||||
// 初始化登录成功时间问候语
|
||||
let currentTimeInfo = currentTime.value
|
||||
// 登录成功,跳到转首页
|
||||
// 登录成功,跳到系统入口页面
|
||||
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
||||
if (route.query?.redirect) {
|
||||
if (route.query?.redirect && route.query.redirect !== '/') {
|
||||
router.push({
|
||||
path: <string>route.query?.redirect,
|
||||
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
||||
})
|
||||
} else {
|
||||
router.push('/')
|
||||
router.push('/system-entrance')
|
||||
}
|
||||
// 登录成功提示
|
||||
const signInText = t('message.signInText')
|
||||
|
481
src/views/system-entrance/index.vue
Normal file
481
src/views/system-entrance/index.vue
Normal file
@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div class="system-entrance-page">
|
||||
<!-- Logo区域 -->
|
||||
<div class="logo-section">
|
||||
<div class="logo-container">
|
||||
<img src="/@/assets/logo-mini.png" alt="WuXi Biologics" class="company-logo" />
|
||||
<div class="logo-text">
|
||||
<span class="company-name">WuXi Biologics</span>
|
||||
<span class="company-subtitle">Global Solution Provider</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 欢迎标题 -->
|
||||
<div class="welcome-title">
|
||||
<h1>欢迎来到系统入口</h1>
|
||||
</div>
|
||||
|
||||
<!-- 功能模块卡片 -->
|
||||
<div class="modules-grid">
|
||||
<!-- 调试信息 -->
|
||||
<div v-if="state.columnsAsideList.length === 0" class="debug-info">
|
||||
<div v-if="!storesUserInfo.getToken()">
|
||||
<p>👤 您尚未登录</p>
|
||||
<p>请先<router-link to="/login" class="login-link">点击这里登录</router-link>,然后再访问系统功能</p>
|
||||
</div>
|
||||
<div v-else-if="state.isInitializing">
|
||||
<p>🔄 正在初始化系统路由数据...</p>
|
||||
<p>请稍候,系统正在准备菜单数据</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>⚡ 系统需要初始化路由数据</p>
|
||||
<p>请<router-link to="/" class="login-link">点击这里进入主页</router-link>完成系统初始化,然后返回此页面</p>
|
||||
<p style="margin-top: 15px; font-size: 12px; color: #666;">
|
||||
或者刷新页面重新尝试加载路由数据
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 动态路由模块 -->
|
||||
<div
|
||||
v-for="(v, k) in state.columnsAsideList"
|
||||
:key="`route-${k}`"
|
||||
class="module-card"
|
||||
@click="onColumnsAsideMenuClick(v)"
|
||||
>
|
||||
<div class="module-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" :d="getModuleIcon(k)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="module-title">{{ getModuleTitle(v) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SystemEntrancePage">
|
||||
import { reactive, onMounted, computed, watch } from 'vue'
|
||||
import { useRoute, useRouter, RouteRecordRaw } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRoutesList } from '/@/stores/routesList'
|
||||
import { useThemeConfig } from '/@/stores/themeConfig'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import mittBus from '/@/utils/mitt'
|
||||
import { treeToList, listToTree, filterList } from '/@/utils/tree'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { initBackEndControlRoutes } from '/@/router/backEnd'
|
||||
import { initFrontEndControlRoutes } from '/@/router/frontEnd'
|
||||
import { NextLoading } from '/@/utils/loading'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const stores = useRoutesList()
|
||||
const storesThemeConfig = useThemeConfig()
|
||||
const storesUserInfo = useUserInfo()
|
||||
const { routesList } = storeToRefs(stores)
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig)
|
||||
|
||||
const state = reactive({
|
||||
columnsAsideList: [] as RouteItem[],
|
||||
isInitializing: false,
|
||||
})
|
||||
|
||||
// 获取模块图标
|
||||
const getModuleIcon = (index: number): string => {
|
||||
// 预定义的图标数组
|
||||
const iconPaths = [
|
||||
'M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333z m0 64C311.168 149.333333 149.333333 311.168 149.333333 512s161.834667 362.666667 362.666667 362.666667 362.666667-161.834667 362.666667-362.666667S712.832 149.333333 512 149.333333z m-32 149.333334v277.333333l170.666667 170.666667-45.254667 45.254666L469.333333 656V298.666667h74.666667z',
|
||||
'M469.333333 170.666667c23.564267 0 42.666667 19.102933 42.666667 42.666666v128c0 23.564267-19.102933 42.666667-42.666667 42.666667s-42.666667-19.102933-42.666667-42.666667V213.333333c0-23.564267 19.102933-42.666667 42.666667-42.666667z m0 469.333333c23.564267 0 42.666667 19.102933 42.666667 42.666667v128c0 23.564267-19.102933 42.666667-42.666667 42.666667s-42.666667-19.102933-42.666667-42.666667V682.666667c0-23.564267 19.102933-42.666667 42.666667-42.666667z',
|
||||
'M170.666667 85.333333h682.666666c47.128533 0 85.333333 38.204800 85.333334 85.333334v682.666666c0 47.128533-38.204800 85.333333-85.333334 85.333334H170.666667c-47.128533 0-85.333333-38.204800-85.333334-85.333334V170.666667c0-47.128533 38.204800-85.333333 85.333334-85.333334z m0 64c-11.776 0-21.333333 9.557333-21.333334 21.333334v682.666666c0 11.776 9.557333 21.333333 21.333334 21.333334h682.666666c11.776 0 21.333333-9.557333 21.333334-21.333334V170.666667c0-11.776-9.557333-21.333333-21.333334-21.333334H170.666667z',
|
||||
'M512 618.666667c58.88 0 106.666667-47.786667 106.666667-106.666667s-47.786667-106.666667-106.666667-106.666667-106.666667 47.786667-106.666667 106.666667 47.786667 106.666667 106.666667 106.666667z m0 64c-94.293333 0-170.666667-76.373333-170.666667-170.666667s76.373333-170.666667 170.666667-170.666667 170.666667 76.373333 170.666667 170.666667-76.373333 170.666667-170.666667 170.666667z'
|
||||
]
|
||||
return iconPaths[index % iconPaths.length]
|
||||
}
|
||||
|
||||
// 获取模块标题
|
||||
const getModuleTitle = (moduleItem: RouteItem): string => {
|
||||
return moduleItem.meta?.title || '未命名模块'
|
||||
}
|
||||
|
||||
// 路由过滤递归函数(完全按照 columnsAside.vue)
|
||||
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
|
||||
return arr
|
||||
.filter((item: T) => !item.meta?.isHide)
|
||||
.map((item: T) => {
|
||||
item = Object.assign({}, item)
|
||||
if (item.children) item.children = filterRoutesFun(item.children)
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
// 设置/过滤路由(完全按照 columnsAside.vue)
|
||||
const setFilterRoutes = () => {
|
||||
state.columnsAsideList = filterRoutesFun(routesList.value)
|
||||
}
|
||||
|
||||
// 主动初始化路由数据
|
||||
const initializeRoutes = async () => {
|
||||
if (state.isInitializing) return
|
||||
|
||||
const token = storesUserInfo.getToken()
|
||||
if (!token) return
|
||||
|
||||
state.isInitializing = true
|
||||
|
||||
try {
|
||||
if (themeConfig.value.isRequestRoutes) {
|
||||
await initBackEndControlRoutes()
|
||||
} else {
|
||||
await initFrontEndControlRoutes()
|
||||
}
|
||||
// 确保停止加载动画
|
||||
NextLoading.done()
|
||||
} catch (error) {
|
||||
console.error('路由初始化失败:', error)
|
||||
// 即使出错也要停止加载动画
|
||||
NextLoading.done()
|
||||
} finally {
|
||||
state.isInitializing = false
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单高亮点击事件(完全按照 columnsAside.vue 的方式)
|
||||
const onColumnsAsideMenuClick = async (v: RouteItem) => {
|
||||
let { path, redirect } = v
|
||||
|
||||
if (redirect) {
|
||||
if (route.path.startsWith(redirect)) {
|
||||
mittBus.emit('setSendColumnsChildren', setSendChildren(redirect))
|
||||
} else {
|
||||
router.push(redirect)
|
||||
}
|
||||
} else {
|
||||
if (v.children && v.children.length > 0) {
|
||||
const resData: MittMenu = setSendChildren(path)
|
||||
if (Object.keys(resData).length <= 0) return false
|
||||
mittBus.emit('setSendColumnsChildren', resData)
|
||||
router.push('/')
|
||||
themeConfig.value.isCollapse = false
|
||||
} else {
|
||||
router.push(path)
|
||||
themeConfig.value.isCollapse = true
|
||||
}
|
||||
}
|
||||
|
||||
// 一个路由设置自动收起菜单(按照 columnsAside.vue 的逻辑)
|
||||
!v.children || v.children.length < 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false)
|
||||
}
|
||||
|
||||
// 传送当前子级数据到菜单中(完全按照 columnsAside.vue 的方式)
|
||||
const setSendChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/')
|
||||
let rootPath = `/${currentPathSplit[1]}`
|
||||
|
||||
// 判断是否能够找到根节点
|
||||
if (!state.columnsAsideList.find((v) => v.path === rootPath)) {
|
||||
// 不存在则使用顶级的分类
|
||||
let routeTree = listToTree(
|
||||
filterList(treeToList(cloneDeep(state.columnsAsideList)), path, {
|
||||
filterWhere: (item: any, filterword: string) => {
|
||||
return item.path?.toLocaleLowerCase() === filterword
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
// 找到根节点则使用根节点
|
||||
if (routeTree.length > 0 && routeTree[0]?.path) {
|
||||
rootPath = routeTree[0].path
|
||||
}
|
||||
}
|
||||
|
||||
let currentData: MittMenu = { children: [] }
|
||||
state.columnsAsideList.map((v: RouteItem, k: number) => {
|
||||
if (v.path === rootPath) {
|
||||
v['k'] = k
|
||||
currentData['item'] = { ...v }
|
||||
currentData['children'] = [{ ...v }]
|
||||
if (v.children) currentData['children'] = v.children
|
||||
}
|
||||
})
|
||||
return currentData
|
||||
}
|
||||
|
||||
// 页面加载时(完全按照 columnsAside.vue)
|
||||
onMounted(() => {
|
||||
setFilterRoutes()
|
||||
|
||||
// 如果路由数据为空且用户已登录,主动初始化
|
||||
if (routesList.value.length === 0 && storesUserInfo.getToken()) {
|
||||
initializeRoutes()
|
||||
} else {
|
||||
// 如果不需要初始化,也要确保停止可能存在的加载动画
|
||||
NextLoading.done()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听路由列表变化(关键:确保路由数据更新时重新过滤)
|
||||
watch(
|
||||
() => routesList.value,
|
||||
() => {
|
||||
setFilterRoutes()
|
||||
// 当路由数据加载完成时,确保停止加载动画
|
||||
if (routesList.value.length > 0) {
|
||||
NextLoading.done()
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: false }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.system-entrance-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.logo-section {
|
||||
margin-bottom: 60px;
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
|
||||
.company-logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.company-name {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 4px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.company-subtitle {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
margin-bottom: 80px;
|
||||
|
||||
h1 {
|
||||
font-size: 42px;
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.modules-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 50px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
|
||||
.debug-info {
|
||||
grid-column: 1 / -1;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.login-link {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
color: #5a6fd8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.module-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 24px;
|
||||
padding: 50px 40px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.4s ease;
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(20px);
|
||||
min-height: 240px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-15px) scale(1.02);
|
||||
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.25);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
|
||||
.module-icon .icon-svg {
|
||||
color: #667eea;
|
||||
transform: scale(1.15) rotateY(10deg);
|
||||
}
|
||||
|
||||
.module-title {
|
||||
color: #667eea;
|
||||
}
|
||||
}
|
||||
|
||||
.module-icon {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.icon-svg {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
color: #555;
|
||||
transition: all 0.4s ease;
|
||||
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
}
|
||||
|
||||
.module-title {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
transition: color 0.3s ease;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 1024px) {
|
||||
.modules-grid {
|
||||
max-width: 600px;
|
||||
gap: 40px;
|
||||
|
||||
.module-card {
|
||||
padding: 40px 30px;
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 30px 20px;
|
||||
|
||||
.logo-section {
|
||||
margin-bottom: 50px;
|
||||
|
||||
.logo-container {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
|
||||
.company-logo {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
align-items: center;
|
||||
|
||||
.company-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.company-subtitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
margin-bottom: 60px;
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.modules-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
max-width: 400px;
|
||||
|
||||
.module-card {
|
||||
padding: 35px 25px;
|
||||
min-height: 180px;
|
||||
|
||||
.module-icon {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.icon-svg {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
padding: 20px 15px;
|
||||
|
||||
.welcome-title h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.modules-grid {
|
||||
max-width: 320px;
|
||||
|
||||
.module-card {
|
||||
padding: 30px 20px;
|
||||
min-height: 160px;
|
||||
|
||||
.module-icon .icon-svg {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.module-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user