IM/frontend/web/src/views/auth/Login.vue

327 lines
7.2 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-layout">
<div class="login-card">
<div class="side-visual">
<div class="brand-container">
<h1 class="hero-title">Work<br>Together.</h1>
<p class="hero-subtitle">下一代企业级即时通讯平台让沟通无距离</p>
</div>
<div class="visual-footer">
<span>© 2025 IM System</span>
<div class="dots">
<span></span><span></span><span></span>
</div>
</div>
</div>
<div class="side-form">
<div class="form-wrapper">
<div class="welcome-header">
<h2>账号登录</h2>
<p>请输入您的工作账号以继续</p>
</div>
<form @submit.prevent="handleLogin">
<IconInput class="input"
placeholder="请输入用户名" lab="用户名 / 邮箱" type="text" icon-name="user" v-model="form.username"/>
<IconInput class="input"
placeholder="请输入密码" lab="密码" type="password" icon-name="lock" v-model="form.password"/>
<div class="login-btn-wrapper">
<MyButton variant="pill" class="login-btn" :loading="loading">
登录
</MyButton>
</div>
</form>
<div class="register-hint">
还没有账号? <router-link to="/auth/register">立即注册</router-link>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { useMessage } from '@/components/messages/useAlert'
import { authService } from '@/services/auth'
import { useRouter } from 'vue-router'
import feather from 'feather-icons'
import IconInput from '@/components/IconInput.vue'
import MyButton from '@/components/MyButton.vue'
import { required, maxLength, helpers } from '@vuelidate/validators'
import useVuelidate from '@vuelidate/core'
import { useAuthStore } from '@/stores/auth'
const message = useMessage();
const router = useRouter();
const authStore = useAuthStore();
const loading = ref(false)
const form = reactive({
username: '',
password: ''
})
const rules = {
username:{
required:helpers.withMessage('用户名不能为空', required),
maxLength:helpers.withMessage('用户名最大20字符', maxLength(20))
},
password:{
required:helpers.withMessage('密码不能为空', required),
maxLength:helpers.withMessage('密码最大50字符', maxLength(50))
}
};
const v$ = useVuelidate(rules,form);
const handleLogin = async () => {
const isFormCorrect = await v$.value.$validate()
if (!isFormCorrect) {
if (v$.value.$errors.length > 0) {
message.error(v$.value.$errors[0].$message)
}
return
}
try{
loading.value = true;
const res = await authService.login(form);
if(res.code === 0){ // Assuming 0 is success
message.success('登录成功')
authStore.setLoginInfo(res.data.token, res.data.refreshToken, res.data.userInfo);
router.push('/')
}else{
message.error(res.message || '登录失败')
}
} catch (e) {
console.error(e)
message.error('登录请求异常')
} finally{
loading.value = false;
}
}
onMounted(() => {
feather.replace()
})
</script>
<style scoped>
/* Soft Mesh Gradient Background */
.login-layout {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
min-height: 100vh;
background-color: #f8fafc;
background-image:
radial-gradient(at 0% 0%, hsla(190, 100%, 95%, 1) 0, transparent 50%),
radial-gradient(at 50% 0%, hsla(160, 100%, 96%, 1) 0, transparent 50%),
radial-gradient(at 100% 0%, hsla(210, 100%, 96%, 1) 0, transparent 50%);
overflow: hidden;
position: relative;
}
/* Very subtle grid overlay */
.login-layout::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: radial-gradient(rgba(0,0,0,0.02) 1px, transparent 1px);
background-size: 20px 20px;
z-index: 0;
}
.login-card {
display: flex;
width: 1000px;
height: 600px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(30px);
border-radius: 32px;
box-shadow:
0 10px 25px -5px rgba(0, 0, 0, 0.02),
0 40px 100px -20px rgba(0, 0, 0, 0.08);
overflow: hidden;
z-index: 10;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.7);
}
.side-visual {
flex: 1;
/* Soft connectivity gradient */
background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%);
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
padding: 60px;
color: white;
overflow: hidden;
}
/* Abstract "Connection" Circles */
.side-visual::before {
content: '';
position: absolute;
top: -20%; left: -20%;
width: 400px; height: 400px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 60%);
z-index: 1;
}
.side-visual::after {
content: '';
position: absolute;
bottom: -10%; right: -10%;
width: 300px; height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 60%);
z-index: 1;
}
.brand-container {
position: relative;
z-index: 2;
}
.hero-title {
font-size: 44px;
font-weight: 800;
line-height: 1.2;
margin-bottom: 20px;
text-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.hero-subtitle {
font-size: 16px;
opacity: 0.95;
line-height: 1.6;
max-width: 340px;
font-weight: 400;
}
.visual-footer {
position: absolute;
bottom: 40px;
left: 60px;
right: 60px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
opacity: 0.8;
z-index: 2;
}
.dots span {
display: inline-block;
width: 6px; height: 6px;
background: white;
border-radius: 50%;
margin-left: 6px;
opacity: 0.7;
}
.side-form {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
background: #fff;
}
.form-wrapper {
width: 100%;
max-width: 340px;
}
.welcome-header {
margin-bottom: 30px;
text-align: center;
}
.welcome-header h2 {
font-size: 26px;
font-weight: 700;
color: #1e293b;
margin-bottom: 8px;
}
.welcome-header p {
color: #64748b;
font-size: 14px;
}
.input {
width: 100%;
margin-bottom: 20px;
}
.login-btn-wrapper {
display: flex;
justify-content: center;
width: 100%;
margin-top: 32px;
}
.register-hint {
margin-top: 24px;
text-align: center;
font-size: 13px;
color: #64748b;
}
.register-hint a {
color: #2563eb;
font-weight: 600;
text-decoration: none;
}
.register-hint a:hover {
text-decoration: underline;
}
/* Response Design */
@media (max-width: 960px) {
.login-card {
flex-direction: column;
width: 90%;
margin: 20px;
height: auto;
border-radius: 16px;
}
.side-visual {
padding: 30px;
min-height: 160px;
background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
}
.hero-title { font-size: 28px; }
.hero-subtitle, .visual-footer { display: none; }
.side-form { padding: 40px 20px; }
}
</style>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: #f0f4f8; /* Fallback */
}
</style>