This commit is contained in:
2026-04-04 08:52:59 +08:00
parent 66bcd8061a
commit d98ac8f146
33 changed files with 2565 additions and 328 deletions
+569 -55
View File
@@ -39,7 +39,7 @@
min-height: 100vh;
line-height: 1.6;
padding: 20px;
_background-image: radial-gradient(at 0 0,rgba(var(--primary-rgb),0.05) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(var(--secondary-rgb),0.05) 0,transparent 50%)
background-image: radial-gradient(at 0 0,rgba(39, 186, 87, 0.05) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(39, 186, 87, 0.05) 0,transparent 50%)
}
.container {
@@ -50,13 +50,17 @@
box-shadow: var(--shadow);
overflow: hidden;
position: relative;
z-index: 1
z-index: 1;
transition: var(--transition);
}
.container:hover {
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
}
.container::before {
content: '';
position: absolute;
display: none;
top: 0;
left: 0;
width: 100%;
@@ -68,7 +72,9 @@
.header {
padding: 40px 40px 30px;
text-align: center
text-align: center;
position: relative;
z-index: 1;
}
.logo {
@@ -80,7 +86,13 @@
align-items: center;
justify-content: center;
box-shadow: var(--shadow);
margin-bottom: 20px
margin-bottom: 20px;
transition: var(--transition);
}
.logo:hover {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
}
.logo svg {
@@ -99,7 +111,8 @@
}
.header p {
font-size: 14pt
font-size: 14pt;
color: var(--text-light);
}
.card {
@@ -161,10 +174,29 @@
.form-control:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(67,97,238,0.15);
box-shadow: 0 0 0 3px rgba(39, 186, 87, 0.15);
outline: 0
}
.form-control.error {
border-color: #f56c6c;
}
.form-control.success {
border-color: var(--primary);
}
.error-message {
color: #f56c6c;
font-size: 12px;
margin-top: 4px;
display: none;
}
.error-message.show {
display: block;
}
.captcha-group {
display: flex;
gap: 12px
@@ -184,11 +216,15 @@
font-weight: 600;
cursor: pointer;
transition: var(--transition);
padding: 0 16px
padding: 0 16px;
display: flex;
align-items: center;
justify-content: center;
}
.captcha-btn:hover {
background-color: rgba(67,97,238,0.1)
.captcha-btn:hover:not(:disabled) {
background-color: rgba(39, 186, 87, 0.1);
transform: translateY(-1px);
}
.captcha-btn:disabled {
@@ -209,18 +245,26 @@
cursor: pointer;
transition: var(--transition);
margin-top: 10px;
box-shadow: 0 4px 12px rgba(67,97,238,0.2)
box-shadow: 0 4px 12px rgba(39, 186, 87, 0.2);
position: relative;
overflow: hidden;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(67,97,238,0.3)
box-shadow: 0 6px 16px rgba(39, 186, 87, 0.3)
}
.btn:active {
transform: translateY(0)
}
.btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.footer {
margin-top: 30px;
text-align: center;
@@ -239,7 +283,6 @@
text-decoration: underline
}
.other-options {
margin-top: 20px;
text-align: center;
@@ -282,6 +325,14 @@
height: 60px;
border-radius: 15px;
}
.header h1 {
font-size: 20px;
}
.header p {
font-size: 12pt;
}
}
/*成功卡片*/
.success-card {
@@ -447,6 +498,184 @@
}
}
.region .layui-select-title .layui-input{
height: 51px;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
/* 密码强度指示器 */
.password-strength {
display: flex;
gap: 4px;
margin-top: 8px;
}
.strength-bar {
flex: 1;
height: 4px;
border-radius: 2px;
background-color: var(--border);
transition: var(--transition);
}
.strength-bar.weak {
background-color: #f56c6c;
}
.strength-bar.medium {
background-color: #e6a23c;
}
.strength-bar.strong {
background-color: var(--primary);
}
.strength-text {
font-size: 12px;
color: var(--text-light);
margin-top: 4px;
}
.strength-text.weak {
color: #f56c6c;
}
.strength-text.medium {
color: #e6a23c;
}
.strength-text.strong {
color: var(--primary);
}
/* 加载动画 */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 输入框图标 */
.input-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--text-light);
}
.form-control.has-icon {
padding-left: 40px;
}
/* 验证码按钮加载状态 */
.captcha-btn.loading {
position: relative;
pointer-events: none;
}
.captcha-btn.loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
border: 2px solid rgba(39, 186, 87, 0.3);
border-radius: 50%;
border-top-color: var(--primary);
animation: spin 1s ease-in-out infinite;
}
/* 响应式优化 */
@media (max-width: 768px) {
.container {
padding: 16px;
max-width: 95%;
}
.card {
padding: 0 24px 32px;
}
.form-group {
margin-bottom: 16px;
}
.btn {
height: 48px;
font-size: 16px;
}
.captcha-btn {
min-width: 100px;
font-size: 13px;
}
}
/* 动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.form-container {
animation: fadeInUp 0.3s ease-out;
}
/* 错误消息优化 */
.error-message {
font-size: 12px;
color: #f56c6c;
margin-top: 4px;
opacity: 0;
height: 0;
overflow: hidden;
transition: var(--transition);
}
.error-message.show {
opacity: 1;
height: auto;
min-height: 16px;
}
/* 星星装饰优化 */
.star {
position: absolute;
background-color: #fff;
border-radius: 50%;
animation: twinkle 3s infinite;
}
@keyframes twinkle {
0%, 100% {
opacity: 0.3;
}
50% {
opacity: 1;
}
}
/* 庆祝效果 */
.confetti {
position: absolute;
border-radius: 2px;
}
</style>
{/literal}
</head>
@@ -819,52 +1048,77 @@
</svg>
</div>
<h1>欢迎回来</h1>
<p>使用手机号注册您的账户</p>
<p>使用手机号或邮箱注册您的账户</p>
</div>
<div class="card" data-type="register">
<!-- 注册表单 -->
<div class="form-container active" id="register-form">
<form id="registerForm" class="layui-form" method="post" action="/api/common/register">
<div class="form-group">
<label for="register-phone">手机号码</label>
<div class="input-wrapper">
<input type="tel" name="mobile" lay-reqtext="请输入您的手机号码" id="register-phone" class="form-control" placeholder="请输入您的手机号码" required lay-verify="required|phone">
<input type="hidden" name="type" id="register_type" value="mobile" />
<div class="layui-tab-item layui-show">
<div class="form-group">
<label for="register-phone">手机号/邮箱</label>
<div class="input-wrapper">
<i class="layui-icon layui-icon-cellphone input-icon"></i>
<input type="text" name="mobile"
lay-reqtext="请输入您的手机号或邮箱"
id="register-phone"
class="form-control has-icon"
placeholder="请输入您的手机号或邮箱"
required
lay-verify="require|username" />
</div>
</div>
</div>
<div class="error-message" id="phone-error"></div>
<div class="form-group" style="display: none;">
<label for="register-password">我的昵称</label>
<div class="input-wrapper">
<input type="text" name="nickname" lay-reqtext="请设置您的昵称" id="register-nickname" class="form-control" placeholder="给自己取一个好听的昵称吧" required lay-verify="required">
<i class="layui-icon layui-icon-username input-icon"></i>
<input type="text" name="nickname" lay-reqtext="请设置您的昵称" id="register-nickname" class="form-control has-icon" placeholder="给自己取一个好听的昵称吧" lay-verify="">
</div>
</div>
<div class="form-group">
<label for="register-captcha">验证码</label>
<div class="captcha-group">
<div class="input-wrapper" style="flex: 1;">
<input type="text" lay-reqtext="请输入验证码" name="code" id="register-captcha" class="form-control captcha-input"
<i class="layui-icon layui-icon-vercode input-icon"></i>
<input type="text" lay-reqtext="请输入验证码" name="code" id="register-captcha" class="form-control captcha-input has-icon"
placeholder="请输入验证码" required lay-verify="required|number">
</div>
<button type="button" class="captcha-btn" id="register-get-captcha">获取验证码</button>
</div>
</div>
<div class="error-message" id="captcha-error"></div>
<div class="form-group">
<label for="register-password">设置密码</label>
<div class="input-wrapper">
<input type="password" lay-reqtext="请设置您的登录密码" name="password" id="register-password" class="form-control" placeholder="请输入6-20位密码"
<i class="layui-icon layui-icon-password input-icon"></i>
<input type="password" lay-reqtext="请设置您的登录密码" name="password" id="register-password" class="form-control has-icon" placeholder="请输入6-20位密码"
required lay-verify="required|password">
</div>
<div class="password-strength">
<div class="strength-bar" id="strength-bar-1"></div>
<div class="strength-bar" id="strength-bar-2"></div>
<div class="strength-bar" id="strength-bar-3"></div>
</div>
<div class="strength-text" id="strength-text">密码强度:弱</div>
</div>
<div class="error-message" id="password-error"></div>
<div class="form-group">
<label for="register-password">确认密码</label>
<div class="input-wrapper">
<input type="password" lay-reqtext="请再次输入密码" name="repassword" id="register-repassword" class="form-control" placeholder="请输入6-20位密码"
<i class="layui-icon layui-icon-password input-icon"></i>
<input type="password" lay-reqtext="请再次输入密码" name="repassword" id="register-repassword" class="form-control has-icon" placeholder="请输入6-20位密码"
required lay-verify="required|repassword">
</div>
</div>
<div class="error-message" id="repassword-error"></div>
<div class="form-group">
<label for="register-password">邀请码</label>
<div class="input-wrapper">
<input type="text" readonly class="form-control" lay-reqtext="参数错误" value="{$invite_code}" name="invite_code" required lay-verify="required">
<i class="layui-icon layui-icon-code input-icon"></i>
<input type="text" readonly class="form-control has-icon" lay-reqtext="参数错误" value="{$invite_code}" name="invite_code" required lay-verify="required">
</div>
</div>
<div class="form-group">
@@ -904,15 +1158,162 @@
{literal}
<script type="text/javascript">
layui.use(function(){
var element = layui.element;
var layer = layui.layer;
var form = layui.form;
var $ = layui.jquery;
$('[data-type="register"]').show();
$('[data-type="success"]').hide();
// 初始化页面
function initPage() {
$('[data-type="register"]').show();
$('[data-type="success"]').hide();
generateStars();
}
// 生成星星装饰
function generateStars() {
const stars = document.getElementById('stars');
if (stars) {
for (let i = 0; i < 50; i++) {
const star = document.createElement('div');
star.className = 'star';
star.style.left = Math.random() * 100 + '%';
star.style.top = Math.random() * 100 + '%';
star.style.width = Math.random() * 3 + 1 + 'px';
star.style.height = star.style.width;
star.style.animationDelay = Math.random() * 3 + 's';
stars.appendChild(star);
}
}
}
// 密码强度检测
function checkPasswordStrength(password) {
let strength = 0;
if (password.length >= 6) strength++;
if (password.length >= 10) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^A-Za-z0-9]/.test(password)) strength++;
// 更新密码强度指示器
const strengthBars = document.querySelectorAll('.strength-bar');
const strengthText = document.getElementById('strength-text');
strengthBars.forEach((bar, index) => {
bar.className = 'strength-bar';
if (index < Math.floor(strength / 2)) {
if (strength <= 2) {
bar.classList.add('weak');
} else if (strength <= 4) {
bar.classList.add('medium');
} else {
bar.classList.add('strong');
}
}
});
if (strength <= 2) {
strengthText.textContent = '密码强度:弱';
strengthText.className = 'strength-text weak';
} else if (strength <= 4) {
strengthText.textContent = '密码强度:中';
strengthText.className = 'strength-text medium';
} else {
strengthText.textContent = '密码强度:强';
strengthText.className = 'strength-text strong';
}
}
// 输入框验证
function validateInput(input, errorId, message) {
const errorElement = document.getElementById(errorId);
if (input.value.trim() === '') {
input.classList.add('error');
errorElement.textContent = message;
errorElement.classList.add('show');
return false;
} else {
input.classList.remove('error');
errorElement.classList.remove('show');
return true;
}
}
// 实时验证
$('#register-phone').on('blur', function() {
validateInput(this, 'phone-error', '请输入手机号或邮箱');
});
$('#register-captcha').on('blur', function() {
validateInput(this, 'captcha-error', '请输入验证码');
});
$('#register-password').on('input', function() {
checkPasswordStrength(this.value);
validateInput(this, 'password-error', '请设置密码');
});
$('#register-password').on('blur', function() {
validateInput(this, 'password-error', '请设置密码');
});
$('#register-repassword').on('blur', function() {
if (this.value !== $('#register-password').val()) {
this.classList.add('error');
document.getElementById('repassword-error').textContent = '两次密码输入不一致';
document.getElementById('repassword-error').classList.add('show');
return false;
} else {
this.classList.remove('error');
document.getElementById('repassword-error').classList.remove('show');
return true;
}
});
// hash 地址定位
var hashName = 'register_type'; // hash 名称
var layid = location.hash.replace(new RegExp('^#'+ hashName + '='), ''); // 获取 lay-id 值
layid = layid || 'mobile';
// 初始切换
element.tabChange('registe-type', layid);
// 切换事件
element.on('tab(registe-type)', function(obj){
location.hash = hashName +'='+ this.getAttribute('lay-id');
$('#register_type').val(this.getAttribute('lay-id'));
});
form.verify({
username:function(v,elem){
if(v.indexOf('@') == -1){
if(!v){return '手机号不能为空';}
const reg = /^1[3-9]\d{9}$/;
if(!reg.test(v)){
return '手机号码格式错误';
}
$('#register_type').val('mobile');
}else{
if(!v){return '手机号或邮箱不能为空';}
const reg = /^[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
if(!reg.test(v) || /[<>(){}[\]\\]/.test(v)){
return '邮箱格式错误';
}
$('#register_type').val('email');
}
},
phonerequire: function(value, elem) {
if($('#register_type').val() == 'mobile'){
if(!value){return '手机号不能为空';}
}
},
emailrequire: function(value, elem) {
if($('#register_type').val() == 'email'){
if(!value){return '邮箱不能为空';}
}
},
password: function(value, elem) {
if (value.length < 6) {
return '密码太过简单';
return '密码长度不能少于6位';
}
},
repassword: function(value, elem) {
@@ -921,68 +1322,181 @@
}
}
});
function check_exist(callback){
var elem = $('#register-phone');
var v = elem.val();
var msg='';
var data={};
var api_url='';
if($('#register_type').val() == 'email'){
api_url = '/api/Validate/check_email_exist';
data={'email':v};
msg='邮箱已经存在';
}else{
api_url = '/api/Validate/check_mobile_exist';
data={'mobile':v};
msg='手机号码已经存在';
}
$.post(api_url,data,function(res){
if(res.code === 0){
elem.removeClass('layui-form-danger').addClass('layui-form-danger');
layer.msg(msg,{icon:2});
return ;
}
callback && callback();
});
}
$('#register-get-captcha').on('click',function(){
var isValid = form.validate('#register-phone');
var msg = '请输入正确的手机号或邮箱';
var val = $('#register-phone').val();
var senddata = {
type:$('#register_type').val(),
mobile:val,
'event':'register'
};
if($('#register_type').val() == 'email'){
delete senddata['mobile'];
senddata['email'] = val;
}
// 验证通过
if(!isValid){
layer.msg('请输入正确的手机号',{icon:2});
layer.msg(msg,{icon:2});
return ;
}
var btn = $('#register-get-captcha').get(0);
var phone = $('#register-phone').val();
// 禁用按钮并开始倒计时
btn.disabled = true;
let countdown = 60;
btn.innerHTML = `${countdown}秒后重新获取`;
btn.classList.add('loading');
check_exist(function(){
$.post('/api/common/captcha',senddata,function(res){
btn.classList.remove('loading');
if(res.code === 0){
layer.msg(`验证码已发送`,{icon:1});
let countdown = 60;
btn.innerHTML = `${countdown}秒后重新获取`;
const timer = setInterval(() => {
countdown--;
btn.innerHTML = `${countdown}秒后重新获取`;
const timer = setInterval(() => {
countdown--;
btn.innerHTML = `${countdown}秒后重新获取`;
if (countdown <= 0) {
clearInterval(timer);
if (countdown <= 0) {
clearInterval(timer);
btn.disabled = false;
btn.innerHTML = '获取验证码';
}
}, 1000);
}else{
btn.disabled = false;
btn.innerHTML = '获取验证码';
layer.msg(res.msg,{icon:2});
}
}).fail(function() {
btn.classList.remove('loading');
btn.disabled = false;
btn.innerHTML = '获取验证码';
}
}, 1000);
// 这里应该是发送验证码的AJAX请求
console.log(`发送验证码到手机: ${phone}`);
$.post('/api/common/captcha',{type:'mobile',mobile:phone,'event':'register'},function(res){
if(res.code === 0){
console.log(res);
layer.msg(`验证码已发送到手机 ${phone} (模拟)`,{icon:1});
}else{
clearInterval(timer);
btn.disabled = false;
btn.innerHTML = '获取验证码';
}
});
layer.msg('网络错误,请稍后重试',{icon:2});
});
})
});
// 提交事件
form.on('submit(register-form)', function(data){
var field = data.field; // 获取表单字段值
// 显示填写结果,仅作演示用
field['type'] = 'mobile';
console.log(field);
// 此处可执行 Ajax 等操作
// 验证所有输入框
const phoneValid = validateInput($('#register-phone')[0], 'phone-error', '请输入手机号或邮箱');
const captchaValid = validateInput($('#register-captcha')[0], 'captcha-error', '请输入验证码');
const passwordValid = validateInput($('#register-password')[0], 'password-error', '请设置密码');
const repasswordValid = validateInput($('#register-repassword')[0], 'repassword-error', '请确认密码');
if (!phoneValid || !captchaValid || !passwordValid || !repasswordValid) {
layer.msg('请完善表单信息',{icon:2});
return false;
}
if ($('#register-password').val() !== $('#register-repassword').val()) {
$('#register-repassword')[0].classList.add('error');
document.getElementById('repassword-error').textContent = '两次密码输入不一致';
document.getElementById('repassword-error').classList.add('show');
layer.msg('两次密码输入不一致',{icon:2});
return false;
}
const btn = this;
const originalText = btn.innerHTML;
btn.innerHTML = `<span class="layui-icon layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop"></span> 注册中...`;
btn.disabled = true;
$.post('/api/common/register',field,function(res){
if(field['type'] == 'email'){
field['email'] = field['mobile'];
delete field['mobile'];
}
$.post('/api/common/register', field, function(res){
btn.innerHTML = originalText;
btn.disabled = false;
if(res.code === 0 ){
// 更新成功页面的用户名
if (res.data && res.data.username) {
$('#username').text(res.data.username);
}
// 显示成功页面
$('[data-type="register"]').hide();
$('[data-type="success"]').show();
// 生成庆祝效果
createConfetti();
}else{
layer.msg(res.msg,{icon:2});
}
}).fail(function() {
btn.innerHTML = originalText;
btn.disabled = false;
layer.msg('网络错误,请稍后重试',{icon:2});
});
return false; // 阻止默认 form 跳转
});
// 创建庆祝效果
function createConfetti() {
const successCard = document.querySelector('.success-card');
if (!successCard) return;
for (let i = 0; i < 100; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + '%';
confetti.style.top = Math.random() * 100 + '%';
confetti.style.backgroundColor = getRandomColor();
confetti.style.width = Math.random() * 10 + 5 + 'px';
confetti.style.height = Math.random() * 10 + 5 + 'px';
confetti.style.opacity = 1;
confetti.style.transform = 'translateY(0) rotate(0deg)';
confetti.style.transition = 'all ' + (Math.random() * 3 + 2) + 's ease-out';
successCard.appendChild(confetti);
setTimeout(() => {
confetti.style.opacity = 0;
confetti.style.transform = 'translateY(100px) rotate(720deg)';
setTimeout(() => {
confetti.remove();
}, 5000);
}, 100);
}
}
// 获取随机颜色
function getRandomColor() {
const colors = ['#f00', '#0f0', '#00f', '#ff0', '#f0f', '#0ff', '#333', '#666', '#999'];
return colors[Math.floor(Math.random() * colors.length)];
}
// 初始化页面
initPage();
});
</script>