Commit bcee74f8 authored by Administrator's avatar Administrator
Browse files

Re-designed login page, re-designed look&feel.

parent cf4bbfe0
<script setup>
import { computed } from 'vue'
import { useAuthStore } from './stores/auth'
import { useRoute } from 'vue-router'
import AppLayout from './components/AppLayout.vue'
const authStore = useAuthStore()
const isAuthenticated = computed(() => authStore.isAuthenticated)
const handleLogout = () => {
authStore.logout()
}
const route = useRoute()
const layout = computed(() => route.meta.layout || 'default')
</script>
<template>
<v-app>
<!-- 导航栏 -->
<v-app-bar v-if="isAuthenticated" app color="primary" dark>
<v-app-bar-title>学生管理系统</v-app-bar-title>
<v-spacer />
<v-btn
:to="{ name: 'home' }"
color="white"
variant="text"
>
主页
</v-btn>
<v-btn
:to="{ name: 'profile' }"
color="white"
variant="text"
>
个人资料
</v-btn>
<v-btn color="white" variant="text" @click="handleLogout"> 登出 </v-btn>
</v-app-bar>
<!-- 主要内容区域 -->
<v-main>
<router-view />
</v-main>
</v-app>
<component :is="layout === 'default' ? AppLayout : 'div'">
<router-view />
</component>
</template>
<style scoped>
.v-app-bar {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
</style>
<template>
<v-navigation-drawer
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
app
width="250"
>
<v-list-item title="Welcome to Students App" subtitle="powered by vuetify"></v-list-item>
<v-divider></v-divider>
<v-list>
<v-list-item link title="Home" @click="$router.push('/')"></v-list-item>
<v-list-item link title="Profile" @click="$router.push('/profile')"></v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script setup>
const props = defineProps({
modelValue: Boolean
})
const emit = defineEmits(['update:modelValue'])
</script>
<style scoped>
</style>
<template>
<v-footer app color="primary" dark :elevation="2">
<v-col class="text-center white--text">
&copy; 2025 Student App
</v-col>
</v-footer>
</template>
<script setup>
// 无需脚本
</script>
<style scoped>
</style>
<template>
<v-app-bar app :elevation="2">
<template v-slot:prepend>
<v-app-bar-nav-icon @click="$emit('toggle-drawer')"></v-app-bar-nav-icon>
</template>
<template v-slot:append>
<v-btn icon>
<v-icon>mdi-magnify</v-icon>
</v-btn>
<v-btn icon>
<v-icon>mdi-bell</v-icon>
</v-btn>
<v-btn icon @click="$emit('logout')">
<v-icon>mdi-logout</v-icon>
</v-btn>
</template>
<v-app-bar-title>Student Management Application</v-app-bar-title>
</v-app-bar>
</template>
<script setup>
</script>
<style scoped>
</style>
<template>
<v-app>
<v-img
src="/src/assets/images/login.jpg"
cover
height="100vh"
style="object-position: center top;"
>
<v-layout height="100vh" style="background: rgba(255, 255, 255, 0.85);">
<AppHeader :drawer-active="drawerActive" @toggle-drawer="toggleDrawer" @logout="showLogoutDialog = true" />
<AppDrawer v-model="drawerActive" />
<v-main>
<slot></slot>
</v-main>
<AppFooter />
</v-layout>
</v-img>
<!-- 登出确认对话框 -->
<v-dialog v-model="showLogoutDialog" max-width="400">
<v-card>
<v-card-title>确认登出</v-card-title>
<v-card-text>你确定要退出登录吗?</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" text @click="showLogoutDialog = false">取消</v-btn>
<v-btn color="primary" text @click="confirmLogout">确认</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-app>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import AppHeader from './AppHeader.vue'
import AppDrawer from './AppDrawer.vue'
import AppFooter from './AppFooter.vue'
import { useAuthStore } from '../stores/auth'
import { useRouter } from 'vue-router'
const auth = useAuthStore()
const router = useRouter()
const drawerActive = ref(false)
const toggleDrawer = () => { drawerActive.value = !drawerActive.value }
const showLogoutDialog = ref(false)
const confirmLogout = () => {
showLogoutDialog.value = false
handleLogout()
}
const handleLogout = () => {
// 1. 调用 Pinia 的登出方法
auth.logout && auth.logout()
// 2. 清除 localStorage/sessionStorage(如有)
localStorage.removeItem('token')
sessionStorage.removeItem('token')
// 3. 跳转到登录页
router.push('/login')
}
// 登录后自动展开 Drawer
onMounted(() => {
drawerActive.value = true
})
</script>
<style scoped>
</style>
......@@ -11,19 +11,19 @@ const router = createRouter({
path: '/',
name: 'home',
component: HomeView,
meta: { requiresAuth: true },
meta: { requiresAuth: true, layout: 'default' },
},
{
path: '/login',
name: 'login',
component: LoginView,
meta: { requiresAuth: false },
meta: { requiresAuth: false, layout: 'none' },
},
{
path: '/profile',
name: 'profile',
component: ProfileView,
meta: { requiresAuth: true },
meta: { requiresAuth: true, layout: 'default' },
},
{
path: '/:pathMatch(.*)*',
......@@ -42,11 +42,19 @@ router.beforeEach(async (to, from, next) => {
return
}
// 对受保护页面,若即将过期,尝试静默刷新一次(不强制等待,可选:等待)
if (to.meta.requiresAuth && authStore.isAccessTokenExpiringSoon && authStore.isAccessTokenExpiringSoon()) {
// 只有已认证用户才尝试刷新token,且不在/login页面
if (
to.meta.requiresAuth &&
to.path !== '/login' &&
isAuthed &&
authStore.isAccessTokenExpiringSoon &&
authStore.isAccessTokenExpiringSoon()
) {
try {
await authStore.refreshAccessToken()
} catch {
// 刷新失败,清理token并跳转到登录
if (authStore.logout) authStore.logout()
next('/login')
return
}
......
......@@ -9,11 +9,11 @@ const router = useRouter()
<v-row>
<v-col cols="12">
<v-card>
<v-card-title class="text-h4"> 欢迎来到学生管理系统 </v-card-title>
<v-card-title class="text-h4"> Welcome to student management application </v-card-title>
<v-card-text>
<p class="text-body-1">这是一个基于Vue 3和Vuetify构建的现代化学生管理系统。</p>
<p class="text-body-1">Powered by Vue 3 and Vuetify</p>
<v-alert type="info" variant="tonal" class="mt-4">
当前功能正在开发中,更多功能即将推出。
Features built in progress ...
</v-alert>
</v-card-text>
<v-card-actions>
......
<script setup>
import { ref } from 'vue'
import { useAuthStore } from '../stores/auth'
import loginImage from '@/assets/images/login.jpg'
const authStore = useAuthStore()
const username = ref('')
......@@ -35,48 +36,70 @@ const handleLogin = async () => {
</script>
<template>
<v-container fluid fill-height>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<v-card class="elevation-12">
<v-toolbar color="primary" dark flat>
<v-toolbar-title>学生管理系统 - 登录</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-alert v-if="error" type="error" variant="tonal" class="mb-4">
{{ error }}
</v-alert>
<v-form @submit.prevent="handleLogin">
<v-img
src="/src/assets/images/login.jpg"
cover
height="100vh"
style="object-position: center top;"
>
<v-container class="fill-height" fluid>
<v-row
align="center"
justify="center"
class="fill-height"
no-gutters
>
<!-- 登录表单 -->
<v-col cols="12" md="4" class="">
<v-sheet
elevation="10"
rounded="xl"
class="pa-8 h-100 justify-center align-center flex-grow-1"
style="background: rgba(255, 255, 255, 0.95);"
>
<form @submit.prevent="handleLogin" style="width: 100%;">
<div class="text-h4 font-weight-bold mb-4">Welcome!</div>
<div class="text-body-1">
欢迎使用学生管理系统!本系统是用来追踪学生课程、作业、考试的一站式服务平台。请登录使用全部功能。
</div>
<v-row justify="center" class="pa-6">
<v-avatar size="128" class="mb-4" >
<v-img src="/src/assets/images/loginAvatar.jpg" />
</v-avatar>
</v-row>
<v-text-field
v-model="username"
label="用户名"
name="username"
prepend-icon="mdi-account"
type="text"
required
prepend-inner-icon="mdi-account"
class="mb-4"
variant="outlined"
density="comfortable"
/>
<v-text-field
v-model="password"
label="密码"
name="password"
prepend-icon="mdi-lock"
type="password"
required
prepend-inner-icon="mdi-lock"
class="mb-6"
variant="outlined"
density="comfortable"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" :loading="loading" @click="handleLogin"> 登录 </v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
<v-btn
color="primary"
block
large
:loading="loading"
type="submit"
>
登录
</v-btn>
</form>
</v-sheet>
</v-col>
</v-row>
</v-container>
</v-img>
</template>
<style scoped>
.v-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
</style>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment