登录界面调整

This commit is contained in:
dxc
2026-02-04 15:41:51 +08:00
parent ea17413bc1
commit c1c525b699
5 changed files with 73 additions and 34 deletions

View File

@ -28,8 +28,11 @@ def login():
return jsonify(response_data), 200 return jsonify(response_data), 200
except ValueError as ve: except ValueError as ve:
# [修改] 捕获业务逻辑错误(如密码错误、用户不存在),返回 401 Unauthorized
return jsonify({'msg': str(ve)}), 401 return jsonify({'msg': str(ve)}), 401
except Exception as e: except Exception as e:
# [修改] 捕获系统级错误(如数据库连接失败),返回 500 Internal Server Error
current_app.logger.error(f"Login Failed Error: {str(e)}") current_app.logger.error(f"Login Failed Error: {str(e)}")
return jsonify({'msg': f'服务器内部错误: {str(e)}'}), 500 return jsonify({'msg': f'服务器内部错误: {str(e)}'}), 500

View File

@ -30,16 +30,22 @@ class AuthService:
'department': 'System' 'department': 'System'
} }
else: else:
raise Exception("密码错误") # [修改] 使用 ValueError 表示认证失败
raise ValueError("密码错误")
# 2. 如果不是 IRIS检查数据库用户 # 2. 如果不是 IRIS检查数据库用户
else: else:
user = SysUser.query.filter_by(username=username).first() user = SysUser.query.filter_by(username=username).first()
if not user or not user.check_password(password):
raise Exception("用户名或密码错误") # [修改] 分开判断,逻辑更清晰,且使用 ValueError
if not user:
raise ValueError("用户不存在")
if not user.check_password(password):
raise ValueError("密码错误")
if user.status != 'active': if user.status != 'active':
raise Exception("账号已被禁用") raise ValueError("账号已被禁用,请联系管理员")
user_role = user.role user_role = user.role
user_id = user.id user_id = user.id
@ -75,7 +81,7 @@ class AuthService:
if role not in valid_roles: if role not in valid_roles:
raise Exception(f"角色无效,可选角色: {valid_roles}") raise Exception(f"角色无效,可选角色: {valid_roles}")
# 处理 Email 为空的情况,避免违反 Unique 约束 (视数据库设置而定,这里简单处理为空串) # 处理 Email 为空的情况
email = data.get('email', '') email = data.get('email', '')
if email and SysUser.query.filter_by(email=email).first(): if email and SysUser.query.filter_by(email=email).first():
raise Exception("邮箱已被使用") raise Exception("邮箱已被使用")

View File

