You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 lines
11 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="login-container">
<AuthLogo />
<AuthBackground />
<div class="login-content">
<div class="login-box">
<div class="login-header">
<div class="logo-area">
<div class="logo-icon">
<el-icon :size="40"><Box /></el-icon>
</div>
<h1 class="logo-text">视觉管理平台</h1>
</div>
<p class="subtitle">智能 · 高效 · 可视化</p>
</div>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
size="large"
>
<el-form-item prop="userName">
<el-input
v-model="loginForm.userName"
placeholder="请输入账号"
prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="userPwd">
<el-input
v-model="loginForm.userPwd"
type="password"
placeholder="请输入密码"
prefix-icon="Lock"
show-password
@keyup.enter="handleLogin"
/>
</el-form-item>
<div class="form-options">
<el-checkbox v-model="rememberMe">记住密码</el-checkbox>
<a href="#" class="forgot-link">忘记密码?</a>
</div>
<el-button
type="primary"
:loading="loading"
class="login-btn"
@click="handleLogin"
>
{{ loading ? '登录中...' : '登 录' }}
</el-button>
</el-form>
<div class="register-tip">
还没有账号?
<router-link to="/register" class="register-link">立即注册</router-link>
</div>
</div>
<div class="intro-panel">
<h2 class="intro-title">欢迎使用智能视觉管理平台</h2>
<p class="intro-desc">
打造高效、智能、可视化的容器管理体验<br>
简化运维流程,提升集群性能
</p>
<div class="feature-grid">
<div class="feature-item">
<div class="feature-icon">
<el-icon><Monitor /></el-icon>
</div>
<div class="feature-info">
<h4>实时监控</h4>
<p>全方位监控集群状态</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">
<el-icon><Setting /></el-icon>
</div>
<div class="feature-info">
<h4>智能调度</h4>
<p>自动化资源分配</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">
<el-icon><DataLine /></el-icon>
</div>
<div class="feature-info">
<h4>数据分析</h4>
<p>深度洞察集群性能</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/stores/user'
import { newPodApi } from '@/api/user'
import md5 from 'js-md5'
import sha256 from 'js-sha256'
import AuthLogo from '@/components/AuthLogo.vue'
import AuthBackground from '@/components/AuthBackground.vue'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const loginFormRef = ref(null)
const loading = ref(false)
const rememberMe = ref(false)
const loginForm = reactive({
userName: '',
userPwd: ''
})
// 记住密码有效期7天
const REMEMBER_EXPIRY_DAYS = 7
// 检查并加载保存的账号密码
const loadSavedCredentials = () => {
const savedData = localStorage.getItem('rememberedCredentials')
if (savedData) {
try {
const { userName, userPwd, expiryTime } = JSON.parse(savedData)
if (Date.now() < expiryTime) {
loginForm.userName = userName
loginForm.userPwd = userPwd
rememberMe.value = true
} else {
localStorage.removeItem('rememberedCredentials')
}
} catch (e) {
localStorage.removeItem('rememberedCredentials')
}
}
}
// 保存账号密码
const saveCredentials = () => {
const expiryTime = Date.now() + REMEMBER_EXPIRY_DAYS * 24 * 60 * 60 * 1000
localStorage.setItem('rememberedCredentials', JSON.stringify({
userName: loginForm.userName,
userPwd: loginForm.userPwd,
expiryTime
}))
}
// 清除保存的账号密码
const clearCredentials = () => {
localStorage.removeItem('rememberedCredentials')
}
onMounted(() => {
loadSavedCredentials()
})
const loginRules = {
userName: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
userPwd: [
{ required: true, message: '请输入密码', trigger: 'blur' }
]
}
const generateRandomString = (length = 6) => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
let result = ''
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
return result
}
const handleLogin = async () => {
// const random = generateRandomString()
// const podLogin = await newPodApi({
// "session": 0,
// "id": 2,
// "call": {
// "service": "rpc",
// "method": "login"
// },
// "params": {
// "userName": "admin",
// "password": sha256(md5('abcd1234')+random),
// "random": random,
// "ip": "127.0.0.1",
// "port": 80,
// "encryptType": 1
// }
// })
// return;
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
if (valid) {
loading.value = true
try {
const res = await userStore.login(loginForm)
if (res.success) {
// 处理记住密码
if (rememberMe.value) {
saveCredentials()
} else {
clearCredentials()
}
ElMessage.success('登录成功')
const redirect = route.query.redirect || '/home'
router.push(redirect)
}
} catch (error) {
ElMessage.error(error.message || '登录失败')
} finally {
loading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
@use '@/styles/variables.scss' as *;
@use '@/styles/mixin.scss' as *;
.login-container {
position: relative;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.login-content {
position: relative;
z-index: 10;
display: flex;
width: 1000px;
height: 600px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.1);
overflow: hidden;
backdrop-filter: blur(20px);
}
.login-box {
width: 420px;
padding: 60px 50px;
display: flex;
flex-direction: column;
justify-content: center;
background: #fff;
.login-header {
text-align: center;
margin-bottom: 40px;
.logo-area {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 12px;
.logo-icon {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, $primary-color 0%, #764ba2 100%);
border-radius: 16px;
color: #fff;
box-shadow: 0 8px 20px rgba($primary-color, 0.3);
}
.logo-text {
font-size: 24px;
font-weight: 700;
color: $text-primary;
margin: 0;
}
}
.subtitle {
font-size: 14px;
color: $text-secondary;
margin: 0;
letter-spacing: 4px;
}
}
.login-form {
:deep(.el-form-item) {
margin-bottom: 24px;
}
:deep(.el-input__wrapper) {
padding: 14px 16px;
border-radius: 10px;
box-shadow: 0 0 0 1px $border-base;
transition: all 0.3s;
&:hover,
&.is-focus {
box-shadow: 0 0 0 2px rgba($primary-color, 0.3);
}
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
:deep(.el-checkbox__label) {
color: $text-regular;
font-size: 14px;
}
.forgot-link {
color: $primary-color;
font-size: 14px;
text-decoration: none;
transition: color 0.3s;
&:hover {
color: darken($primary-color, 10%);
}
}
}
.login-btn {
width: 100%;
height: 48px;
font-size: 16px;
font-weight: 600;
border-radius: 10px;
border: none;
background: linear-gradient(135deg, $primary-color 0%, #764ba2 100%);
box-shadow: 0 8px 20px rgba($primary-color, 0.3);
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 12px 28px rgba($primary-color, 0.4);
}
&:active {
transform: translateY(0);
}
}
}
.register-tip {
text-align: center;
margin-top: 28px;
font-size: 14px;
color: $text-secondary;
.register-link {
color: $primary-color;
font-weight: 600;
text-decoration: none;
margin-left: 4px;
transition: color 0.3s;
&:hover {
color: darken($primary-color, 10%);
}
}
}
}
.intro-panel {
flex: 1;
padding: 60px;
display: flex;
flex-direction: column;
justify-content: center;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #fff;
.intro-title {
font-size: 28px;
font-weight: 700;
margin: 0 0 16px 0;
background: linear-gradient(135deg, #fff 0%, rgba(255, 255, 255, 0.8) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.intro-desc {
font-size: 16px;
line-height: 1.8;
color: rgba(255, 255, 255, 0.7);
margin: 0 0 50px 0;
}
.feature-grid {
display: flex;
flex-direction: column;
gap: 24px;
.feature-item {
display: flex;
align-items: center;
gap: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s;
&:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateX(8px);
}
.feature-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, $primary-color 0%, #764ba2 100%);
border-radius: 12px;
font-size: 24px;
color: #fff;
box-shadow: 0 4px 12px rgba($primary-color, 0.3);
}
.feature-info {
h4 {
font-size: 16px;
font-weight: 600;
margin: 0 0 4px 0;
color: #fff;
}
p {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
margin: 0;
}
}
}
}
}
</style>