Files
SMANAGE/TempLate/login.html
T
2026-06-25 17:56:08 +08:00

234 lines
12 KiB
HTML

<script>
// 父框架刷新逻辑不变
if (window.parent && window.parent !== window) {
window.parent.location.reload();
}
</script>
<div class="relative min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-100 p-4">
<div class="hidden md:flex md:w-1/2 items-center justify-start px-4 opacity-0 translate-x-[-20px] animate-fade-in-left">
<div class="text-center w-full">
<div class="w-56 h-56 mx-auto mb-8 rounded-2xl bg-blue-500/5 flex items-center justify-center p-6 transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-blue-500/5"></div>
<h2 class="text-3xl font-bold text-gray-900 mb-4 opacity-0 animate-fade-in delay-100">欢迎使用 G-ERP 系统</h2>
<p class="text-gray-600 mx-auto leading-relaxed opacity-0 animate-fade-in delay-200"> 高效、安全的企业级管理平台,为您的工作提供便捷支持<br> 请尽可能使用工号和密码登录,保障账户信息安全。 </p>
<div class="relative mt-12 h-64">
<div class="absolute top-0 left-1/4 w-8 h-8 rounded bg-blue-500/30 animate-pulse"></div>
<div class="absolute top-1/4 left-1/2 w-12 h-12 rounded bg-purple-500/30 animate-pulse delay-300"></div>
<div class="absolute bottom-1/4 left-1/3 w-10 h-10 rounded bg-green-500/30 animate-pulse delay-500"></div>
<div class="absolute bottom-0 left-2/3 w-14 h-14 rounded bg-orange-500/30 animate-pulse delay-700"></div>
</div>
</div>
</div>
<div class="w-full md:w-1/2 max-w-md opacity-0 translate-x-[20px] animate-fade-in-right">
<div class="bg-white dark:bg-gray-50 rounded-xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-blue-500/5">
<div class="relative h-24">
<img src="https://bing.img.run/uhd.php" alt="Card image" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-white to-transparent"></div>
<h4 class="absolute bottom-4 left-6 text-xl font-bold text-gray-900">登录</h4>
</div>
<!-- Toast固定容器,永远放在表单上方 -->
<div id="toastWrap" class="px-6 pt-6"></div>
<div class="p-6 pt-0">
<form class="space-y-4" id="loginForm">
<div class="relative">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none transition-colors duration-200">
<i class="tim-icons icon-single-02 text-gray-500 input-icon"></i>
</div>
<input type="text" name="UserName"
class="block w-full pl-10 pr-3 py-2.5 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 placeholder-gray-500 transition-all duration-200 focus:scale-[1.01] focus:bg-white" placeholder="昵称">
</div>
<div class="relative">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none transition-colors duration-200">
<i class="tim-icons icon-lock-circle text-gray-500 input-icon"></i>
</div>
<input type="password" name="UserPassword"
class="block w-full pl-10 pr-3 py-2.5 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 placeholder-gray-500 transition-all duration-200 focus:scale-[1.01] focus:bg-white" placeholder="密码" required>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="UserConsent" name="UserConsent" type="checkbox" class="w-4 h-4 rounded border-gray-300 text-blue-500 focus:ring-blue-500 bg-gray-100 transition-all duration-200 hover:border-gray-400 checked:scale-[0.95]">
</div>
<div class="ml-3 text-sm">
<label for="UserConsent" class="text-gray-700 transition-colors duration-200 hover:text-gray-900">
我阅读并同意
<a href="https://doc.gvsds.com/d2ae1dd63e9c35d687464a160896e1ab" target="_blank"
class="text-blue-600 hover:underline transition-colors duration-200 hover:text-blue-700">GVSDS用户协议</a>
</label>
</div>
</div>
<div class="text-sm text-gray-700">
我还没有账号,去
<a href="/register.html" class="text-blue-600 hover:underline transition-colors duration-200 hover:text-blue-700">注册</a>
</div>
<div class="pt-2">
<button type="submit" class="w-full py-3 px-4 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium transition-all duration-200 flex items-center justify-center hover:scale-[1.02] active:scale-[0.98] hover:shadow-lg hover:shadow-blue-500/10">
登录
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<style>
@keyframes fadeInLeft{from{opacity:0;transform:translateX(-20px)}to{opacity:1;transform:translateX(0)}}
.animate-fade-in-left{animation:fadeInLeft .5s ease-out forwards}
@keyframes fadeInRight{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:translateX(0)}}
.animate-fade-in-right{animation:fadeInRight .5s ease-out forwards}
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
.animate-fade-in{animation:fadeIn .3s ease-out forwards}
input:focus~.input-icon{color:#a855f7}
#LoginToast{
animation:fadeIn .3s ease-out forwards;
position:relative;
z-index:999;
width:100%;
box-sizing:border-box;
}
</style>
<script>
// 统一Toast渲染函数,替换原来插入逻辑
function showToast(content, type='error'){
const wrap = document.getElementById('toastWrap');
let toast = document.getElementById('LoginToast');
// 存在先清空内容
if(toast) toast.remove();
toast = document.createElement('div');
toast.id = 'LoginToast';
toast.className = 'mb-4 p-3 rounded-lg animate-fade-in';
if(type === 'error'){
toast.style.color='#dc2626';
toast.style.background='#fef2f2';
toast.style.border='1px solid #fecaca';
}else{
toast.style.color='#16a34a';
toast.style.background='#f0fdf4';
toast.style.border='1px solid #bbf7d0';
}
toast.textContent = content;
wrap.appendChild(toast);
}
// 页面URL参数消息,DOM加载完成后执行
document.addEventListener('DOMContentLoaded', ()=>{
const msg = new URLSearchParams(window.location.search).get('msg');
const urlRedirect = new URLSearchParams(window.location.search).get('redirect');
if(msg){
showToast(msg, 'error');
}
const loginForm = document.getElementById('loginForm');
document.querySelectorAll('label[for]').forEach(label=>{
label.addEventListener('click',()=>{
const targetId=label.getAttribute('for');
const targetEl=document.getElementById(targetId);
if(targetEl)targetEl.focus()
})
});
// 表单提交逻辑
loginForm.addEventListener('submit',function(e){
e.preventDefault();
const UserConsent=document.getElementById('UserConsent').checked;
if(!UserConsent){
showToast('必须签署协议');
return
}
const submitButton=this.querySelector('button[type="submit"]');
const originalText=submitButton.innerHTML;
submitButton.disabled=true;
submitButton.innerHTML='<i class="fa fa-spinner fa-spin mr-2"></i> 登录中...';
const formData=new FormData(loginForm);
fetch('https://ugm.gvsds.com/Auth/Login',{
method:'POST',
credentials:'include',
body:formData
}).then(res=>res.json()).then(result=>{
submitButton.disabled=false;
submitButton.innerHTML=originalText;
if(result.code===0){
showToast('登录成功,即将跳转...','success');
setTimeout(()=>{
window.location.href=urlRedirect||result.redirect||'/'
},500)
}else if(result.code===2&&result.require2FA){
show2FAForm(result.pendingKey)
}else{
showToast(result.msg||'登录失败')
}
}).catch(err=>{
submitButton.disabled=false;
submitButton.innerHTML=originalText;
showToast('网络错误:'+err.message)
})
});
// 两步验证弹窗逻辑
function show2FAForm(key){
const _pendingKey=key;
const formContainer=loginForm.parentElement;
formContainer.innerHTML=`
<div class="text-center mb-6">
<div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-blue-500/10 flex items-center justify-center">
<i class="fas fa-shield-halved text-2xl text-blue-500"></i>
</div>
<h3 class="text-lg font-semibold text-gray-900">两步验证</h3>
<p class="text-sm text-gray-500 mt-1">请输入身份验证器中的6位数字</p>
</div>
<div class="space-y-4">
<div class="relative">
<input type="text" id="twoFAToken" maxlength="6" inputmode="numeric" pattern="[0-9]*"
class="block w-full py-3 px-4 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 text-center text-2xl tracking-[8px] placeholder-gray-400 placeholder-tracking-normal placeholder-text-base"
placeholder="000000" autocomplete="one-time-code" autofocus>
</div>
<button id="verify2FABtn" class="w-full py-3 px-4 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium transition-all duration-200 flex items-center justify-center hover:scale-[1.02] active:scale-[0.98]">
验证
</button>
<button id="cancel2FABtn" class="w-full py-2 px-4 text-gray-500 hover:text-gray-700 text-sm transition-colors duration-200">
返回登录
</button>
</div>
`;
const tokenInput=document.getElementById('twoFAToken');
const verifyBtn=document.getElementById('verify2FABtn');
const cancelBtn=document.getElementById('cancel2FABtn');
tokenInput.focus();
tokenInput.addEventListener('input',function(){
this.value=this.value.replace(/[^0-9]/g,'')
});
verifyBtn.addEventListener('click',function(){
const token=tokenInput.value.trim();
if(!token||token.length!==6){
showToast('请输入6位验证码');
return
}
verifyBtn.disabled=true;
verifyBtn.innerHTML='<i class="fa fa-spinner fa-spin mr-2"></i> 验证中...';
fetch('https://ugm.gvsds.com/Auth/Verify2FA',{
method:'POST',
credentials:'include',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:`pendingKey=${encodeURIComponent(_pendingKey)}&token=${encodeURIComponent(token)}`
}).then(res=>res.json()).then(result=>{
if(result.code===0){
showToast('验证成功,即将跳转...','success');
setTimeout(()=>{
window.location.href=urlRedirect||result.redirect||'/'
},500)
}else{
verifyBtn.disabled=false;
verifyBtn.textContent='验证';
showToast(result.msg||'验证码错误')
}
}).catch(err=>{
verifyBtn.disabled=false;
verifyBtn.textContent='验证';
showToast('网络错误')
})
});
cancelBtn.addEventListener('click',()=>window.location.reload())
}
})
</script>