| | |
| | | <template> |
| | | <div class="login-container"> |
| | | <!-- 动态背景 --> |
| | | <div class="background-animation"> |
| | | <div class="floating-shapes"> |
| | | <div class="shape shape-1"></div> |
| | |
| | | <div class="gradient-overlay"></div> |
| | | </div> |
| | | |
| | | <!-- 主登录区域 --> |
| | | <div class="login-content"> |
| | | <!-- 左侧品牌展示区 --> |
| | | <div class="brand-section"> |
| | | <div class="brand-logo-container"> |
| | | <div class="logo-icon"> |
| | | <svg viewBox="0 0 100 100" class="api-icon"> |
| | | <!-- 主API连接图标 --> |
| | | <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> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 右侧登录表单 --> |
| | | <div class="login-form-section"> |
| | | <div class="form-container"> |
| | | <div class="form-header"> |
| | | <h2>欢迎登录</h2> |
| | | <p>请使用您的账号密码登录系统</p> |
| | | <h2>{{ isLoginMode ? '欢迎登录' : '用户注册' }}</h2> |
| | | <p>{{ isLoginMode ? '请使用您的账号密码登录系统' : '请填写以下信息完成注册' }}</p> |
| | | </div> |
| | | |
| | | <el-form |
| | |
| | | @submit.native.prevent="submitForm" |
| | | class="login-form" |
| | | > |
| | | <div class="input-group"> |
| | | <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> |
| | | |
| | | <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" |
| | |
| | | </transition> |
| | | </div> |
| | | |
| | | <div class="input-group"> |
| | | <div class="input-group" v-if="isLoginMode"> |
| | | <label class="input-label">密码</label> |
| | | <el-input |
| | | v-model="loginForm.password" |
| | |
| | | :loading="isLoading" |
| | | @click="submitForm" |
| | | > |
| | | {{ isLoading ? '登录中...' : '登录系统' }} |
| | | {{ isLoading ? (isLoginMode ? '登录中...' : '注册中...') : (isLoginMode ? '登录系统' : '注册账号') }} |
| | | </el-button> |
| | | </el-form> |
| | | |
| | | <div class="form-footer"> |
| | | <p>还没有账号?<a href="#" class="register-link">联系管理员注册</a></p> |
| | | <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> |
| | |
| | | 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 { |
| | |
| | | }); |
| | | }, |
| | | submitForm() { |
| | | 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> |