Compare commits
2 Commits
2cbcb14ff7
...
255f707e0c
Author | SHA1 | Date | |
---|---|---|---|
|
255f707e0c | ||
|
cd8148f65a |
@ -1,24 +1,25 @@
|
||||
import store from '@/store'; // 导入 Vuex store 实例
|
||||
import router from '@/router'; // 导入 Vue Router 实例
|
||||
import { notification } from 'ant-design-vue'; // 导入 ant-design-vue 的 notification 组件
|
||||
import NProgress from 'nprogress'; // 导入进度条组件
|
||||
import 'nprogress/nprogress.css'; // 导入进度条样式
|
||||
NProgress.configure({ showSpinner: false }); // 配置进度条,隐藏加载动画
|
||||
import store from '@/store';
|
||||
import router from '@/router';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
const loginPath = '/login'; // 登录路径
|
||||
const defaultRoutePath = '/race/list'; // 默认路由路径
|
||||
const whiteList = ['Login']; // 白名单,无需登录即可访问的页面
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
const loginPath = '/login';
|
||||
const defaultRoutePath = '/race/list';
|
||||
const whiteList = ['Login', 'Index']; // 白名单保持不变
|
||||
|
||||
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) {
|
||||
// alert(tokens)
|
||||
if (whiteList.includes(to.name)) { // 如果在白名单中,直接放行
|
||||
// 修改判断逻辑,同时检查路由名称和路径
|
||||
if (whiteList.includes(to.name) || to.path === '/Index') {
|
||||
next();
|
||||
} else { // 否则重定向至登录页面
|
||||
} else {
|
||||
next({
|
||||
path: loginPath,
|
||||
query: {
|
||||
@ -30,69 +31,59 @@ router.beforeEach(async(to, from, next) => {
|
||||
}
|
||||
|
||||
/* 已登录情况下访问登录界面的处理 */
|
||||
if (to.path === loginPath) { // 如果已登录且访问登录页面,则重定向至默认页面
|
||||
if (to.path === loginPath) {
|
||||
next(defaultRoutePath);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 已获取用户信息的情况 */
|
||||
if (store.getters.permissions!=null && store.getters.permissions!=="") { // 如果已获取用户权限信息,则直接放行
|
||||
if (store.getters.permissions != null && store.getters.permissions !== "") {
|
||||
next();
|
||||
NProgress.done(); // 结束进度条
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 未获取用户信息的情况 */
|
||||
try {
|
||||
await store.dispatch('initUser'); // 异步获取用户信息
|
||||
await store.dispatch('initUser');
|
||||
} catch (e) {
|
||||
store.commit('logout'); // 获取失败时执行退出登录操作
|
||||
next({ path: loginPath, query: { redirect: to.fullPath } }); // 重定向至登录页面
|
||||
store.commit('logout');
|
||||
next({ path: loginPath, query: { redirect: to.fullPath } });
|
||||
notification.error({
|
||||
message: '错误',
|
||||
description: '请求用户信息失败,请重试',
|
||||
}); // 显示错误通知
|
||||
NProgress.done(); // 结束进度条
|
||||
});
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 成功获取用户信息的情况 */
|
||||
// 检查路由权限
|
||||
if (!checkAccess(to)) { // 如果当前路由无权限访问,则重定向至默认页面
|
||||
if (!checkAccess(to)) {
|
||||
next({ path: defaultRoutePath, replace: true });
|
||||
NProgress.done(); // 结束进度条
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
const redirect = decodeURIComponent(from.query.redirect || to.path); // 获取重定向地址
|
||||
// 如果重定向地址与当前路径相同,则替换历史记录
|
||||
|
||||
const redirect = decodeURIComponent(from.query.redirect || to.path);
|
||||
if (redirect === to.path) {
|
||||
next({...to, replace: true });
|
||||
} else { // 否则正常重定向
|
||||
} else {
|
||||
next(redirect);
|
||||
}
|
||||
NProgress.done(); // 结束进度条
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done(); // 结束进度条
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
/**
|
||||
* 判断当前路由是否具有权限访问
|
||||
* @param {Route} route 路由对象
|
||||
* @returns {boolean} 是否具有权限
|
||||
*/
|
||||
|
||||
function checkAccess(route) {
|
||||
const userPrivileges = store.state.user.userPrivileges == null ? 1 : store.state.user.userPrivileges;
|
||||
const requiredPrivileges = route.meta.auth;
|
||||
console.log(userPrivileges);
|
||||
|
||||
const userPrivileges = store.state.user.userPrivileges==null ? 1 : store.state.user.userPrivileges; // 获取用户权限信息
|
||||
const requiredPrivileges = route.meta.auth; // 获取当前路由所需权限信息
|
||||
console.log(userPrivileges)
|
||||
// 如果路由未设置权限要求,则默认放行
|
||||
if (!requiredPrivileges) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查用户的权限是否包含在所需权限中
|
||||
return requiredPrivileges.includes(userPrivileges);
|
||||
}
|
@ -29,17 +29,10 @@
|
||||
<span>联系我们</span>
|
||||
<div class="link-hover-effect"></div>
|
||||
</a>
|
||||
<button class="cyber-button" onclick="window.location.href='/login'">
|
||||
<button class="cyber-button" @click="goToLogin">
|
||||
<span class="btn-text">登录</span>
|
||||
<span class="btn-glitch"></span>
|
||||
</button>
|
||||
<button
|
||||
class="cyber-button outline"
|
||||
onclick="window.location.href='/register'"
|
||||
>
|
||||
<span class="btn-text">注册</span>
|
||||
<span class="btn-glitch"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="menu-toggle">
|
||||
<div class="hamburger">
|
||||
@ -68,18 +61,12 @@
|
||||
<span class="typing-cursor">|</span>
|
||||
</p>
|
||||
<div class="index-cta-buttons animate-slide-up-delay-2">
|
||||
<button
|
||||
class="cyber-btn glow-button"
|
||||
onclick="window.location.href='/login'"
|
||||
>
|
||||
<button class="cyber-btn glow-button" @click="goToLogin">
|
||||
<span class="cyber-btn-text">开始使用</span>
|
||||
<span class="cyber-btn-glitch"></span>
|
||||
<span class="cyber-btn-glow"></span>
|
||||
</button>
|
||||
<button
|
||||
class="cyber-btn outline-button"
|
||||
onclick="window.scrollTo({top: document.querySelector('#features').offsetTop, behavior: 'smooth'})"
|
||||
>
|
||||
<button class="cyber-btn outline-button" @click="goToLogin">
|
||||
<span class="cyber-btn-text">了解更多</span>
|
||||
<span class="cyber-arrow">→</span>
|
||||
</button>
|
||||
@ -1618,169 +1605,13 @@ h6 {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
// 移动菜单控制
|
||||
const menuOpen = ref(false);
|
||||
|
||||
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,
|
||||
<script>
|
||||
export default {
|
||||
name: "Index",
|
||||
methods: {
|
||||
goToLogin() {
|
||||
this.$router.push("/login");
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
@ -1,26 +1,245 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="background-shapes">
|
||||
<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" />
|
||||
<p style="position: fixed;bottom: 0;text-align: center;left: 0;right: 0;font-family: '宋体';">@ 2024-青创先锋</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="copyright">@ 2025-基于ChatGLM大模型的高校竞赛管理系统</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserLogin from '@/components/common/UserLogin.vue';
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: { UserLogin },
|
||||
metaInfo: {
|
||||
title: '登录',
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
.container
|
||||
height 100vh
|
||||
background #f0f2f5 url('~@/assets/bg.svg') repeat
|
||||
.login
|
||||
center()
|
||||
|
||||
background-image url('https://www.sxhju.cn/images/weixintupian_20240717112809.jpg')
|
||||
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>
|
||||
|
||||
<script>
|
||||
import UserLogin from "@/components/common/UserLogin.vue";
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
components: { UserLogin },
|
||||
metaInfo: {
|
||||
title: "登录",
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user