| | |
| | | <template> |
| | | <div class="login-container"> |
| | | <!-- <div class="login-background">--> |
| | | <!-- <div class="gradient-overlay"></div>--> |
| | | <!-- </div>--> |
| | | <div class="background-animation"> |
| | | <div class="floating-shapes"> |
| | | <div class="shape shape-1"></div> |
| | | <div class="shape shape-2"></div> |
| | | <div class="shape shape-3"></div> |
| | | <div class="shape shape-4"></div> |
| | | </div> |
| | | <div class="network-lines"></div> |
| | | <div class="gradient-overlay"></div> |
| | | </div> |
| | | |
| | | <el-card class="login-card"> |
| | | <div class="login-content"> |
| | | <div class="brand-section"> |
| | | <img |
| | | src="~@/assets/images/img.png" |
| | | class="brand-logo" |
| | | alt="系统logo" |
| | | /> |
| | | <h1 class="system-name">接口自动化平台</h1> |
| | | <div class="brand-logo-container"> |
| | | <div class="logo-icon"> |
| | | <svg viewBox="0 0 100 100" class="api-icon"> |
| | | <path d="M20,30 L50,10 L80,30 L80,70 L50,90 L20,70 Z" fill="rgba(0,212,255,0.1)" stroke="rgba(0,212,255,0.6)" stroke-width="2"/> |
| | | <path d="M30,40 L50,25 L70,40 L70,60 L50,75 L30,60 Z" fill="rgba(148,0,211,0.1)" stroke="rgba(148,0,211,0.6)" stroke-width="1.5"/> |
| | | <path d="M40,50 L50,40 L60,50 L60,55 L50,60 L40,55 Z" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.8)" stroke-width="1"/> |
| | | <line x1="35" y1="45" x2="45" y2="52" stroke="#00d4ff" stroke-width="1.5" stroke-dasharray="2,2"/> |
| | | <line x1="55" y1="45" x2="65" y2="52" stroke="#9400d3" stroke-width="1.5" stroke-dasharray="2,2"/> |
| | | <circle cx="50" cy="50" r="4" fill="#00d4ff"> |
| | | <animate attributeName="r" values="4;6;4" dur="2s" repeatCount="indefinite"/> |
| | | <animate attributeName="opacity" values="1;0.7;1" dur="2s" repeatCount="indefinite"/> |
| | | </circle> |
| | | <circle cx="35" cy="35" r="2" fill="#00d4ff"> |
| | | <animate attributeName="opacity" values="0.5;1;0.5" dur="1.5s" repeatCount="indefinite"/> |
| | | </circle> |
| | | <circle cx="65" cy="35" r="2" fill="#9400d3"> |
| | | <animate attributeName="opacity" values="0.5;1;0.5" dur="1.5s" repeatCount="indefinite" begin="0.5s"/> |
| | | </circle> |
| | | <circle cx="35" cy="65" r="2" fill="#9400d3"> |
| | | <animate attributeName="opacity" values="0.5;1;0.5" dur="1.5s" repeatCount="indefinite" begin="1s"/> |
| | | </circle> |
| | | <circle cx="65" cy="65" r="2" fill="#00d4ff"> |
| | | <animate attributeName="opacity" values="0.5;1;0.5" dur="1.5s" repeatCount="indefinite" begin="1.5s"/> |
| | | </circle> |
| | | </svg> |
| | | </div> |
| | | <h1 class="system-name">APITest Pro</h1> |
| | | <p class="system-desc">智能接口自动化测试平台</p> |
| | | </div> |
| | | |
| | | <div class="feature-list"> |
| | | <div class="feature-item"> |
| | | <span class="feature-icon">🚀</span> |
| | | <span>高效自动化测试</span> |
| | | </div> |
| | | <div class="feature-item"> |
| | | <span class="feature-icon">🔗</span> |
| | | <span>智能接口管理</span> |
| | | </div> |
| | | <div class="feature-item"> |
| | | <span class="feature-icon">📊</span> |
| | | <span>实时数据监控</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-form |
| | | ref="loginForm" |
| | | @submit.native.prevent="submitForm" |
| | | class="login-form" |
| | | > |
| | | <el-form-item> |
| | | <el-input |
| | | v-model="loginForm.username" |
| | | placeholder="账号" |
| | | prefix-icon="el-icon-user-solid" |
| | | class="custom-input" |
| | | :class="{ 'input-error': usernameInvalid }" |
| | | @blur="validateUserName" |
| | | /> |
| | | <transition name="el-zoom-in-top"> |
| | | <div v-if="usernameInvalid" class="error-msg">{{ usernameInvalid }}</div> |
| | | </transition> |
| | | </el-form-item> |
| | | <div class="login-form-section"> |
| | | <div class="form-container"> |
| | | <div class="form-header"> |
| | | <h2>{{ isLoginMode ? '欢迎登录' : '用户注册' }}</h2> |
| | | <p>{{ isLoginMode ? '请使用您的账号密码登录系统' : '请填写以下信息完成注册' }}</p> |
| | | </div> |
| | | |
| | | <el-form-item> |
| | | <el-input |
| | | v-model="loginForm.password" |
| | | type="password" |
| | | placeholder="密码" |
| | | prefix-icon="el-icon-lock" |
| | | show-password |
| | | class="custom-input" |
| | | :class="{ 'input-error': passwordInvalid }" |
| | | @blur="validatePassword" |
| | | /> |
| | | <transition name="el-zoom-in-top"> |
| | | <div v-if="passwordInvalid" class="error-msg">{{ passwordInvalid }}</div> |
| | | </transition> |
| | | </el-form-item> |
| | | <el-form |
| | | ref="loginForm" |
| | | @submit.native.prevent="submitForm" |
| | | class="login-form" |
| | | > |
| | | <div class="input-group" v-if="!isLoginMode"> |
| | | <label class="input-label">用户名</label> |
| | | <el-input |
| | | v-model="registerForm.username" |
| | | placeholder="请输入用户名" |
| | | prefix-icon="el-icon-user" |
| | | class="modern-input" |
| | | :class="{ 'input-error': registerErrors.username }" |
| | | @blur="validateRegisterField('username')" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="registerErrors.username" class="error-msg">{{ registerErrors.username }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <el-button |
| | | type="primary" |
| | | class="login-button" |
| | | :loading="isLoading" |
| | | @click="submitForm" |
| | | > |
| | | {{ isLoading ? '登录中...' : '立即登录' }} |
| | | </el-button> |
| | | </el-form> |
| | | </el-card> |
| | | <div class="input-group" v-if="!isLoginMode"> |
| | | <label class="input-label">姓名</label> |
| | | <el-input |
| | | v-model="registerForm.name" |
| | | placeholder="请输入姓名" |
| | | prefix-icon="el-icon-edit" |
| | | class="modern-input" |
| | | :class="{ 'input-error': registerErrors.name }" |
| | | @blur="validateRegisterField('name')" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="registerErrors.name" class="error-msg">{{ registerErrors.name }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <div class="input-group" v-if="!isLoginMode"> |
| | | <label class="input-label">手机号</label> |
| | | <el-input |
| | | v-model="registerForm.phone" |
| | | placeholder="请输入手机号" |
| | | prefix-icon="el-icon-phone" |
| | | class="modern-input" |
| | | :class="{ 'input-error': registerErrors.phone }" |
| | | @blur="validateRegisterField('phone')" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="registerErrors.phone" class="error-msg">{{ registerErrors.phone }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <div class="input-group" v-if="!isLoginMode"> |
| | | <label class="input-label">所属分组(可选)</label> |
| | | <el-select |
| | | v-model="registerForm.groups" |
| | | multiple |
| | | placeholder="请选择分组" |
| | | class="modern-input" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="group in groupList" |
| | | :key="group.id" |
| | | :label="group.name" |
| | | :value="group.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </div> |
| | | |
| | | <div class="input-group" v-if="!isLoginMode"> |
| | | <label class="input-label">密码</label> |
| | | <el-input |
| | | v-model="registerForm.password" |
| | | type="password" |
| | | placeholder="请输入密码" |
| | | prefix-icon="el-icon-lock" |
| | | show-password |
| | | class="modern-input" |
| | | :class="{ 'input-error': registerErrors.password }" |
| | | @blur="validateRegisterField('password')" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="registerErrors.password" class="error-msg">{{ registerErrors.password }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <div class="input-group" v-if="!isLoginMode"> |
| | | <label class="input-label">确认密码</label> |
| | | <el-input |
| | | v-model="registerForm.confirm_password" |
| | | type="password" |
| | | placeholder="请再次输入密码" |
| | | prefix-icon="el-icon-lock" |
| | | show-password |
| | | class="modern-input" |
| | | :class="{ 'input-error': registerErrors.confirm_password }" |
| | | @blur="validateRegisterField('confirm_password')" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="registerErrors.confirm_password" class="error-msg">{{ registerErrors.confirm_password }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <div class="input-group" v-if="isLoginMode"> |
| | | <label class="input-label">用户名</label> |
| | | <el-input |
| | | v-model="loginForm.username" |
| | | placeholder="请输入用户名" |
| | | prefix-icon="el-icon-user" |
| | | class="modern-input" |
| | | :class="{ 'input-error': usernameInvalid }" |
| | | @blur="validateUserName" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="usernameInvalid" class="error-msg">{{ usernameInvalid }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <div class="input-group" v-if="isLoginMode"> |
| | | <label class="input-label">密码</label> |
| | | <el-input |
| | | v-model="loginForm.password" |
| | | type="password" |
| | | placeholder="请输入密码" |
| | | prefix-icon="el-icon-lock" |
| | | show-password |
| | | class="modern-input" |
| | | :class="{ 'input-error': passwordInvalid }" |
| | | @blur="validatePassword" |
| | | /> |
| | | <transition name="slide-fade"> |
| | | <div v-if="passwordInvalid" class="error-msg">{{ passwordInvalid }}</div> |
| | | </transition> |
| | | </div> |
| | | |
| | | <el-button |
| | | type="primary" |
| | | class="login-button" |
| | | :loading="isLoading" |
| | | @click="submitForm" |
| | | > |
| | | {{ isLoading ? (isLoginMode ? '登录中...' : '注册中...') : (isLoginMode ? '登录系统' : '注册账号') }} |
| | | </el-button> |
| | | </el-form> |
| | | |
| | | <div class="form-footer"> |
| | | <p v-if="isLoginMode">还没有账号?<a href="#" class="register-link" @click.prevent="switchMode">立即注册</a></p> |
| | | <p v-else>已有账号?<a href="#" class="register-link" @click.prevent="switchMode">返回登录</a></p> |
| | | <p class="copyright">© 2025 APITest Pro 智能接口自动化平台</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | data() { |
| | | return { |
| | | isLoading: false, |
| | | isLoginMode: true, |
| | | loginForm: { |
| | | username: "", |
| | | password: "" |
| | | }, |
| | | registerForm: { |
| | | username: "", |
| | | name: "", |
| | | phone: "", |
| | | password: "", |
| | | confirm_password: "", |
| | | groups: [] |
| | | }, |
| | | usernameInvalid: "", |
| | | passwordInvalid: "" |
| | | passwordInvalid: "", |
| | | registerErrors: { |
| | | username: "", |
| | | name: "", |
| | | phone: "", |
| | | password: "", |
| | | confirm_password: "" |
| | | }, |
| | | groupList: [] |
| | | }; |
| | | }, |
| | | |
| | | methods: { |
| | | switchMode() { |
| | | this.isLoginMode = !this.isLoginMode; |
| | | this.resetForms(); |
| | | if (!this.isLoginMode) { |
| | | this.getGroupList(); |
| | | } |
| | | }, |
| | | resetForms() { |
| | | this.loginForm = { |
| | | username: "", |
| | | password: "" |
| | | }; |
| | | this.registerForm = { |
| | | username: "", |
| | | name: "", |
| | | phone: "", |
| | | password: "", |
| | | confirm_password: "", |
| | | groups: [] |
| | | }; |
| | | this.usernameInvalid = ""; |
| | | this.passwordInvalid = ""; |
| | | this.registerErrors = { |
| | | username: "", |
| | | name: "", |
| | | phone: "", |
| | | password: "", |
| | | confirm_password: "" |
| | | }; |
| | | }, |
| | | validateUserName() { |
| | | if (this.loginForm.username.replace(/(^\s*)/g, "") === "") { |
| | | this.usernameInvalid = "用户名不能为空"; |
| | |
| | | } |
| | | return true; |
| | | }, |
| | | validateRegisterField(field) { |
| | | const form = this.registerForm; |
| | | let isValid = true; |
| | | |
| | | switch (field) { |
| | | case 'username': |
| | | if (!form.username) { |
| | | this.registerErrors.username = "用户名不能为空"; |
| | | isValid = false; |
| | | } else { |
| | | this.registerErrors.username = ""; |
| | | } |
| | | break; |
| | | case 'name': |
| | | if (!form.name) { |
| | | this.registerErrors.name = "姓名不能为空"; |
| | | isValid = false; |
| | | } else { |
| | | this.registerErrors.name = ""; |
| | | } |
| | | break; |
| | | case 'phone': |
| | | if (!form.phone) { |
| | | this.registerErrors.phone = "手机号不能为空"; |
| | | isValid = false; |
| | | } else if (!/^1[3-9]\d{9}$/.test(form.phone)) { |
| | | this.registerErrors.phone = "手机号格式不正确"; |
| | | isValid = false; |
| | | } else { |
| | | this.registerErrors.phone = ""; |
| | | } |
| | | break; |
| | | case 'password': |
| | | if (!form.password) { |
| | | this.registerErrors.password = "密码不能为空"; |
| | | isValid = false; |
| | | } else if (form.password.length < 6) { |
| | | this.registerErrors.password = "密码长度不能少于6位"; |
| | | isValid = false; |
| | | } else { |
| | | this.registerErrors.password = ""; |
| | | } |
| | | break; |
| | | case 'confirm_password': |
| | | if (!form.confirm_password) { |
| | | this.registerErrors.confirm_password = "请再次输入密码"; |
| | | isValid = false; |
| | | } else if (form.password !== form.confirm_password) { |
| | | this.registerErrors.confirm_password = "两次输入的密码不一致"; |
| | | isValid = false; |
| | | } else { |
| | | this.registerErrors.confirm_password = ""; |
| | | } |
| | | break; |
| | | } |
| | | return isValid; |
| | | }, |
| | | validateRegisterForm() { |
| | | let isValid = true; |
| | | const fields = ['username', 'name', 'phone', 'password', 'confirm_password']; |
| | | fields.forEach(field => { |
| | | if (!this.validateRegisterField(field)) { |
| | | isValid = false; |
| | | } |
| | | }); |
| | | return isValid; |
| | | }, |
| | | handleLoginSuccess(resp) { |
| | | if (resp.success) { |
| | | // 显示登录信息提示 |
| | | this.showLoginNotification(resp); |
| | | |
| | | // 原有路由跳转和存储逻辑 |
| | | this.$router.push({ name: "ProjectList" }); |
| | | this.$store.commit("isLogin", resp.token); |
| | | this.$store.commit("setUser", resp.user); |
| | | this.$store.commit("setName", resp.name); |
| | | this.$store.commit("setId", resp.id); |
| | | this.$store.commit("setIsSuperuser", resp.is_superuser); |
| | | this.$store.commit("setIsStaff", resp.is_staff); |
| | | this.$store.commit("setRouterName", "ProjectList"); |
| | | this.$store.commit("setShowHosts", resp.show_hosts); |
| | | |
| | |
| | | this.setLocalValue("name", resp.name); |
| | | this.setLocalValue("id", resp.id); |
| | | this.setLocalValue("is_superuser", resp.is_superuser); |
| | | this.setLocalValue("is_staff", resp.is_staff); |
| | | this.setLocalValue("routerName", "ProjectList"); |
| | | this.setLocalValue("show_hosts", resp.show_hosts); |
| | | } else { |
| | |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | |
| | | showLoginNotification(resp) { |
| | | const currentTime = new Date().toLocaleString(); |
| | | |
| | | |
| | | this.$notify({ |
| | | title: '登录成功', |
| | | message: `登录时间: ${currentTime}`, |
| | |
| | | }); |
| | | }, |
| | | submitForm() { |
| | | if (this.validateUserName() && this.validatePassword()) { |
| | | this.isLoading = true; |
| | | this.$api.login(this.loginForm).then(resp => { |
| | | this.handleLoginSuccess(resp); |
| | | this.isLoading = false; |
| | | }); |
| | | if (this.isLoginMode) { |
| | | if (this.validateUserName() && this.validatePassword()) { |
| | | this.isLoading = true; |
| | | this.$api.login(this.loginForm).then(resp => { |
| | | this.handleLoginSuccess(resp); |
| | | this.isLoading = false; |
| | | }).catch(error => { |
| | | this.isLoading = false; |
| | | if (error.response && error.response.data && error.response.data.msg) { |
| | | this.$message.error({ |
| | | message: error.response.data.msg, |
| | | duration: 2000, |
| | | center: true |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | } else { |
| | | if (this.validateRegisterForm()) { |
| | | this.isLoading = true; |
| | | this.$api.register(this.registerForm).then(resp => { |
| | | this.$message.success(resp.msg); |
| | | this.isLoading = false; |
| | | this.switchMode(); |
| | | }).catch(error => { |
| | | this.isLoading = false; |
| | | if (error.response && error.response.data && error.response.data.msg) { |
| | | this.$message.error({ |
| | | message: error.response.data.msg, |
| | | duration: 2000, |
| | | center: true |
| | | }); |
| | | } else if (error.response && error.response.data) { |
| | | const errors = error.response.data; |
| | | let errorMsg = ''; |
| | | for (let key in errors) { |
| | | if (Array.isArray(errors[key])) { |
| | | errorMsg += errors[key].join('、') + ';'; |
| | | } else if (typeof errors[key] === 'string') { |
| | | errorMsg += errors[key] + ';'; |
| | | } |
| | | } |
| | | this.$message.error({ |
| | | message: errorMsg || '注册失败,请检查输入信息', |
| | | duration: 3000, |
| | | center: true |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }, |
| | | getGroupList() { |
| | | this.$api.getGroupList().then(resp => { |
| | | this.groupList = resp; |
| | | }).catch(() => { |
| | | this.groupList = []; |
| | | }); |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.getGroupList(); |
| | | } |
| | | }; |
| | | </script> |
| | |
| | | <style scoped> |
| | | .login-container { |
| | | min-height: 100vh; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | background: linear-gradient(135deg, #0c0e27 0%, #1a1f3d 50%, #2d1b69 100%); |
| | | position: relative; |
| | | overflow: hidden; |
| | | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | | } |
| | | |
| | | .login-background { |
| | | /* 动态背景动画 */ |
| | | .background-animation { |
| | | position: absolute; |
| | | width: 100%; |
| | | height: 100%; |
| | | background: rgba(255, 255, 255, 0.1); |
| | | opacity: 0.1; |
| | | top: 0; |
| | | left: 0; |
| | | } |
| | | |
| | | .floating-shapes { |
| | | position: absolute; |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .shape { |
| | | position: absolute; |
| | | border-radius: 50%; |
| | | background: linear-gradient(45deg, rgba(0, 212, 255, 0.1), rgba(148, 0, 211, 0.1)); |
| | | animation: float 20s infinite linear; |
| | | } |
| | | |
| | | .shape-1 { |
| | | width: 100px; |
| | | height: 100px; |
| | | top: 10%; |
| | | left: 10%; |
| | | animation-delay: 0s; |
| | | } |
| | | |
| | | .shape-2 { |
| | | width: 150px; |
| | | height: 150px; |
| | | top: 60%; |
| | | right: 10%; |
| | | animation-delay: -5s; |
| | | } |
| | | |
| | | .shape-3 { |
| | | width: 80px; |
| | | height: 80px; |
| | | bottom: 20%; |
| | | left: 20%; |
| | | animation-delay: -10s; |
| | | } |
| | | |
| | | .shape-4 { |
| | | width: 120px; |
| | | height: 120px; |
| | | top: 30%; |
| | | right: 30%; |
| | | animation-delay: -15s; |
| | | } |
| | | |
| | | .network-lines { |
| | | position: absolute; |
| | | width: 100%; |
| | | height: 100%; |
| | | background-image: |
| | | linear-gradient(90deg, transparent 24px, rgba(0, 212, 255, 0.03) 25px, rgba(0, 212, 255, 0.03) 26px, transparent 27px, transparent 74px, rgba(148, 0, 211, 0.03) 75px, rgba(148, 0, 211, 0.03) 76px, transparent 77px), |
| | | linear-gradient(0deg, transparent 24px, rgba(0, 212, 255, 0.03) 25px, rgba(0, 212, 255, 0.03) 26px, transparent 27px, transparent 74px, rgba(148, 0, 211, 0.03) 75px, rgba(148, 0, 211, 0.03) 76px, transparent 77px); |
| | | background-size: 100px 100px; |
| | | animation: gridMove 40s linear infinite; |
| | | } |
| | | |
| | | .gradient-overlay { |
| | | position: absolute; |
| | | width: 100%; |
| | | height: 100%; |
| | | background: linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%); |
| | | background: radial-gradient(circle at 20% 80%, rgba(0, 212, 255, 0.1) 0%, transparent 50%), |
| | | radial-gradient(circle at 80% 20%, rgba(148, 0, 211, 0.1) 0%, transparent 50%); |
| | | } |
| | | |
| | | .login-card { |
| | | width: 420px; |
| | | border-radius: 12px; |
| | | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15); |
| | | border: none; |
| | | z-index: 1; |
| | | background: rgba(255, 255, 255, 0.95); |
| | | /* 主内容区域 */ |
| | | .login-content { |
| | | display: flex; |
| | | min-height: 100vh; |
| | | max-width: 1400px; |
| | | margin: 0 auto; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | |
| | | /* 左侧品牌区域 */ |
| | | .brand-section { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | padding: 0 4rem; |
| | | color: white; |
| | | } |
| | | |
| | | .brand-logo-container { |
| | | margin-bottom: 3rem; |
| | | } |
| | | |
| | | .logo-icon { |
| | | width: 120px; |
| | | height: 120px; |
| | | margin-bottom: 1.5rem; |
| | | filter: drop-shadow(0 0 20px rgba(0, 212, 255, 0.5)); |
| | | } |
| | | |
| | | .api-icon { |
| | | width: 100%; |
| | | height: 100%; |
| | | animation: pulse 3s ease-in-out infinite; |
| | | } |
| | | |
| | | .system-name { |
| | | font-size: 3rem; |
| | | font-weight: 700; |
| | | margin-bottom: 0.5rem; |
| | | background: linear-gradient(135deg, #00d4ff 0%, #9400d3 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .system-desc { |
| | | font-size: 1.2rem; |
| | | opacity: 0.8; |
| | | margin: 0; |
| | | } |
| | | |
| | | .feature-list { |
| | | margin-top: 2rem; |
| | | } |
| | | |
| | | .feature-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 1rem; |
| | | font-size: 1.1rem; |
| | | opacity: 0.9; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .feature-item:hover { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .feature-icon { |
| | | font-size: 1.5rem; |
| | | margin-right: 0.8rem; |
| | | filter: drop-shadow(0 0 10px rgba(0, 212, 255, 0.5)); |
| | | } |
| | | |
| | | /* 右侧表单区域 */ |
| | | .login-form-section { |
| | | flex: 0 0 500px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 2rem; |
| | | } |
| | | |
| | | .form-container { |
| | | background: rgba(255, 255, 255, 0.95); |
| | | backdrop-filter: blur(20px); |
| | | border-radius: 20px; |
| | | padding: 3rem 2.5rem; |
| | | width: 100%; |
| | | max-width: 400px; |
| | | box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); |
| | | border: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | .form-header { |
| | | text-align: center; |
| | | margin-bottom: 2rem; |
| | | margin-bottom: 2.5rem; |
| | | } |
| | | |
| | | .brand-logo { |
| | | width: 120px; /* 尺寸放大50% */ |
| | | height: auto; |
| | | margin-bottom: 1.5rem; /* 增加下边距保持间距 */ |
| | | transition: transform 0.3s ease; /* 添加悬停动效 */ |
| | | .form-header h2 { |
| | | font-size: 2rem; |
| | | font-weight: 600; |
| | | margin-bottom: 0.5rem; |
| | | color: #1a1f3d; |
| | | } |
| | | |
| | | /* 可选悬停效果 */ |
| | | .brand-logo:hover { |
| | | transform: scale(1.05); |
| | | .form-header p { |
| | | color: #666; |
| | | margin: 0; |
| | | } |
| | | |
| | | .input-group { |
| | | margin-bottom: 1.5rem; |
| | | } |
| | | |
| | | .input-label { |
| | | display: block; |
| | | margin-bottom: 0.5rem; |
| | | font-weight: 500; |
| | | color: #333; |
| | | font-size: 0.9rem; |
| | | } |
| | | |
| | | .modern-input { |
| | | width: 100%; |
| | | } |
| | | |
| | | .modern-input >>> .el-input__inner { |
| | | height: 48px; |
| | | border-radius: 12px; |
| | | border: 2px solid #e1e5e9; |
| | | font-size: 1rem; |
| | | transition: all 0.3s ease; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .modern-input >>> .el-input__inner:focus { |
| | | border-color: #00d4ff; |
| | | box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1); |
| | | background: white; |
| | | } |
| | | |
| | | .modern-input.input-error >>> .el-input__inner { |
| | | border-color: #ff4757; |
| | | } |
| | | |
| | | .login-button { |
| | | width: 100%; |
| | | height: 50px; |
| | | border-radius: 12px; |
| | | font-size: 1.1rem; |
| | | font-weight: 600; |
| | | background: linear-gradient(135deg, #00d4ff 0%, #9400d3 100%); |
| | | border: none; |
| | | transition: all 0.3s ease; |
| | | margin-top: 0.5rem; |
| | | } |
| | | |
| | | .login-button:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 10px 25px rgba(0, 212, 255, 0.4); |
| | | } |
| | | |
| | | .login-button:active { |
| | | transform: translateY(0); |
| | | } |
| | | |
| | | .error-msg { |
| | | color: #ff4757; |
| | | font-size: 0.85rem; |
| | | margin-top: 0.3rem; |
| | | animation: slideIn 0.3s ease; |
| | | } |
| | | |
| | | .form-footer { |
| | | margin-top: 2rem; |
| | | text-align: center; |
| | | } |
| | | |
| | | .form-footer p { |
| | | margin: 0.5rem 0; |
| | | color: #666; |
| | | font-size: 0.9rem; |
| | | } |
| | | |
| | | .register-link { |
| | | color: #00d4ff; |
| | | text-decoration: none; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .register-link:hover { |
| | | text-decoration: underline; |
| | | } |
| | | |
| | | .copyright { |
| | | opacity: 0.6; |
| | | font-size: 0.8rem; |
| | | } |
| | | |
| | | /* 动画效果 */ |
| | | @keyframes float { |
| | | 0%, 100% { |
| | | transform: translateY(0px) rotate(0deg); |
| | | } |
| | | 33% { |
| | | transform: translateY(-20px) rotate(120deg); |
| | | } |
| | | 66% { |
| | | transform: translateY(10px) rotate(240deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes gridMove { |
| | | 0% { |
| | | transform: translate(0, 0); |
| | | } |
| | | 100% { |
| | | transform: translate(100px, 100px); |
| | | } |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0%, 100% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | 50% { |
| | | transform: scale(1.05); |
| | | opacity: 0.8; |
| | | } |
| | | } |
| | | |
| | | @keyframes slideIn { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateY(-10px); |
| | | } |
| | | to { |
| | | opacity: 1; |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | .slide-fade-enter-active { |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .slide-fade-leave-active { |
| | | transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1); |
| | | } |
| | | |
| | | .slide-fade-enter, .slide-fade-leave-to { |
| | | transform: translateY(-10px); |
| | | opacity: 0; |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | | @media (max-width: 1024px) { |
| | | .login-content { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .brand-section { |
| | | padding: 2rem; |
| | | text-align: center; |
| | | } |
| | | |
| | | .login-form-section { |
| | | flex: none; |
| | | width: 100%; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .form-container { |
| | | margin: 1rem; |
| | | padding: 2rem 1.5rem; |
| | | } |
| | | |
| | | .system-name { |
| | | font-size: 2.5rem; |
| | | } |
| | | } |
| | | |
| | | |
| | | .system-name { |
| | | font-size: 2rem; /* 同步放大系统名称字号 */ |
| | | margin-top: 0.5rem; /* 增加与LOGO的间距 */ |