Compare commits

...

2 Commits

Author SHA1 Message Date
Shu Guang
255f707e0c fix: 权限修改 2025-05-17 00:58:44 +08:00
Shu Guang
cd8148f65a fix: 登录与首页修改 2025-05-17 00:58:31 +08:00
3 changed files with 286 additions and 245 deletions

View File

@ -1,24 +1,25 @@
import store from '@/store'; // 导入 Vuex store 实例 import store from '@/store';
import router from '@/router'; // 导入 Vue Router 实例 import router from '@/router';
import { notification } from 'ant-design-vue'; // 导入 ant-design-vue 的 notification 组件 import { notification } from 'ant-design-vue';
import NProgress from 'nprogress'; // 导入进度条组件 import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; // 导入进度条样式 import 'nprogress/nprogress.css';
NProgress.configure({ showSpinner: false }); // 配置进度条,隐藏加载动画
const loginPath = '/login'; // 登录路径 NProgress.configure({ showSpinner: false });
const defaultRoutePath = '/race/list'; // 默认路由路径
const whiteList = ['Login']; // 白名单,无需登录即可访问的页面 const loginPath = '/login';
const defaultRoutePath = '/race/list';
const whiteList = ['Login', 'Index']; // 白名单保持不变
router.beforeEach(async(to, from, next) => { router.beforeEach(async(to, from, next) => {
NProgress.start(); // 开始进度条 NProgress.start();
const tokens = window.localStorage.getItem("token");
const tokens = window.localStorage.getItem("token"); // 使用相同的键来获取token
/* 未登录情况下的路由拦截 */ /* 未登录情况下的路由拦截 */
if (!tokens) { if (!tokens) {
// alert(tokens) // 修改判断逻辑,同时检查路由名称和路径
if (whiteList.includes(to.name)) { // 如果在白名单中,直接放行 if (whiteList.includes(to.name) || to.path === '/Index') {
next(); next();
} else { // 否则重定向至登录页面 } else {
next({ next({
path: loginPath, path: loginPath,
query: { query: {
@ -30,69 +31,59 @@ router.beforeEach(async(to, from, next) => {
} }
/* 已登录情况下访问登录界面的处理 */ /* 已登录情况下访问登录界面的处理 */
if (to.path === loginPath) { // 如果已登录且访问登录页面,则重定向至默认页面 if (to.path === loginPath) {
next(defaultRoutePath); next(defaultRoutePath);
return; return;
} }
/* 已获取用户信息的情况 */ /* 已获取用户信息的情况 */
if (store.getters.permissions!=null && store.getters.permissions!=="") { // 如果已获取用户权限信息,则直接放行 if (store.getters.permissions != null && store.getters.permissions !== "") {
next(); next();
NProgress.done(); // 结束进度条 NProgress.done();
return; return;
} }
/* 未获取用户信息的情况 */ /* 未获取用户信息的情况 */
try { try {
await store.dispatch('initUser'); // 异步获取用户信息 await store.dispatch('initUser');
} catch (e) { } catch (e) {
store.commit('logout'); // 获取失败时执行退出登录操作 store.commit('logout');
next({ path: loginPath, query: { redirect: to.fullPath } }); // 重定向至登录页面 next({ path: loginPath, query: { redirect: to.fullPath } });
notification.error({ notification.error({
message: '错误', message: '错误',
description: '请求用户信息失败,请重试', description: '请求用户信息失败,请重试',
}); // 显示错误通知 });
NProgress.done(); // 结束进度条 NProgress.done();
return; return;
} }
/* 成功获取用户信息的情况 */ /* 成功获取用户信息的情况 */
// 检查路由权限 if (!checkAccess(to)) {
if (!checkAccess(to)) { // 如果当前路由无权限访问,则重定向至默认页面
next({ path: defaultRoutePath, replace: true }); next({ path: defaultRoutePath, replace: true });
NProgress.done(); // 结束进度条 NProgress.done();
return; return;
} }
const redirect = decodeURIComponent(from.query.redirect || to.path); // 获取重定向地址
// 如果重定向地址与当前路径相同,则替换历史记录 const redirect = decodeURIComponent(from.query.redirect || to.path);
if (redirect === to.path) { if (redirect === to.path) {
next({...to, replace: true }); next({...to, replace: true });
} else { // 否则正常重定向 } else {
next(redirect); next(redirect);
} }
NProgress.done(); // 结束进度条 NProgress.done();
}); });
router.afterEach(() => { router.afterEach(() => {
NProgress.done(); // 结束进度条 NProgress.done();
}); });
/**
* 判断当前路由是否具有权限访问
* @param {Route} route 路由对象
* @returns {boolean} 是否具有权限
*/
function checkAccess(route) { function checkAccess(route) {
const userPrivileges = store.state.user.userPrivileges == null ? 1 : store.state.user.userPrivileges;
const userPrivileges = store.state.user.userPrivileges==null ? 1 : store.state.user.userPrivileges; // 获取用户权限信息 const requiredPrivileges = route.meta.auth;
const requiredPrivileges = route.meta.auth; // 获取当前路由所需权限信息 console.log(userPrivileges);
console.log(userPrivileges)
// 如果路由未设置权限要求,则默认放行 if (!requiredPrivileges) {
if (!requiredPrivileges) { return true;
return true; }
} return requiredPrivileges.includes(userPrivileges);
}
// 检查用户的权限是否包含在所需权限中
return requiredPrivileges.includes(userPrivileges);
}

View File

@ -29,17 +29,10 @@
<span>联系我们</span> <span>联系我们</span>
<div class="link-hover-effect"></div> <div class="link-hover-effect"></div>
</a> </a>
<button class="cyber-button" onclick="window.location.href='/login'"> <button class="cyber-button" @click="goToLogin">
<span class="btn-text">登录</span> <span class="btn-text">登录</span>
<span class="btn-glitch"></span> <span class="btn-glitch"></span>
</button> </button>
<button
class="cyber-button outline"
onclick="window.location.href='/register'"
>
<span class="btn-text">注册</span>
<span class="btn-glitch"></span>
</button>
</div> </div>
<div class="menu-toggle"> <div class="menu-toggle">
<div class="hamburger"> <div class="hamburger">
@ -68,18 +61,12 @@
<span class="typing-cursor">|</span> <span class="typing-cursor">|</span>
</p> </p>
<div class="index-cta-buttons animate-slide-up-delay-2"> <div class="index-cta-buttons animate-slide-up-delay-2">
<button <button class="cyber-btn glow-button" @click="goToLogin">
class="cyber-btn glow-button"
onclick="window.location.href='/login'"
>
<span class="cyber-btn-text">开始使用</span> <span class="cyber-btn-text">开始使用</span>
<span class="cyber-btn-glitch"></span> <span class="cyber-btn-glitch"></span>
<span class="cyber-btn-glow"></span> <span class="cyber-btn-glow"></span>
</button> </button>
<button <button class="cyber-btn outline-button" @click="goToLogin">
class="cyber-btn outline-button"
onclick="window.scrollTo({top: document.querySelector('#features').offsetTop, behavior: 'smooth'})"
>
<span class="cyber-btn-text">了解更多</span> <span class="cyber-btn-text">了解更多</span>
<span class="cyber-arrow"></span> <span class="cyber-arrow"></span>
</button> </button>
@ -1618,169 +1605,13 @@ h6 {
} }
} }
</style> </style>
<script>
<script setup> export default {
import { onMounted, ref } from "vue"; name: "Index",
methods: {
// goToLogin() {
const menuOpen = ref(false); this.$router.push("/login");
},
const toggleMenu = () => { },
menuOpen.value = !menuOpen.value;
const navLinks = document.querySelector(".index-nav-links");
if (navLinks) {
if (menuOpen.value) {
navLinks.classList.add("active");
} else {
navLinks.classList.remove("active");
}
}
}; };
onMounted(() => {
//
const menuToggle = document.querySelector(".menu-toggle");
if (menuToggle) {
menuToggle.addEventListener("click", toggleMenu);
}
//
const addMatrixElements = () => {
const grid = document.createElement("div");
grid.classList.add("matrix-bg");
document.body.appendChild(grid);
for (let i = 0; i < 50; i++) {
const digit = document.createElement("div");
digit.classList.add("matrix-digit");
digit.style.left = `${Math.random() * 100}%`;
digit.style.top = `${Math.random() * 100}%`;
digit.style.animationDelay = `${Math.random() * 5}s`;
digit.textContent = Math.floor(Math.random() * 10);
grid.appendChild(digit);
}
};
// particles.js
if (typeof particlesJS !== "undefined") {
particlesJS("particles-js", {
particles: {
number: { value: 80, density: { enable: true, value_area: 800 } },
color: { value: "#4e54c8" },
shape: { type: "circle" },
opacity: { value: 0.5, random: true },
size: { value: 3, random: true },
line_linked: {
enable: true,
distance: 150,
color: "#4e54c8",
opacity: 0.2,
width: 1,
},
move: {
enable: true,
speed: 1.5,
direction: "none",
random: true,
straight: false,
out_mode: "out",
bounce: false,
},
},
interactivity: {
detect_on: "canvas",
events: {
onhover: { enable: true, mode: "grab" },
onclick: { enable: true, mode: "push" },
resize: true,
},
modes: {
grab: { distance: 140, line_linked: { opacity: 0.5 } },
push: { particles_nb: 3 },
},
},
retina_detect: true,
});
} else {
// particlesJS
addMatrixElements();
}
//
const subtitleElement = document.querySelector(".index-subtitle");
if (subtitleElement) {
const text = subtitleElement.textContent.trim().replace("|", "");
subtitleElement.textContent = "";
const typeText = async () => {
for (let i = 0; i < text.length; i++) {
await new Promise((resolve) => setTimeout(resolve, 100));
subtitleElement.textContent = text.substring(0, i + 1);
if (i === text.length - 1) {
subtitleElement.innerHTML += '<span class="typing-cursor">|</span>';
}
}
};
setTimeout(typeText, 1000);
}
//
const scrollElements = document.querySelectorAll(
".index-feature-card, .index-stat-item"
);
const isElementInView = (el) => {
const rect = el.getBoundingClientRect();
return (
rect.top <=
(window.innerHeight || document.documentElement.clientHeight) * 0.8
);
};
const displayScrollElement = (element) => {
element.classList.add("scrolled");
};
const hideScrollElement = (element) => {
element.classList.remove("scrolled");
};
const handleScrollAnimation = () => {
scrollElements.forEach((el) => {
if (isElementInView(el)) {
displayScrollElement(el);
} else {
hideScrollElement(el);
}
});
};
//
const styleSheet = document.styleSheets[0];
styleSheet.insertRule(
`
.index-feature-card, .index-stat-item {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
`,
styleSheet.cssRules.length
);
styleSheet.insertRule(
`
.index-feature-card.scrolled, .index-stat-item.scrolled {
opacity: 1;
transform: translateY(0);
}
`,
styleSheet.cssRules.length
);
//
setTimeout(handleScrollAnimation, 100);
window.addEventListener("scroll", handleScrollAnimation);
});
</script> </script>

View File

@ -1,26 +1,245 @@
<template> <template>
<div class="container"> <div class="container">
<UserLogin class="login" /> <div class="background-shapes">
<p style="position: fixed;bottom: 0;text-align: center;left: 0;right: 0;font-family: '宋体';">@ 2024-青创先锋</p> <div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
</div>
<div class="content-wrapper">
<div class="left-section">
<h1 class="welcome-text">欢迎使用</h1>
<div class="brand">
<div class="logo-container">
<!-- <img src="@/assets/logo.png" alt="Logo" class="logo" /> -->
<!-- <h2 class="gradient-text">CodeMaster</h2> -->
</div>
<div class="brand-description">
<p class="main-slogan">基于ChatGLM大模型的高校竞赛管理系统</p>
<p class="sub-slogan">智能辅助 · 高效管理 · 实时评测</p>
</div>
</div>
<div class="features">
<div class="feature-item">
<i class="el-icon-trophy"></i>
<span>竞赛管理</span>
</div>
<div class="feature-item">
<i class="el-icon-monitor"></i>
<span>实时评测</span>
</div>
<div class="feature-item">
<i class="el-icon-data-line"></i>
<span>数据分析</span>
</div>
</div>
</div>
<div class="login-wrapper">
<UserLogin class="login" />
</div>
</div>
<p class="copyright">@ 2025-基于ChatGLM大模型的高校竞赛管理系统</p>
</div> </div>
</template> </template>
<script>
import UserLogin from '@/components/common/UserLogin.vue';
export default {
name: 'Login',
components: { UserLogin },
metaInfo: {
title: '登录',
},
};
</script>
<style scoped lang="stylus"> <style scoped lang="stylus">
.container .container
height 100vh height 100vh
background #f0f2f5 url('~@/assets/bg.svg') repeat
.login background-image url('https://www.sxhju.cn/images/weixintupian_20240717112809.jpg')
center() position relative
display flex
justify-content center
align-items center
overflow hidden
.content-wrapper
display flex
background rgba(255, 255, 255, 0.8)
backdrop-filter blur(10px)
border-radius 20px
box-shadow 0 10px 30px rgba(0, 0, 0, 0.1)
width 900px
height 560px
position relative
z-index 2
animation fadeIn 0.8s ease-out
.left-section
flex 1
padding 60px
background linear-gradient(135deg, #2196f3 0%, #00bcd4 100%)
border-radius 20px 0 0 20px
color white
display flex
flex-direction column
justify-content space-between
.welcome-text
font-size 32px
margin-bottom 20px
font-weight 300
.brand
margin-bottom 40px
.logo-container
display flex
align-items center
gap 15px
margin-bottom 15px
.logo
width 48px
height 48px
object-fit contain
.gradient-text
font-size 36px
background linear-gradient(45deg, #ffffff, #e0f7fa)
-webkit-background-clip text
color transparent
margin 0
font-weight 700
letter-spacing 1px
.brand-description
margin-top 20px
.main-slogan
font-size 20px
font-weight 500
margin-bottom 8px
color #ffffff
letter-spacing 0.5px
.sub-slogan
font-size 16px
color rgba(255, 255, 255, 0.8)
font-weight 300
letter-spacing 0.5px
display flex
align-items center
gap 15px
&::before, &::after
content ''
height 1px
flex 1
background linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent)
.slogan
font-size 18px
opacity 0.9
margin-top 10px
.features
display flex
flex-direction column
gap 20px
.feature-item
display flex
align-items center
gap 15px
font-size 18px
opacity 0.9
transition transform 0.3s ease
&:hover
transform translateX(10px)
i
font-size 24px
.login-wrapper
flex 1
padding 40px
display flex
align-items center
justify-content center
.shape
position absolute
border-radius 50%
opacity 0.1
animation float 20s infinite
.shape-1
width 300px
height 300px
top -150px
right -150px
background white
animation-delay -5s
.shape-2
width 200px
height 200px
bottom -100px
left -100px
background white
animation-delay -10s
.shape-3
width 150px
height 150px
top 50%
right -75px
background white
animation-delay -15s
.copyright
position fixed
bottom 20px
left 0
right 0
text-align center
color #666
font-size 14px
z-index 2
@keyframes float
0%, 100%
transform translate(0, 0)
50%
transform translate(20px, -20px)
@keyframes fadeIn
from
opacity 0
transform translateY(20px)
to
opacity 1
transform translateY(0)
@media (max-width: 992px)
.content-wrapper
width 95%
flex-direction column
height auto
.left-section
border-radius 20px 20px 0 0
padding 40px
.login-wrapper
padding 30px
.main-slogan
font-size 20px
font-weight 500
margin-bottom 8px
color #ffffff
letter-spacing 0.5px
</style> </style>
<script>
import UserLogin from "@/components/common/UserLogin.vue";
export default {
name: "Login",
components: { UserLogin },
metaInfo: {
title: "登录",
},
};
</script>