文件
chengduzhongchuliang/application/admin/view/index/login.html

492 行
17 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>{$title|default='登录'} - 中央储备粮遵义直属库有限公司</title>
<link rel="shortcut icon" href="/assets/img/favicon.ico" />
<style>
:root {
--primary: #0284c7;
--primary-hover: #0369a1;
--primary-glow: rgba(56, 189, 248, 0.35);
--bg-page: #0a1120;
--bg-card: rgba(15, 23, 42, 0.78);
--border-glass: rgba(56, 189, 248, 0.22);
--text-main: #f8fafc;
--text-sub: #94a3b8;
--input-bg: rgba(30, 41, 59, 0.7);
--input-border: rgba(148, 163, 184, 0.2);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
background-color: var(--bg-page);
color: var(--text-main);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 20px;
}
.login-bg {
position: fixed;
inset: 0;
background: {if $background}url('{$background}') center/cover no-repeat{else}radial-gradient(ellipse at 50% 30%, rgba(2, 132, 199, 0.2) 0%, transparent 65%), #070e1b{/if};
z-index: 0;
}
.login-card {
position: relative;
z-index: 1;
width: 100%;
max-width: 420px;
background: var(--bg-card);
border: 1px solid var(--border-glass);
border-radius: 16px;
padding: 40px 36px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), 0 0 30px rgba(56, 189, 248, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-logo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #0284c7, #0ea5e9);
box-shadow: 0 4px 14px var(--primary-glow);
margin-bottom: 16px;
}
.login-logo svg {
width: 26px;
height: 26px;
stroke: #fff;
fill: none;
stroke-width: 2;
}
.login-header h1 {
font-size: 19px;
font-weight: 600;
color: #ffffff;
letter-spacing: 0.5px;
margin-bottom: 6px;
}
.login-header p {
font-size: 13px;
color: var(--text-sub);
}
.form-item {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 13px;
color: #cbd5e1;
margin-bottom: 8px;
font-weight: 500;
}
.input-wrap {
position: relative;
display: flex;
align-items: center;
}
.input-icon {
position: absolute;
left: 14px;
width: 18px;
height: 18px;
stroke: #64748b;
fill: none;
stroke-width: 2;
pointer-events: none;
transition: stroke 0.2s;
}
.form-input {
width: 100%;
height: 44px;
padding: 0 14px 0 42px;
background: var(--input-bg);
border: 1px solid var(--input-border);
border-radius: 8px;
font-size: 14px;
color: #ffffff;
outline: none;
transition: all 0.2s ease;
}
.form-input:focus {
border-color: #38bdf8;
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.2);
background: rgba(15, 23, 42, 0.85);
}
.form-input:focus + .input-icon,
.input-wrap:focus-within .input-icon {
stroke: #38bdf8;
}
.password-toggle {
position: absolute;
right: 12px;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
color: #64748b;
transition: color 0.2s;
}
.password-toggle:hover {
color: #38bdf8;
}
.captcha-row {
display: flex;
gap: 12px;
}
.captcha-row .input-wrap {
flex: 1;
}
.captcha-img {
height: 44px;
min-width: 110px;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--input-border);
cursor: pointer;
background: #fff;
}
.captcha-img img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.form-options {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
font-size: 13px;
color: var(--text-sub);
}
.remember-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
}
.remember-label input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--primary);
cursor: pointer;
}
.login-btn {
width: 100%;
height: 44px;
background: linear-gradient(135deg, #0284c7, #0274ae);
color: #ffffff;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
letter-spacing: 2px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.2s ease;
box-shadow: 0 4px 16px var(--primary-glow);
}
.login-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #0369a1, #0284c7);
transform: translateY(-1px);
}
.login-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 0.8s linear infinite;
display: none;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.toast-box {
position: fixed;
top: 24px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
color: #fff;
opacity: 0;
pointer-events: none;
transition: all 0.25s ease;
}
.toast-box.show {
opacity: 1;
transform: translateX(-50%) translateY(4px);
}
.toast-success { background: #059669; }
.toast-error { background: #dc2626; }
.toast-warning { background: #d97706; }
</style>
</head>
<body>
<div class="login-bg"></div>
<div id="toast" class="toast-box"></div>
<div class="login-card">
<div class="login-header">
<div class="login-logo">
<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
</div>
<h1>中央储备粮遵义直属库有限公司</h1>
<p>智慧仓储管理系统</p>
</div>
<form id="login-form" autocomplete="off" onsubmit="return false;">
{:token()}
<div class="form-item">
<label class="form-label" for="login-username">账号</label>
<div class="input-wrap">
<svg class="input-icon" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
<input type="text" id="login-username" name="username" class="form-input" placeholder="请输入账号" autofocus required>
</div>
</div>
<div class="form-item">
<label class="form-label" for="login-password">密码</label>
<div class="input-wrap">
<svg class="input-icon" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
<input type="password" id="login-password" name="password" class="form-input" placeholder="请输入密码" required>
<div class="password-toggle" id="btn-toggle-pwd" title="切换密码可见性">
<svg id="eye-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</div>
</div>
</div>
{if $Think.config.fastadmin.login_captcha}
<div class="form-item">
<label class="form-label" for="login-captcha">验证码</label>
<div class="captcha-row">
<div class="input-wrap">
<svg class="input-icon" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
<input type="text" id="login-captcha" name="captcha" class="form-input" placeholder="请输入验证码" maxlength="6" required>
</div>
<div class="captcha-img" title="点击刷新验证码">
<img id="verify-img" src="{:rtrim('__PUBLIC__', '/')}/captcha" alt="验证码" onclick="refreshCaptcha()" />
</div>
</div>
</div>
{/if}
<div class="form-options">
<label class="remember-label" for="login-keeplogin">
<input type="checkbox" id="login-keeplogin" name="keeplogin" value="1" checked>
<span>保持登录</span>
</label>
</div>
<button type="submit" class="login-btn" id="btn-submit">
<span class="btn-spinner" id="btn-spinner"></span>
<span id="btn-text">登 录</span>
</button>
</form>
</div>
<script>
function refreshCaptcha() {
var img = document.getElementById('verify-img');
if (img) {
img.src = "{:rtrim('__PUBLIC__', '/')}/captcha?r=" + Math.random();
}
}
(function () {
var form = document.getElementById('login-form');
var btnSubmit = document.getElementById('btn-submit');
var btnText = document.getElementById('btn-text');
var btnSpinner = document.getElementById('btn-spinner');
var usernameInput = document.getElementById('login-username');
var passwordInput = document.getElementById('login-password');
var captchaInput = document.getElementById('login-captcha');
var keeploginInput = document.getElementById('login-keeplogin');
var btnTogglePwd = document.getElementById('btn-toggle-pwd');
var eyeIcon = document.getElementById('eye-icon');
var toastEl = document.getElementById('toast');
var toastTimer = null;
// 密码显隐切换
if (btnTogglePwd && passwordInput && eyeIcon) {
btnTogglePwd.addEventListener('click', function () {
var isPassword = passwordInput.getAttribute('type') === 'password';
passwordInput.setAttribute('type', isPassword ? 'text' : 'password');
eyeIcon.innerHTML = isPassword
? '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>'
: '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>';
});
}
// 轻量 Toast 提示
function showToast(msg, type) {
if (!toastEl) return;
clearTimeout(toastTimer);
toastEl.className = 'toast-box toast-' + (type || 'info');
toastEl.textContent = msg;
toastEl.classList.add('show');
toastTimer = setTimeout(function () {
toastEl.classList.remove('show');
}, 2600);
}
// 设置 Loading 状态
function setLoading(loading) {
if (loading) {
btnSubmit.disabled = true;
btnSpinner.style.display = 'inline-block';
btnText.textContent = '登录中...';
} else {
btnSubmit.disabled = false;
btnSpinner.style.display = 'none';
btnText.textContent = '登 录';
}
}
// 提交处理
function handleSubmit() {
var username = usernameInput ? usernameInput.value.trim() : '';
var password = passwordInput ? passwordInput.value : '';
var captcha = captchaInput ? captchaInput.value.trim() : '';
var keeplogin = (keeploginInput && keeploginInput.checked) ? 1 : 0;
var tokenInput = document.querySelector('input[name="__token__"]');
var token = tokenInput ? tokenInput.value : '';
if (!username) {
showToast('请输入账号', 'warning');
if (usernameInput) usernameInput.focus();
return;
}
if (!password) {
showToast('请输入密码', 'warning');
if (passwordInput) passwordInput.focus();
return;
}
if (captchaInput && !captcha) {
showToast('请输入验证码', 'warning');
captchaInput.focus();
return;
}
setLoading(true);
var postData = new URLSearchParams();
postData.append('username', username);
postData.append('password', password);
postData.append('keeplogin', keeplogin);
postData.append('__token__', token);
if (captchaInput) {
postData.append('captcha', captcha);
}
fetch('{:url("login")}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest'
},
body: postData.toString()
})
.then(function (res) { return res.json(); })
.then(function (res) {
setLoading(false);
if (res.data && res.data.token && tokenInput) {
tokenInput.value = res.data.token;
}
if (res.code === 1) {
showToast(res.msg || '登录成功', 'success');
setTimeout(function () {
window.location.href = res.url || 'index/index';
}, 600);
} else {
refreshCaptcha();
showToast(res.msg || '用户名或密码错误', 'error');
}
})
.catch(function () {
setLoading(false);
refreshCaptcha();
showToast('网络异常,请重试', 'error');
});
}
btnSubmit.addEventListener('click', handleSubmit);
[usernameInput, passwordInput, captchaInput].forEach(function (inp) {
if (inp) {
inp.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.keyCode === 13) {
handleSubmit();
}
});
}
});
})();
</script>
</body>
</html>