924 lines
21 KiB
Vue
924 lines
21 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="login-page-container">
|
|||
|
|
<!-- 页面头部 -->
|
|||
|
|
<div class="page-header">
|
|||
|
|
<div class="page-title">
|
|||
|
|
<h1 class="main-title">用户登录</h1>
|
|||
|
|
<p class="subtitle">您的专属彩票数据助理</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 登录表单 -->
|
|||
|
|
<el-card class="login-form-container" shadow="never">
|
|||
|
|
<!-- 被踢出提示 -->
|
|||
|
|
<el-alert
|
|||
|
|
v-if="showKickedOutAlert"
|
|||
|
|
title="账号已在其他设备登录"
|
|||
|
|
type="warning"
|
|||
|
|
description="为保障账号安全,您的账号已在其他设备登录,当前会话已失效。请重新登录。"
|
|||
|
|
show-icon
|
|||
|
|
:closable="true"
|
|||
|
|
@close="showKickedOutAlert = false"
|
|||
|
|
style="margin-bottom: 20px"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<div class="login-tabs">
|
|||
|
|
<div
|
|||
|
|
class="login-tab"
|
|||
|
|
:class="{ active: loginType === 'account' }"
|
|||
|
|
@click="switchLoginType('account')"
|
|||
|
|
>
|
|||
|
|
账号登录
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
class="login-tab"
|
|||
|
|
:class="{ active: loginType === 'phone' }"
|
|||
|
|
@click="switchLoginType('phone')"
|
|||
|
|
>
|
|||
|
|
手机号登录
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<form @submit.prevent="handleLogin" class="login-form">
|
|||
|
|
<!-- 账号登录表单 -->
|
|||
|
|
<div v-if="loginType === 'account'">
|
|||
|
|
<!-- 账号 -->
|
|||
|
|
<div class="form-group">
|
|||
|
|
<el-input
|
|||
|
|
v-model="formData.username"
|
|||
|
|
placeholder="请输入账号"
|
|||
|
|
:error="errors.username"
|
|||
|
|
prefix-icon="User"
|
|||
|
|
size="large"
|
|||
|
|
clearable
|
|||
|
|
/>
|
|||
|
|
<div v-if="errors.username" class="error-text">{{ errors.username }}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 密码 -->
|
|||
|
|
<div class="form-group">
|
|||
|
|
<el-input
|
|||
|
|
v-model="formData.password"
|
|||
|
|
placeholder="请输入密码"
|
|||
|
|
:error="errors.password"
|
|||
|
|
prefix-icon="Lock"
|
|||
|
|
size="large"
|
|||
|
|
:type="showPassword ? 'text' : 'password'"
|
|||
|
|
:show-password="true"
|
|||
|
|
autocomplete="new-password"
|
|||
|
|
/>
|
|||
|
|
<div v-if="errors.password" class="error-text">{{ errors.password }}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 记住密码 -->
|
|||
|
|
<div class="form-options">
|
|||
|
|
<el-checkbox v-model="formData.remember" label="记住密码" />
|
|||
|
|
<router-link to="/reset-password" class="forgot-password-link">忘记密码?</router-link>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 手机号登录表单 -->
|
|||
|
|
<div v-else>
|
|||
|
|
<!-- 手机号和发送验证码按钮 -->
|
|||
|
|
<div class="form-group">
|
|||
|
|
<div class="phone-code-row">
|
|||
|
|
<el-input
|
|||
|
|
v-model="formData.phone"
|
|||
|
|
type="tel"
|
|||
|
|
placeholder="请输入手机号"
|
|||
|
|
prefix-icon="Iphone"
|
|||
|
|
size="large"
|
|||
|
|
maxlength="11"
|
|||
|
|
@input="validatePhoneInput"
|
|||
|
|
@blur="validatePhoneOnBlur"
|
|||
|
|
clearable
|
|||
|
|
class="phone-input"
|
|||
|
|
/>
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
:disabled="codeBtnDisabled"
|
|||
|
|
@click="sendVerificationCode"
|
|||
|
|
class="send-code-btn-inline"
|
|||
|
|
size="large"
|
|||
|
|
>
|
|||
|
|
{{ codeButtonText }}
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="errors.phone" class="error-text">{{ errors.phone }}</div>
|
|||
|
|
<div v-else-if="formData.phone && formData.phone.length > 0 && formData.phone.length < 11" class="tip-text">请输入11位手机号码</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 验证码 -->
|
|||
|
|
<div class="form-group">
|
|||
|
|
<el-input
|
|||
|
|
v-model="formData.code"
|
|||
|
|
placeholder="请输入验证码"
|
|||
|
|
prefix-icon="Key"
|
|||
|
|
size="large"
|
|||
|
|
maxlength="6"
|
|||
|
|
/>
|
|||
|
|
<div v-if="errors.code" class="error-text">{{ errors.code }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 登录按钮 -->
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
native-type="submit"
|
|||
|
|
:loading="loading"
|
|||
|
|
class="login-btn"
|
|||
|
|
size="large"
|
|||
|
|
>
|
|||
|
|
{{ loading ? '登录中...' : '登录' }}
|
|||
|
|
</el-button>
|
|||
|
|
|
|||
|
|
<!-- 注册链接 -->
|
|||
|
|
<div class="register-link">
|
|||
|
|
<span>还没有账号?</span>
|
|||
|
|
<router-link to="/register" class="link">立即注册</router-link>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { userStore } from '../store/user'
|
|||
|
|
import { lotteryApi } from '../api/index.js'
|
|||
|
|
import { useToast } from 'vue-toastification'
|
|||
|
|
import { useRouter } from 'vue-router'
|
|||
|
|
import { ElCard, ElInput, ElButton, ElCheckbox, ElAlert } from 'element-plus'
|
|||
|
|
import { User, Lock, Iphone, Key } from '@element-plus/icons-vue'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'Login',
|
|||
|
|
components: {
|
|||
|
|
ElCard,
|
|||
|
|
ElInput,
|
|||
|
|
ElButton,
|
|||
|
|
ElCheckbox,
|
|||
|
|
ElAlert,
|
|||
|
|
User,
|
|||
|
|
Lock,
|
|||
|
|
Iphone,
|
|||
|
|
Key
|
|||
|
|
},
|
|||
|
|
setup() {
|
|||
|
|
const toast = useToast()
|
|||
|
|
const router = useRouter()
|
|||
|
|
return { toast, router }
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
loginType: 'account', // 默认使用账号登录方式
|
|||
|
|
showPassword: false,
|
|||
|
|
loading: false,
|
|||
|
|
codeCountdown: 0,
|
|||
|
|
timer: null,
|
|||
|
|
showPhoneError: false,
|
|||
|
|
phoneValid: false,
|
|||
|
|
showKickedOutAlert: false, // 是否显示被踢出提示
|
|||
|
|
|
|||
|
|
formData: {
|
|||
|
|
username: '',
|
|||
|
|
password: '',
|
|||
|
|
phone: '',
|
|||
|
|
code: '',
|
|||
|
|
remember: false
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
errors: {}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
computed: {
|
|||
|
|
codeButtonText() {
|
|||
|
|
return this.codeCountdown > 0 ? `${this.codeCountdown}秒后重试` : '获取验证码';
|
|||
|
|
},
|
|||
|
|
codeBtnDisabled() {
|
|||
|
|
return this.codeCountdown > 0 || !this.isValidPhone(this.formData.phone);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
// 检查是否因为被踢出而跳转到登录页
|
|||
|
|
if (userStore.isKickedOut) {
|
|||
|
|
this.showKickedOutAlert = true
|
|||
|
|
// 显示提示后重置状态
|
|||
|
|
setTimeout(() => {
|
|||
|
|
userStore.resetKickedOutStatus()
|
|||
|
|
}, 500)
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
// 切换登录方式
|
|||
|
|
switchLoginType(type) {
|
|||
|
|
this.loginType = type;
|
|||
|
|
this.errors = {};
|
|||
|
|
|
|||
|
|
// 切换时重置相关表单数据
|
|||
|
|
if (type === 'account') {
|
|||
|
|
this.formData.phone = '';
|
|||
|
|
this.formData.code = '';
|
|||
|
|
} else {
|
|||
|
|
this.formData.username = '';
|
|||
|
|
this.formData.password = '';
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 手机号格式验证
|
|||
|
|
isValidPhone(phone) {
|
|||
|
|
return /^1[3-9]\d{9}$/.test(phone);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 手机号输入时验证
|
|||
|
|
validatePhoneInput() {
|
|||
|
|
// 清除之前的错误
|
|||
|
|
this.errors.phone = '';
|
|||
|
|
this.showPhoneError = false;
|
|||
|
|
this.phoneValid = false;
|
|||
|
|
|
|||
|
|
const phone = this.formData.phone;
|
|||
|
|
|
|||
|
|
// 如果输入不是数字,替换非数字字符
|
|||
|
|
if (!/^\d*$/.test(phone)) {
|
|||
|
|
this.formData.phone = phone.replace(/\D/g, '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果长度达到11位,验证格式
|
|||
|
|
if (phone.length === 11) {
|
|||
|
|
if (this.isValidPhone(phone)) {
|
|||
|
|
this.phoneValid = true;
|
|||
|
|
} else {
|
|||
|
|
this.errors.phone = '手机号格式不正确';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 手机号失焦时验证
|
|||
|
|
validatePhoneOnBlur() {
|
|||
|
|
const phone = this.formData.phone;
|
|||
|
|
|
|||
|
|
if (phone && phone.length > 0) {
|
|||
|
|
if (phone.length !== 11) {
|
|||
|
|
this.errors.phone = '手机号应为11位数字';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
this.phoneValid = false;
|
|||
|
|
} else if (!this.isValidPhone(phone)) {
|
|||
|
|
this.errors.phone = '请输入正确的手机号码';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
this.phoneValid = false;
|
|||
|
|
} else {
|
|||
|
|
this.phoneValid = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 发送验证码
|
|||
|
|
async sendVerificationCode() {
|
|||
|
|
if (!this.formData.phone) {
|
|||
|
|
this.errors.phone = '请输入手机号';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
this.phoneValid = false;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!this.isValidPhone(this.formData.phone)) {
|
|||
|
|
this.errors.phone = '请输入正确的手机号码';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
this.phoneValid = false;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.phoneValid = true;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 开始倒计时 (先启动倒计时,避免API延迟导致用户体验不佳)
|
|||
|
|
this.codeCountdown = 60;
|
|||
|
|
this.startCodeCountdown();
|
|||
|
|
|
|||
|
|
const response = await lotteryApi.sendSmsCode(this.formData.phone);
|
|||
|
|
|
|||
|
|
if (response.success) {
|
|||
|
|
this.toast.success('验证码已发送,请注意查收');
|
|||
|
|
} else {
|
|||
|
|
// 如果发送失败,停止倒计时
|
|||
|
|
this.codeCountdown = 0;
|
|||
|
|
clearInterval(this.timer);
|
|||
|
|
this.toast.error(response.message || '发送验证码失败,请稍后重试');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
// 如果发送出错,停止倒计时
|
|||
|
|
this.codeCountdown = 0;
|
|||
|
|
clearInterval(this.timer);
|
|||
|
|
console.error('发送验证码失败:', error);
|
|||
|
|
this.toast.error('发送验证码失败,请稍后重试');
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 开始倒计时
|
|||
|
|
startCodeCountdown() {
|
|||
|
|
if (this.timer) {
|
|||
|
|
clearInterval(this.timer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.timer = setInterval(() => {
|
|||
|
|
if (this.codeCountdown > 0) {
|
|||
|
|
this.codeCountdown--;
|
|||
|
|
} else {
|
|||
|
|
clearInterval(this.timer);
|
|||
|
|
this.timer = null;
|
|||
|
|
}
|
|||
|
|
}, 1000);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 表单验证
|
|||
|
|
validateForm() {
|
|||
|
|
this.errors = {};
|
|||
|
|
|
|||
|
|
if (this.loginType === 'account') {
|
|||
|
|
// 账号登录验证
|
|||
|
|
if (!this.formData.username) {
|
|||
|
|
this.errors.username = '请输入账号';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!this.formData.password) {
|
|||
|
|
this.errors.password = '请输入密码';
|
|||
|
|
} else if (this.formData.password.length < 6) {
|
|||
|
|
this.errors.password = '密码至少6位';
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 手机号登录验证
|
|||
|
|
if (!this.formData.phone) {
|
|||
|
|
this.errors.phone = '请输入手机号';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
} else if (!this.isValidPhone(this.formData.phone)) {
|
|||
|
|
this.errors.phone = '请输入正确的手机号码';
|
|||
|
|
this.showPhoneError = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!this.formData.code) {
|
|||
|
|
this.errors.code = '请输入验证码';
|
|||
|
|
} else if (this.formData.code.length < 4 || this.formData.code.length > 6) {
|
|||
|
|
this.errors.code = '验证码格式不正确';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Object.keys(this.errors).length === 0;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理登录
|
|||
|
|
async handleLogin() {
|
|||
|
|
if (!this.validateForm()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.loading = true;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let response;
|
|||
|
|
|
|||
|
|
if (this.loginType === 'account') {
|
|||
|
|
// 账号密码登录
|
|||
|
|
response = await lotteryApi.userLogin(this.formData.username, this.formData.password);
|
|||
|
|
} else {
|
|||
|
|
// 手机号验证码登录
|
|||
|
|
response = await lotteryApi.userPhoneLogin(this.formData.phone, this.formData.code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (response.success === true) {
|
|||
|
|
// 登录成功,调用getLoginUser获取完整用户信息
|
|||
|
|
try {
|
|||
|
|
const userInfo = await userStore.fetchLoginUser();
|
|||
|
|
if (userInfo) {
|
|||
|
|
// 触发Coze SDK重新初始化事件
|
|||
|
|
setTimeout(() => {
|
|||
|
|
window.dispatchEvent(new CustomEvent('reinitializeCozeSDK'));
|
|||
|
|
console.log('已触发Coze SDK重新初始化事件');
|
|||
|
|
}, 200);
|
|||
|
|
|
|||
|
|
// 直接跳转到个人中心
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.router.push('/profile');
|
|||
|
|
}, 300);
|
|||
|
|
} else {
|
|||
|
|
this.toast.error('获取用户信息失败,请重新登录');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户信息失败:', error);
|
|||
|
|
this.toast.error('获取用户信息失败,请重新登录');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 登录失败
|
|||
|
|
this.toast.error(response.message || '登录失败,请检查账号密码');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('登录失败:', error);
|
|||
|
|
if (error.response && error.response.data) {
|
|||
|
|
this.toast.error(error.response.data.message || '登录失败,请检查账号密码');
|
|||
|
|
} else {
|
|||
|
|
this.toast.error('网络错误,请重试');
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
this.loading = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
// 组件销毁时清除定时器
|
|||
|
|
beforeUnmount() {
|
|||
|
|
if (this.timer) {
|
|||
|
|
clearInterval(this.timer);
|
|||
|
|
this.timer = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
/* 登录页面容器 */
|
|||
|
|
.login-page-container {
|
|||
|
|
min-height: calc(100vh - 70px);
|
|||
|
|
background: #f0f2f5;
|
|||
|
|
padding: 20px 20px 8px 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 页面头部 */
|
|||
|
|
.page-header {
|
|||
|
|
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
|
|||
|
|
color: white;
|
|||
|
|
padding: 35px 20px 25px;
|
|||
|
|
text-align: center;
|
|||
|
|
position: relative;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
box-shadow: 0 4px 20px rgba(238, 90, 82, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.page-title {
|
|||
|
|
margin: 0;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.main-title {
|
|||
|
|
font-size: 32px;
|
|||
|
|
margin: 0 auto 4px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
color: white;
|
|||
|
|
text-shadow: 0 2px 8px rgba(0,0,0,0.5), 0 0 20px rgba(0,0,0,0.3);
|
|||
|
|
letter-spacing: 1px;
|
|||
|
|
text-align: center;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.subtitle {
|
|||
|
|
font-size: 16px;
|
|||
|
|
margin: 0;
|
|||
|
|
color: white;
|
|||
|
|
opacity: 0.95;
|
|||
|
|
text-shadow: 0 2px 4px rgba(0,0,0,0.4);
|
|||
|
|
text-align: center;
|
|||
|
|
width: 100%;
|
|||
|
|
font-weight: 400;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 桌面端样式 */
|
|||
|
|
@media (min-width: 1024px) {
|
|||
|
|
.page-header {
|
|||
|
|
padding: 30px 20px 25px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-form-container {
|
|||
|
|
padding: 0;
|
|||
|
|
background: white;
|
|||
|
|
margin: 0 0 20px 0;
|
|||
|
|
border-radius: 16px;
|
|||
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 登录方式切换标签 */
|
|||
|
|
.login-tabs {
|
|||
|
|
display: flex;
|
|||
|
|
border-bottom: 1px solid #f0f0f0;
|
|||
|
|
background: #fafafa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-tab {
|
|||
|
|
flex: 1;
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 18px 0;
|
|||
|
|
font-size: 15px;
|
|||
|
|
color: #888;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
position: relative;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-tab.active {
|
|||
|
|
color: #e53e3e;
|
|||
|
|
background: white;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
.login-tab:hover:not(.active) {
|
|||
|
|
color: #666;
|
|||
|
|
background: #f8f8f8;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-form {
|
|||
|
|
background: white;
|
|||
|
|
border-radius: 0;
|
|||
|
|
padding: 28px 24px 20px;
|
|||
|
|
box-shadow: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 表单组 */
|
|||
|
|
.form-group {
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper {
|
|||
|
|
position: relative;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
border: 2px solid #e9ecef;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
min-height: 56px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper:focus-within {
|
|||
|
|
border-color: #e9ecef;
|
|||
|
|
background: white;
|
|||
|
|
box-shadow: none;
|
|||
|
|
outline: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
input:focus {
|
|||
|
|
outline: none;
|
|||
|
|
box-shadow: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper.error {
|
|||
|
|
border-color: #dc3545;
|
|||
|
|
background: #fff5f5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper.success {
|
|||
|
|
border-color: #4caf50;
|
|||
|
|
background: #f8fff8;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-icon {
|
|||
|
|
padding: 12px 25px;
|
|||
|
|
color: #6c757d;
|
|||
|
|
font-size: 18px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
width: 24px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.icon-img {
|
|||
|
|
width: 22px;
|
|||
|
|
height: 22px;
|
|||
|
|
object-fit: contain;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-input {
|
|||
|
|
flex: 1;
|
|||
|
|
padding: 16px 8px;
|
|||
|
|
border: none;
|
|||
|
|
outline: none;
|
|||
|
|
font-size: 16px;
|
|||
|
|
background: transparent;
|
|||
|
|
color: #212529;
|
|||
|
|
box-shadow: none;
|
|||
|
|
-webkit-appearance: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-input:focus {
|
|||
|
|
outline: none;
|
|||
|
|
border: none;
|
|||
|
|
box-shadow: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 控制浏览器自动填充的样式 */
|
|||
|
|
input:-webkit-autofill,
|
|||
|
|
input:-webkit-autofill:hover,
|
|||
|
|
input:-webkit-autofill:focus,
|
|||
|
|
input:-webkit-autofill:active {
|
|||
|
|
-webkit-box-shadow: 0 0 0 30px white inset !important;
|
|||
|
|
-webkit-text-fill-color: #212529 !important;
|
|||
|
|
transition: background-color 5000s ease-in-out 0s;
|
|||
|
|
border: none !important;
|
|||
|
|
outline: none !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 手机号和验证码按钮同行布局 */
|
|||
|
|
.phone-code-row {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12px;
|
|||
|
|
align-items: stretch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.phone-input {
|
|||
|
|
flex: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 内联发送验证码按钮样式 */
|
|||
|
|
.send-code-btn-inline {
|
|||
|
|
background: linear-gradient(135deg, #e53e3e, #ff6b6b);
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
min-width: 120px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
height: auto;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-code-btn-inline:hover:not(.is-disabled) {
|
|||
|
|
background: linear-gradient(135deg, #d43030, #ff5a5a);
|
|||
|
|
transform: translateY(-1px);
|
|||
|
|
box-shadow: 0 4px 15px rgba(229, 62, 62, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-code-btn-inline:active:not(.is-disabled) {
|
|||
|
|
transform: translateY(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-code-btn-inline.is-disabled {
|
|||
|
|
background: #cccccc !important;
|
|||
|
|
border-color: #cccccc !important;
|
|||
|
|
color: #888 !important;
|
|||
|
|
transform: none !important;
|
|||
|
|
box-shadow: none !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 提示文本 */
|
|||
|
|
.error-text {
|
|||
|
|
color: #ff4444;
|
|||
|
|
font-size: 12px;
|
|||
|
|
margin-top: 5px;
|
|||
|
|
margin-left: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tip-text {
|
|||
|
|
color: #888888;
|
|||
|
|
font-size: 12px;
|
|||
|
|
margin-top: 5px;
|
|||
|
|
margin-left: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 隐藏浏览器自带的密码控件 */
|
|||
|
|
input::-ms-reveal,
|
|||
|
|
input::-ms-clear {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
input::-webkit-credentials-auto-fill-button {
|
|||
|
|
visibility: hidden;
|
|||
|
|
position: absolute;
|
|||
|
|
right: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-input::placeholder {
|
|||
|
|
color: #9ca3af;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.password-toggle {
|
|||
|
|
padding: 0 15px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.toggle-icon-img {
|
|||
|
|
width: 22px;
|
|||
|
|
height: 22px;
|
|||
|
|
object-fit: contain;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 表单选项 */
|
|||
|
|
.form-options {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.checkbox-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.checkbox-wrapper input {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.checkmark {
|
|||
|
|
width: 16px;
|
|||
|
|
height: 16px;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
margin-right: 8px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 10px;
|
|||
|
|
color: transparent;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.checkbox-wrapper input:checked + .checkmark {
|
|||
|
|
background: #e53e3e;
|
|||
|
|
border-color: #e53e3e;
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 忘记密码链接 */
|
|||
|
|
.forgot-password-link {
|
|||
|
|
color: #e53e3e;
|
|||
|
|
text-decoration: none;
|
|||
|
|
font-size: 14px;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.forgot-password-link:hover {
|
|||
|
|
text-decoration: underline;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 登录按钮 */
|
|||
|
|
.login-btn {
|
|||
|
|
width: 100%;
|
|||
|
|
margin: 24px 0 24px 0;
|
|||
|
|
padding: 14px;
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
height: 52px;
|
|||
|
|
background: linear-gradient(135deg, #e53e3e, #ff6b6b);
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
box-shadow: 0 4px 20px rgba(229, 62, 62, 0.25);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-btn:hover:not(:disabled) {
|
|||
|
|
transform: translateY(-1px);
|
|||
|
|
box-shadow: 0 6px 30px rgba(229, 62, 62, 0.35);
|
|||
|
|
background: linear-gradient(135deg, #d43030, #ff5a5a);
|
|||
|
|
border: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-btn:active:not(:disabled) {
|
|||
|
|
transform: translateY(0);
|
|||
|
|
box-shadow: 0 2px 10px rgba(229, 62, 62, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Element UI 组件自定义样式 */
|
|||
|
|
:deep(.el-input__wrapper) {
|
|||
|
|
padding: 6px 16px;
|
|||
|
|
box-shadow: none !important;
|
|||
|
|
background-color: #f8f9fa;
|
|||
|
|
border: 2px solid #e9ecef;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
:deep(.el-input__wrapper.is-focus) {
|
|||
|
|
background-color: #fff;
|
|||
|
|
border-color: #e53e3e;
|
|||
|
|
box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
:deep(.el-input__prefix) {
|
|||
|
|
margin-right: 12px;
|
|||
|
|
color: #999;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.el-input__inner) {
|
|||
|
|
height: 44px;
|
|||
|
|
font-size: 15px;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.el-checkbox__label) {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.el-checkbox__inner) {
|
|||
|
|
border-color: #ddd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
|||
|
|
background-color: #e53e3e;
|
|||
|
|
border-color: #e53e3e;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
:deep(.el-button.is-disabled) {
|
|||
|
|
background: #cccccc;
|
|||
|
|
border-color: #cccccc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 注册链接 */
|
|||
|
|
.register-link {
|
|||
|
|
text-align: center;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.register-link .link {
|
|||
|
|
color: #e53e3e;
|
|||
|
|
text-decoration: none;
|
|||
|
|
margin-left: 5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.register-link .link:hover {
|
|||
|
|
text-decoration: underline;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 响应式设计 */
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.login-page-container {
|
|||
|
|
padding: 10px 10px 5px 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-form {
|
|||
|
|
padding: 22px 20px 18px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 480px) {
|
|||
|
|
.login-page-container {
|
|||
|
|
padding: 5px 5px 3px 5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.page-header {
|
|||
|
|
padding: 30px 20px 25px;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.page-title {
|
|||
|
|
margin: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.main-title {
|
|||
|
|
font-size: 28px;
|
|||
|
|
margin-bottom: 3px;
|
|||
|
|
letter-spacing: 0.5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.subtitle {
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-form {
|
|||
|
|
padding: 28px 20px 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-tab {
|
|||
|
|
padding: 16px 0;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.el-input__inner) {
|
|||
|
|
height: 42px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.login-btn {
|
|||
|
|
height: 48px;
|
|||
|
|
font-size: 15px;
|
|||
|
|
margin: 20px 0 20px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-code-btn-inline {
|
|||
|
|
font-size: 12px;
|
|||
|
|
min-width: 90px;
|
|||
|
|
padding: 0 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.phone-code-row {
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 桌面端样式 - 这部分已在上面定义,这里移除重复定义 */
|
|||
|
|
</style>
|