Files
xinfra/frontend/src/views/auth/Login.vue
T
wonder 3de59c4fc2 feat(frontend): 对接后端统一响应格式,增强错误处理
- 新增 types/api.ts,定义 ApiResponse/PaginatedData 泛型类型和业务错误码常量
- 增强 request.ts 拦截器:提取后端错误信息、映射 HTTP 状态码和业务错误码为中文提示
- 为 auth/audit/subsystem API 添加泛型返回类型
- 为各视图添加 try/catch 防止未处理的 Promise 拒绝
2026-07-15 10:52:41 +08:00

199 lines
4.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="login-container">
<div class="login-card">
<div class="login-header">
<div class="brand">
<div class="mark">xi</div>
xinfra
</div>
<h2>统一基础设施平台</h2>
<p>LDAP 账号登录</p>
</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
@submit.prevent="handleLogin"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户名"
prefix-icon="User"
size="large"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入密码"
prefix-icon="Lock"
size="large"
show-password
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
:loading="loading"
@click="handleLogin"
style="width: 100%"
>
登录
</el-button>
</el-form-item>
</el-form>
<div class="login-footer">
<p>统一 LDAP 账号,同账号同密码</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useAuth } from '@/composables/useAuth'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
const router = useRouter()
const { login } = useAuth()
const formRef = ref<FormInstance>()
const loading = ref(false)
const form = reactive({
username: '',
password: '',
})
const rules: FormRules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
}
const handleLogin = async () => {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
await login(form.username, form.password)
ElMessage.success('登录成功')
router.push('/')
} catch {
// 错误已由 request 拦截器统一处理
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-deep);
}
.login-card {
width: 400px;
background: var(--bg-panel);
border: 1px solid var(--line);
border-radius: 12px;
padding: 40px;
}
.login-header {
text-align: center;
margin-bottom: 32px;
}
.login-header .brand {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-size: 24px;
font-weight: 700;
margin-bottom: 12px;
}
.login-header .brand .mark {
width: 36px;
height: 36px;
border-radius: 8px;
background: linear-gradient(135deg, var(--accent), #1C8F69);
display: flex;
align-items: center;
justify-content: center;
font-family: var(--mono);
font-size: 16px;
font-weight: 600;
color: #08140F;
}
.login-header h2 {
margin: 0 0 8px;
font-size: 18px;
font-weight: 600;
}
.login-header p {
margin: 0;
color: var(--text-dim);
font-size: 14px;
}
.login-footer {
text-align: center;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--line-soft);
}
.login-footer p {
margin: 0;
color: var(--text-dim);
font-size: 12px;
}
:deep(.el-form-item__label) {
color: var(--text-mid);
}
:deep(.el-input__wrapper) {
background: var(--bg-panel-2);
border: 1px solid var(--line);
box-shadow: none;
}
:deep(.el-input__wrapper:hover) {
border-color: #3A4356;
}
:deep(.el-input__wrapper.is-focus) {
border-color: var(--accent);
}
:deep(.el-input__inner) {
color: var(--text-hi);
}
:deep(.el-button--primary) {
background: var(--accent);
border-color: var(--accent);
color: #06150F;
font-weight: 600;
}
:deep(.el-button--primary:hover) {
background: #55E8B3;
}
</style>