登录界面调整
This commit is contained in:
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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("邮箱已被使用")
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
Reference in New Issue
Block a user