Files
mokeegateway/mokee-gateway-web/backup-on-demand/views-20260612-2236/login/LoginView.vue
2026-07-15 16:13:27 +08:00

282 lines
6.4 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-page">
<div class="login-container">
<!-- 左侧品牌区 -->
<div class="login-brand">
<div class="brand-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<h1 class="brand-title">Mokee Gateway</h1>
<p class="brand-desc">统一登录网关管理平台</p>
<div class="brand-features">
<div class="feature-item">
<span class="feature-dot" />
<span>统一身份认证</span>
</div>
<div class="feature-item">
<span class="feature-dot" />
<span>智能网关转发</span>
</div>
<div class="feature-item">
<span class="feature-dot" />
<span>细粒度权限管控</span>
</div>
</div>
</div>
<!-- 右侧登录表单 -->
<div class="login-form-panel">
<div class="form-header">
<h2>欢迎回来</h2>
<p>请输入账号密码登录系统</p>
</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
@keyup.enter="handleLogin"
>
<el-form-item prop="username">
<el-input
v-model="form.username"
placeholder="用户名"
:prefix-icon="User"
size="large"
/>
</el-form-item>
<el-form-item 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"
class="submit-btn"
@click="handleLogin"
>
</el-button>
</el-form-item>
</el-form>
</div>
</div>
<footer class="login-footer">
&copy; 2026 Mokee Gateway
</footer>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { User, Lock } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const authStore = useAuthStore()
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' }],
}
async function handleLogin() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
await authStore.login(form.username, form.password)
// PROD: 登录后切到主应用域名,避免从 login 域名加载 JS
const webUrl = import.meta.env.VITE_WEB_URL
if (webUrl && webUrl !== window.location.origin) {
window.location.href = webUrl + '/select'
} else {
router.push('/select')
}
} catch (e: any) {
// 响应拦截器已展示通用错误(如 401/403/429
// 此处处理拦截器未覆盖的业务异常信息
const msg = e?.response?.data?.msg || e?.message
if (msg && msg !== '请求失败') ElMessage.error(msg)
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
background: #f8fafc;
padding: 24px;
}
.login-container {
display: flex;
width: 880px;
max-width: 100%;
min-height: 480px;
background: #fff;
border-radius: 20px;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.04),
0 20px 60px -20px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* ===== 左侧品牌区 ===== */
.login-brand {
flex: 1;
background: linear-gradient(160deg, #4f46e5 0%, #6366f1 40%, #818cf8 100%);
padding: 48px 40px;
display: flex;
flex-direction: column;
justify-content: center;
color: #fff;
}
.brand-icon {
width: 52px;
height: 52px;
background: rgba(255, 255, 255, 0.18);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 28px;
backdrop-filter: blur(10px);
}
.brand-icon svg {
width: 28px;
height: 28px;
}
.brand-title {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.5px;
margin-bottom: 6px;
}
.brand-desc {
font-size: 14px;
opacity: 0.8;
font-weight: 400;
margin-bottom: 40px;
}
.brand-features {
display: flex;
flex-direction: column;
gap: 14px;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
opacity: 0.85;
}
.feature-dot {
width: 6px;
height: 6px;
background: rgba(255, 255, 255, 0.6);
border-radius: 50%;
flex-shrink: 0;
}
/* ===== 右侧登录表单 ===== */
.login-form-panel {
flex: 1;
padding: 48px 44px;
display: flex;
flex-direction: column;
justify-content: center;
}
.form-header {
margin-bottom: 32px;
}
.form-header h2 {
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 6px;
letter-spacing: -0.3px;
}
.form-header p {
font-size: 14px;
color: var(--text-muted);
}
.submit-btn {
width: 100%;
height: 46px;
font-size: 15px;
letter-spacing: 3px;
border-radius: var(--radius-lg);
margin-top: 4px;
}
.login-footer {
margin-top: 28px;
color: var(--text-muted);
font-size: 12px;
}
/* ===== 响应式 ===== */
@media (max-width: 768px) {
.login-page { padding: 0; justify-content: flex-start; }
.login-container {
flex-direction: column;
min-height: 100vh;
border-radius: 0;
box-shadow: none;
width: 100%;
}
.login-brand {
padding: 40px 24px 24px;
text-align: center;
align-items: center;
}
.brand-icon { width: 44px; height: 44px; margin-bottom: 16px; }
.brand-icon svg { width: 22px; height: 22px; }
.brand-title { font-size: 22px; }
.brand-features { display: none; }
.brand-desc { margin-bottom: 0; }
.login-form-panel { padding: 24px 24px 40px; }
.form-header h2 { font-size: 20px; }
.login-footer { display: none; }
}
</style>