@ -1,12 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router' import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { InfoFilled, SwitchButton, UserFilled } from '@element-plus/icons-vue' import { InfoFilled, SwitchButton, UserFilled } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
const router = useRouter() const router = useRouter()
const route = useRoute() // [新增] 获取当前路由对象
const userStore = useUserStore() const userStore = useUserStore()
// [新增] 计算属性:判断当前是否是登录页
const isLoginPage = computed(() => {
return route.path === '/login'
})
// --- 退出登录逻辑 Start --- // --- 退出登录逻辑 Start ---
const handleLogout = () => { const handleLogout = () => {
ElMessageBox.confirm( ElMessageBox.confirm(
@ -28,9 +35,7 @@ const handleLogout = () => {
message: '已安全退出', message: '已安全退出',
}) })
// 3. [关键修改] 强制跳转回登录页 // 3. 强制跳转回登录页
// 使用 replace这样用户点浏览器“返回”按钮不会又回到系统里
// 此时 store.token 已为空,路由守卫会放行 /login
await router.replace('/login') await router.replace('/login')
}) })
.catch(() => { .catch(() => {
@ -42,7 +47,7 @@ const handleLogout = () => {
<template> <template>
<div class="app-wrapper"> <div class="app-wrapper">
<header class="app-header"> <header v-if="!isLoginPage" class="app-header">
<div class="logo-container"> <div class="logo-container">
<router-link to="/" class="home-link"> <router-link to="/" class="home-link">
<img src="@/assets/iris.png" class="logo" alt="Logo" /> <img src="@/assets/iris.png" class="logo" alt="Logo" />
@ -74,7 +79,7 @@ const handleLogout = () => {
<router-view /> <router-view />
</main> </main>
<footer class="app-footer"> <footer v-if="!isLoginPage" class="app-footer">
<span class="version-tag"> <span class="version-tag">
<el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon> <el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon>
当前版本: 1.0 Beta (测试版) 当前版本: 1.0 Beta (测试版)
@ -84,7 +89,6 @@ const handleLogout = () => {
</template> </template>
<style> <style>
/* 保持原有的样式,不需要改动 */
.app-wrapper { .app-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -14,13 +14,12 @@ export const useUserStore = defineStore('user', () => {
try { try {
const res = await login(loginForm) const res = await login(loginForm)
// [调试日志] 查看实际返回的数据结构 (调试完成后可删除) // [调试日志] 查看实际返回的数据结构
console.log('Login API Response:', res) console.log('Login API Response:', res)
// ============================================================ // ============================================================
// [关键修复] 兼容 Axios 拦截器的不同处理方式 // [关键修复] 兼容 Axios 拦截器的不同处理方式
// 如果拦截器已经返回了 response.data那么 res 本身就是数据对象 // 如果拦截器已经返回了 response.data那么 res 本身就是数据对象
// 如果拦截器返回的是原始 response那么数据在 res.data 中
// ============================================================ // ============================================================
const data = res.data || res const data = res.data || res
@ -46,9 +45,10 @@ export const useUserStore = defineStore('user', () => {
// 持久化存储 Token // 持久化存储 Token
localStorage.setItem('token', data.access_token) localStorage.setItem('token', data.access_token)
return true return true // 返回 true 表示登录成功
} catch (error) { } catch (error) {
console.error('Login failed:', error) console.error('Login failed:', error)
// 返回 false 表示登录失败Login 组件会据此停止跳转
return false return false
} }
} }
@ -61,13 +61,9 @@ export const useUserStore = defineStore('user', () => {
username.value = '' username.value = ''
// 2. 清空 LocalStorage (硬盘) // 2. 清空 LocalStorage (硬盘)
// 建议使用 removeItem 而不是 clear避免误删该域名下其他非登录数据
localStorage.removeItem('token') localStorage.removeItem('token')
localStorage.removeItem('role') localStorage.removeItem('role')
localStorage.removeItem('username') localStorage.removeItem('username')
// 注意:这里不再执行 window.location.reload()
// 而是把跳转控制权交给调用者 (如 App.vue 中的 router.push)
} }
// 3. Getters / Helpers // 3. Getters / Helpers

View File

@ -12,6 +12,7 @@
:model="loginForm" :model="loginForm"
:rules="loginRules" :rules="loginRules"
size="large" size="large"
@submit.prevent
> >
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
@ -20,6 +21,7 @@
:prefix-icon="User" :prefix-icon="User"
/> />
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input
v-model="loginForm.password" v-model="loginForm.password"
@ -27,11 +29,17 @@
placeholder="请输入密码" placeholder="请输入密码"
:prefix-icon="Lock" :prefix-icon="Lock"
show-password show-password
@keyup.enter="onLogin" @keydown.enter.prevent="onLogin"
/> />
</el-form-item> </el-form-item>
<el-button type="primary" :loading="loading" class="w-100" @click="onLogin"> <el-button
type="primary"
native-type="button"
:loading="loading"
class="w-100"
@click="onLogin"
>
立即登录 立即登录
</el-button> </el-button>
@ -44,7 +52,7 @@
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus' import { ElMessageBox } from 'element-plus' // 引入 ElMessageBox
import { User, Lock } from '@element-plus/icons-vue' import { User, Lock } from '@element-plus/icons-vue'
const router = useRouter() const router = useRouter()
@ -61,15 +69,43 @@ const loginRules = {
const onLogin = async () => { const onLogin = async () => {
if (!loginFormRef.value) return if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid: boolean) => { await loginFormRef.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
loading.value = true loading.value = true
try {
// 执行登录请求
const success = await userStore.handleLogin(loginForm) const success = await userStore.handleLogin(loginForm)
loading.value = false
if (success) { if (success) {
ElMessage.success('登录成功') // 成功:跳转
router.push('/dashboard') // 登录后跳转首页 router.push('/dashboard')
} else {
// 失败(业务逻辑拒绝,如账号密码错):弹出模态框
showLoginFailAlert('用户名或密码错误')
} }
} catch (error: any) {
// 失败(系统错误,如网络断开/500报错弹出模态框
// 优先取后端的报错信息,没有则显示默认
const msg = error.response?.data?.msg || error.message || '登录遇到未知错误'
showLoginFailAlert(msg)
} finally {
// 停止转圈,让用户可以看清弹窗
loading.value = false
}
}
})
}
// 封装错误弹窗
const showLoginFailAlert = (msg: string) => {
ElMessageBox.alert(msg, '登录失败', {
confirmButtonText: '确定',
type: 'error',
callback: () => {
// 点击确定后,清空密码框,让用户重试
// 页面绝对不会刷新,光标还在
loginForm.password = ''
} }
}) })
} }
@ -81,7 +117,7 @@ const onLogin = async () => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh;
background-color: #2d3a4b; /* 深色背景 */ background-color: #2d3a4b;
} }
.login-card { .login-card {
width: 400px; width: 400px;
@ -94,10 +130,4 @@ const onLogin = async () => {
.w-100 { .w-100 {
width: 100%; width: 100%;
} }
.tips {
margin-top: 15px;
font-size: 12px;
color: #999;
text-align: center;
}
</style> </style>