feat: 更新登录注册页面和主布局样式

master
钟良源 4 weeks ago
parent c0f6f75e49
commit 72df46b096

@ -1,87 +1,105 @@
<template>
<div class="multiple-layout">
<aside class="sidebar">
<div class="logo">
<el-icon :size="24">
<DataAnalysis />
</el-icon>
<span>数据工厂</span>
<!-- 顶部导航栏 -->
<header class="top-header">
<div class="header-left">
<div class="logo">
<el-icon :size="22">
<DataAnalysis />
</el-icon>
<span>数据工厂</span>
</div>
<nav class="top-nav">
<router-link to="/data-factory" class="nav-link">工作台</router-link>
</nav>
</div>
<div class="header-right">
<el-dropdown @command="handleCommand">
<span class="user-dropdown">
<el-avatar :size="32" class="user-avatar">
{{ (userStore.userInfo.userFullName || userStore.userInfo.userAccount || '用户').slice(0, 1) }}
</el-avatar>
<span class="username">{{ userStore.userInfo.userFullName || userStore.userInfo.userAccount || '用户' }}</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="logout">
<el-icon><SwitchButton /></el-icon>
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<nav class="nav-menu">
<template v-for="item in menuList" :key="item.path">
<!-- 有子菜单 -->
<div v-if="item.children" class="menu-group">
<div
class="menu-item parent"
:class="{ active: isActive(item.path), expanded: isExpanded(item.path) }"
@click="toggleExpand(item.path)"
</header>
<div class="main-container">
<!-- 左侧菜单 -->
<aside class="sidebar">
<nav class="nav-menu">
<template v-for="item in menuList" :key="item.path">
<!-- 有子菜单 -->
<div v-if="item.children" class="menu-group">
<div
class="menu-item parent"
:class="{ active: isActive(item.path), expanded: isExpanded(item.path) }"
@click="toggleExpand(item.path)"
>
<el-icon class="menu-icon">
<component :is="item.icon" />
</el-icon>
<span class="menu-text">{{ item.title }}</span>
<el-icon class="arrow" :class="{ rotated: isExpanded(item.path) }">
<ArrowDown />
</el-icon>
</div>
<div class="sub-menu" :class="{ show: isExpanded(item.path) }">
<router-link
v-for="child in item.children"
:key="child.path"
:to="child.path"
:class="{ active: isSubActive(child.path) }"
>
<span>{{ child.title }}</span>
</router-link>
</div>
</div>
<!-- 无子菜单 -->
<router-link
v-else
:to="item.path"
class="menu-item"
:class="{ active: isActive(item.path) }"
>
<el-icon>
<el-icon class="menu-icon">
<component :is="item.icon" />
</el-icon>
<span>{{ item.title }}</span>
<el-icon class="arrow" :class="{ rotated: isExpanded(item.path) }">
<ArrowDown />
</el-icon>
</div>
<div class="sub-menu" :class="{ show: isExpanded(item.path) }">
<router-link
v-for="child in item.children"
:key="child.path"
:to="child.path"
:class="{ active: isSubActive(child.path) }"
>
<span>{{ child.title }}</span>
</router-link>
</div>
</div>
<!-- 无子菜单 -->
<router-link
v-else
:to="item.path"
:class="{ active: isActive(item.path) }"
>
<el-icon>
<component :is="item.icon" />
</el-icon>
<span>{{ item.title }}</span>
</router-link>
</template>
</nav>
</aside>
<div class="main-wrapper">
<header class="header">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/data-factory' }">数据工厂</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<router-link v-if="item.path && index < breadcrumbList.length - 1" :to="item.path">
{{ item.title }}
<span class="menu-text">{{ item.title }}</span>
</router-link>
<span v-else>{{ item.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
<div class="header-actions">
<el-dropdown @command="handleCommand">
<span class="user-dropdown">
<el-icon><User /></el-icon>
<span class="username">{{ userStore.userInfo.userFullName || userStore.userInfo.userAccount || '用户' }}</span>
<el-icon class="arrow"><ArrowDown /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="logout">
<el-icon><SwitchButton /></el-icon>
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</nav>
</aside>
<!-- 内容区域 -->
<div class="main-wrapper">
<!-- 面包屑导航 -->
<div class="content-header">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/data-factory' }">数据工厂</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<router-link v-if="item.path && index < breadcrumbList.length - 1" :to="item.path">
{{ item.title }}
</router-link>
<span v-else>{{ item.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</header>
<main class="main-content">
<router-view />
</main>
<main class="main-content">
<!-- 装饰背景 -->
<div class="decoration-bg"></div>
<router-view />
</main>
</div>
</div>
</div>
</template>
@ -101,7 +119,8 @@ import {
Tickets,
Document,
User,
SwitchButton
SwitchButton,
Bell
} from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/modules/user'
import { ElMessageBox } from 'element-plus'
@ -208,65 +227,177 @@ const breadcrumbList = computed(() => {
<style scoped lang="scss">
.multiple-layout {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
background: #f5f7fa;
}
.sidebar {
width: 220px;
background: linear-gradient(180deg, #1e3a5f 0%, #0d2137 100%);
//
.top-header {
height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
position: relative;
z-index: 100;
}
.header-left {
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
padding: 20px;
color: #fff;
font-size: 18px;
font-weight: 600;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
.el-icon {
color: #409eff;
color: #fff;
}
}
.nav-menu {
.top-nav {
display: flex;
align-items: center;
gap: 8px;
.nav-link {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
padding: 6px 16px;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s;
&:hover,
&.router-link-active {
color: #fff;
background: rgba(255, 255, 255, 0.15);
}
}
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.header-icon {
color: rgba(255, 255, 255, 0.85);
font-size: 20px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
transition: all 0.2s;
&:hover {
color: #fff;
background: rgba(255, 255, 255, 0.15);
}
}
.user-dropdown {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 4px 8px;
border-radius: 8px;
transition: all 0.2s;
&:hover {
background: rgba(255, 255, 255, 0.15);
}
.user-avatar {
background: rgba(255, 255, 255, 0.25);
color: #fff;
font-size: 14px;
font-weight: 500;
}
.username {
color: #fff;
font-size: 14px;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
//
.main-container {
display: flex;
flex-direction: column;
padding: 12px 0;
flex: 1;
overflow: hidden;
}
//
.sidebar {
width: 200px;
background: #fff;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid #e8ecf0;
overflow-y: auto;
}
> a {
.nav-menu {
display: flex;
flex-direction: column;
padding: 12px 8px;
.menu-item {
display: flex;
align-items: center;
gap: 10px;
color: rgba(255, 255, 255, 0.7);
gap: 12px;
color: #5a6474;
text-decoration: none;
padding: 14px 24px;
padding: 12px 16px;
font-size: 14px;
transition: all 0.3s;
margin: 2px 12px;
transition: all 0.2s;
border-radius: 8px;
margin-bottom: 4px;
.el-icon {
.menu-icon {
font-size: 18px;
color: #8b95a5;
}
.menu-text {
flex: 1;
}
&:hover {
color: #fff;
background: rgba(64, 158, 255, 0.15);
color: #667eea;
background: #f0f3ff;
.menu-icon {
color: #667eea;
}
}
&.active {
color: #fff;
background: linear-gradient(90deg, #409eff 0%, #2a7dc9 100%);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
color: #667eea;
background: linear-gradient(135deg, #f0f3ff 0%, #e8ecff 100%);
font-weight: 500;
.menu-icon {
color: #667eea;
}
}
}
}
@ -275,22 +406,27 @@ const breadcrumbList = computed(() => {
.menu-item.parent {
display: flex;
align-items: center;
gap: 10px;
color: rgba(255, 255, 255, 0.7);
padding: 14px 24px;
gap: 12px;
color: #5a6474;
padding: 12px 16px;
font-size: 14px;
transition: all 0.3s;
margin: 2px 12px;
transition: all 0.2s;
border-radius: 8px;
margin-bottom: 4px;
cursor: pointer;
.el-icon {
.menu-icon {
font-size: 18px;
color: #8b95a5;
}
.menu-text {
flex: 1;
}
.arrow {
margin-left: auto;
font-size: 12px;
color: #8b95a5;
transition: transform 0.3s;
&.rotated {
@ -299,12 +435,22 @@ const breadcrumbList = computed(() => {
}
&:hover {
color: #fff;
background: rgba(64, 158, 255, 0.15);
color: #667eea;
background: #f0f3ff;
.menu-icon,
.arrow {
color: #667eea;
}
}
&.active {
color: #fff;
color: #667eea;
font-weight: 500;
.menu-icon {
color: #667eea;
}
}
}
@ -320,102 +466,84 @@ const breadcrumbList = computed(() => {
a {
display: flex;
align-items: center;
color: rgba(255, 255, 255, 0.6);
color: #6b7785;
text-decoration: none;
padding: 10px 24px 10px 52px;
padding: 10px 16px 10px 46px;
font-size: 13px;
transition: all 0.2s;
margin: 1px 12px;
border-radius: 6px;
margin: 2px 8px;
&:hover {
color: #fff;
background: rgba(64, 158, 255, 0.1);
color: #667eea;
background: #f5f7ff;
}
&.active {
color: #409eff;
background: rgba(64, 158, 255, 0.2);
color: #667eea;
background: #f0f3ff;
font-weight: 500;
}
}
}
}
//
.main-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: #f0f2f5;
background: linear-gradient(180deg, #eef2f9 0%, #e8edf5 100%);
}
.header {
background: #fff;
.content-header {
padding: 16px 24px;
border-bottom: 1px solid #e8e8e8;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
:deep(.el-breadcrumb) {
font-size: 14px;
font-size: 13px;
.el-breadcrumb__item {
.el-breadcrumb__inner {
color: #666;
color: #8b95a5;
a {
color: #666;
color: #8b95a5;
font-weight: normal;
&:hover {
color: #409eff;
color: #667eea;
}
}
}
&:last-child .el-breadcrumb__inner {
color: #333;
color: #5a6474;
font-weight: 500;
}
}
}
}
.header-actions {
.user-dropdown {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
color: #666;
font-size: 14px;
padding: 6px 12px;
border-radius: 6px;
transition: all 0.2s;
&:hover {
background: #f5f5f5;
color: #409eff;
}
.username {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.arrow {
font-size: 12px;
}
}
}
.main-content {
flex: 1;
overflow: auto;
padding: 20px;
padding: 0 24px 24px;
position: relative;
}
//
.decoration-bg {
position: absolute;
top: 0;
right: 0;
width: 400px;
height: 300px;
background:
radial-gradient(circle at 80% 20%, rgba(102, 126, 234, 0.08) 0%, transparent 50%),
radial-gradient(circle at 90% 80%, rgba(118, 75, 162, 0.06) 0%, transparent 40%);
pointer-events: none;
z-index: 0;
}
</style>

@ -1,65 +1,103 @@
<template>
<div class="login-container">
<!-- 装饰背景 -->
<div class="decoration">
<div class="circle circle-1"></div>
<div class="circle circle-2"></div>
<div class="circle circle-3"></div>
</div>
<div class="login-wrapper">
<div class="login-card">
<div class="login-header">
<h2 class="login-title">欢迎登录</h2>
</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"
:disabled="loading"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
:prefix-icon="Lock"
:disabled="loading"
show-password
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="rememberPassword"></el-checkbox>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="login-btn"
:loading="loading"
@click="handleLogin"
>
{{ loading ? '登录中...' : '登录' }}
</el-button>
</el-form-item>
<el-form-item>
<div class="register-link">
没有账号<router-link to="/register">立即注册</router-link>
<!-- 左侧品牌区域 -->
<div class="brand-section">
<div class="brand-content">
<div class="brand-icon">
<el-icon :size="48"><DataAnalysis /></el-icon>
</div>
<h1 class="brand-title">数据工厂</h1>
<p class="brand-desc">智能数据处理与分析平台</p>
<div class="brand-features">
<div class="feature-item">
<el-icon><Check /></el-icon>
<span>数据连接与集成</span>
</div>
</el-form-item>
</el-form>
<div class="feature-item">
<el-icon><Check /></el-icon>
<span>流程自动化处理</span>
</div>
<div class="feature-item">
<el-icon><Check /></el-icon>
<span>智能数据标注</span>
</div>
</div>
</div>
</div>
<!-- 右侧登录表单 -->
<div class="login-section">
<div class="login-card">
<div class="login-header">
<h2 class="login-title">欢迎回来</h2>
<p class="login-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"
:disabled="loading"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
:prefix-icon="Lock"
:disabled="loading"
show-password
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item>
<div class="form-options">
<el-checkbox v-model="rememberPassword"></el-checkbox>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="login-btn"
:loading="loading"
@click="handleLogin"
>
{{ loading ? '登录中...' : '登录' }}
</el-button>
</el-form-item>
<el-form-item>
<div class="register-link">
还没有账号<router-link to="/register">立即注册</router-link>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {User, Lock} from '@element-plus/icons-vue'
import {ElMessage, FormInstance, FormRules} from 'element-plus'
import {useUserStore} from '@/stores/modules/user'
import {useRouter, useRoute} from 'vue-router'
import { User, Lock, DataAnalysis, Check } from '@element-plus/icons-vue'
import { ElMessage, FormInstance, FormRules } from 'element-plus'
import { useUserStore } from '@/stores/modules/user'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
@ -76,11 +114,11 @@ const loginForm = reactive({
const loginRules: FormRules = {
username: [
{required: true, message: '请输入用户名', trigger: 'blur'},
{min: 1, max: 50, message: '用户名长度不超过50字符', trigger: 'blur'}
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 1, max: 50, message: '用户名长度不超过50字符', trigger: 'blur' }
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'}
{ required: true, message: '请输入密码', trigger: 'blur' }
]
}
@ -89,7 +127,7 @@ onMounted(() => {
const savedCredentials = localStorage.getItem('login-credentials')
if (savedCredentials) {
try {
const {username, password} = JSON.parse(atob(savedCredentials))
const { username, password } = JSON.parse(atob(savedCredentials))
loginForm.username = username || ''
loginForm.password = password || ''
} catch (e) {
@ -115,7 +153,7 @@ const handleLogin = async () => {
if (rememberPassword.value) {
localStorage.setItem(
'login-credentials',
btoa(JSON.stringify({username: loginForm.username, password: loginForm.password}))
btoa(JSON.stringify({ username: loginForm.username, password: loginForm.password }))
)
} else {
localStorage.removeItem('login-credentials')
@ -138,66 +176,270 @@ const handleLogin = async () => {
.login-container {
width: 100vw;
height: 100vh;
background: linear-gradient(135deg, #1e3a5f 0%, #0d2137 100%);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
//
.decoration {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
.circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
.circle-1 {
width: 600px;
height: 600px;
top: -200px;
right: -100px;
}
.circle-2 {
width: 400px;
height: 400px;
bottom: -100px;
left: -100px;
}
.circle-3 {
width: 200px;
height: 200px;
top: 50%;
left: 20%;
background: rgba(255, 255, 255, 0.05);
}
}
.login-wrapper {
width: 100%;
max-width: 400px;
padding: 20px;
display: flex;
width: 900px;
max-width: 95%;
background: #fff;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
position: relative;
z-index: 1;
}
//
.brand-section {
flex: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60px 40px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.08) 0%, transparent 50%);
}
}
.brand-content {
position: relative;
z-index: 1;
text-align: center;
color: #fff;
}
.brand-icon {
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.2);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24px;
backdrop-filter: blur(10px);
}
.brand-title {
font-size: 32px;
font-weight: 700;
margin: 0 0 12px;
}
.brand-desc {
font-size: 16px;
opacity: 0.9;
margin: 0 0 40px;
}
.brand-features {
text-align: left;
display: inline-block;
.feature-item {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
font-size: 14px;
opacity: 0.9;
.el-icon {
width: 20px;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
}
}
//
.login-section {
flex: 1;
padding: 60px 50px;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
background: #fff;
border-radius: 12px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 320px;
}
.login-header {
text-align: center;
margin-bottom: 30px;
margin-bottom: 32px;
}
.login-title {
font-size: 24px;
font-weight: 600;
color: #333;
font-size: 28px;
font-weight: 700;
color: #1a1a2e;
margin: 0 0 8px;
}
.login-subtitle {
font-size: 14px;
color: #8b95a5;
margin: 0;
padding-bottom: 10px;
border-bottom: 3px solid #1e3a5f;
display: inline-block;
}
.login-form {
.el-form-item {
:deep(.el-form-item) {
margin-bottom: 20px;
}
:deep(.el-input__wrapper) {
border-radius: 10px;
padding: 4px 12px;
box-shadow: 0 0 0 1px #e8ecf0;
transition: all 0.2s;
&:hover {
box-shadow: 0 0 0 1px #667eea;
}
&.is-focus {
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
}
:deep(.el-input__inner) {
height: 44px;
}
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
:deep(.el-checkbox__label) {
color: #6b7785;
font-size: 13px;
}
}
.login-btn {
width: 100%;
height: 44px;
height: 48px;
font-size: 16px;
border-radius: 8px;
font-weight: 500;
border-radius: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
&:active {
transform: translateY(0);
}
}
.register-link {
width: 100%;
text-align: center;
color: #666;
color: #8b95a5;
font-size: 14px;
a {
color: #1e3a5f;
color: #667eea;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
&:hover {
text-decoration: underline;
color: #764ba2;
}
}
}
//
@media (max-width: 768px) {
.login-wrapper {
flex-direction: column;
max-width: 400px;
}
.brand-section {
padding: 40px 30px;
.brand-features {
display: none;
}
}
.brand-title {
font-size: 24px;
}
.brand-desc {
margin-bottom: 0;
}
.login-section {
padding: 40px 30px;
}
}
</style>

@ -1,69 +1,105 @@
<template>
<div class="register-container">
<!-- 装饰背景 -->
<div class="decoration">
<div class="circle circle-1"></div>
<div class="circle circle-2"></div>
<div class="circle circle-3"></div>
</div>
<div class="register-wrapper">
<div class="register-card">
<div class="register-header">
<h2 class="register-title">用户注册</h2>
</div>
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
class="register-form"
size="large"
>
<el-form-item prop="username">
<el-input
v-model="registerForm.username"
placeholder="请输入用户名"
:prefix-icon="User"
:disabled="loading"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
placeholder="请输入密码"
:prefix-icon="Lock"
:disabled="loading"
show-password
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
placeholder="请确认密码"
:prefix-icon="Lock"
:disabled="loading"
show-password
@keyup.enter="handleRegister"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="register-btn"
:loading="loading"
@click="handleRegister"
>
{{ loading ? '注册中...' : '注册' }}
</el-button>
</el-form-item>
<el-form-item>
<div class="login-link">
已有账号<router-link to="/login">立即登录</router-link>
<!-- 左侧品牌区域 -->
<div class="brand-section">
<div class="brand-content">
<div class="brand-icon">
<el-icon :size="48"><DataAnalysis /></el-icon>
</div>
<h1 class="brand-title">数据工厂</h1>
<p class="brand-desc">智能数据处理与分析平台</p>
<div class="brand-features">
<div class="feature-item">
<el-icon><Check /></el-icon>
<span>数据连接与集成</span>
</div>
</el-form-item>
</el-form>
<div class="feature-item">
<el-icon><Check /></el-icon>
<span>流程自动化处理</span>
</div>
<div class="feature-item">
<el-icon><Check /></el-icon>
<span>智能数据标注</span>
</div>
</div>
</div>
</div>
<!-- 右侧注册表单 -->
<div class="register-section">
<div class="register-card">
<div class="register-header">
<h2 class="register-title">创建账号</h2>
<p class="register-subtitle">注册以开始使用数据工厂</p>
</div>
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
class="register-form"
size="large"
>
<el-form-item prop="username">
<el-input
v-model="registerForm.username"
placeholder="请输入用户名"
:prefix-icon="User"
:disabled="loading"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
placeholder="请输入密码"
:prefix-icon="Lock"
:disabled="loading"
show-password
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
placeholder="请确认密码"
:prefix-icon="Lock"
:disabled="loading"
show-password
@keyup.enter="handleRegister"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="register-btn"
:loading="loading"
@click="handleRegister"
>
{{ loading ? '注册中...' : '注册' }}
</el-button>
</el-form-item>
<el-form-item>
<div class="login-link">
已有账号<router-link to="/login">立即登录</router-link>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { User, Lock } from '@element-plus/icons-vue'
import { User, Lock, DataAnalysis, Check } from '@element-plus/icons-vue'
import { ElMessage, FormInstance, FormRules } from 'element-plus'
import { useUserStore } from '@/stores/modules/user'
import { useRouter } from 'vue-router'
@ -130,66 +166,258 @@ const handleRegister = async () => {
.register-container {
width: 100vw;
height: 100vh;
background: linear-gradient(135deg, #1e3a5f 0%, #0d2137 100%);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
//
.decoration {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
.circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
.circle-1 {
width: 600px;
height: 600px;
top: -200px;
right: -100px;
}
.circle-2 {
width: 400px;
height: 400px;
bottom: -100px;
left: -100px;
}
.circle-3 {
width: 200px;
height: 200px;
top: 50%;
left: 20%;
background: rgba(255, 255, 255, 0.05);
}
}
.register-wrapper {
width: 100%;
max-width: 400px;
padding: 20px;
display: flex;
width: 900px;
max-width: 95%;
background: #fff;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
position: relative;
z-index: 1;
}
//
.brand-section {
flex: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60px 40px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.08) 0%, transparent 50%);
}
}
.brand-content {
position: relative;
z-index: 1;
text-align: center;
color: #fff;
}
.brand-icon {
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.2);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24px;
backdrop-filter: blur(10px);
}
.brand-title {
font-size: 32px;
font-weight: 700;
margin: 0 0 12px;
}
.brand-desc {
font-size: 16px;
opacity: 0.9;
margin: 0 0 40px;
}
.brand-features {
text-align: left;
display: inline-block;
.feature-item {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
font-size: 14px;
opacity: 0.9;
.el-icon {
width: 20px;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
}
}
//
.register-section {
flex: 1;
padding: 60px 50px;
display: flex;
align-items: center;
justify-content: center;
}
.register-card {
background: #fff;
border-radius: 12px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 320px;
}
.register-header {
text-align: center;
margin-bottom: 30px;
margin-bottom: 32px;
}
.register-title {
font-size: 24px;
font-weight: 600;
color: #333;
font-size: 28px;
font-weight: 700;
color: #1a1a2e;
margin: 0 0 8px;
}
.register-subtitle {
font-size: 14px;
color: #8b95a5;
margin: 0;
padding-bottom: 10px;
border-bottom: 3px solid #1e3a5f;
display: inline-block;
}
.register-form {
.el-form-item {
:deep(.el-form-item) {
margin-bottom: 20px;
}
:deep(.el-input__wrapper) {
border-radius: 10px;
padding: 4px 12px;
box-shadow: 0 0 0 1px #e8ecf0;
transition: all 0.2s;
&:hover {
box-shadow: 0 0 0 1px #667eea;
}
&.is-focus {
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
}
:deep(.el-input__inner) {
height: 44px;
}
}
.register-btn {
width: 100%;
height: 44px;
height: 48px;
font-size: 16px;
border-radius: 8px;
font-weight: 500;
border-radius: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
&:active {
transform: translateY(0);
}
}
.login-link {
width: 100%;
text-align: center;
color: #666;
color: #8b95a5;
font-size: 14px;
a {
color: #1e3a5f;
color: #667eea;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
&:hover {
text-decoration: underline;
color: #764ba2;
}
}
}
//
@media (max-width: 768px) {
.register-wrapper {
flex-direction: column;
max-width: 400px;
}
.brand-section {
padding: 40px 30px;
.brand-features {
display: none;
}
}
.brand-title {
font-size: 24px;
}
.brand-desc {
margin-bottom: 0;
}
.register-section {
padding: 40px 30px;
}
}
</style>

Loading…
Cancel
Save