feat(auth): add password reset flow

Add admin-generated reset links, reset UI, timezone-aware expiry display, and registration captcha coverage.
This commit is contained in:
2026-07-06 17:55:10 +08:00
parent 2fdbaa8876
commit c7ee2adb82
14 changed files with 1307 additions and 47 deletions
+20 -3
View File
@@ -150,7 +150,7 @@
<span class="material-symbols-outlined text-[28px]">water_drop</span>
<span>供水管道健康评估系统</span>
</div>
<h2 class="text-center text-[44px] lg:text-[40px] font-extrabold tracking-tight mb-10">系统门户</h2>
<h2 class="text-center text-[44px] lg:text-[40px] font-extrabold tracking-tight {{ 'mb-7' if mode == 'register' else 'mb-10' }}">系统门户</h2>
<div class="flex items-center gap-8 text-[13px] font-semibold border-b border-slate-200 mb-7">
<a href="{{ url_for('main.login') }}" class="border-b-2 py-3 {{ 'text-primary border-primary' if mode == 'login' else 'text-slate-500 border-transparent' }}">登录</a>
@@ -171,7 +171,7 @@
<div>
<div class="flex items-center justify-between mb-2">
<label class="block text-[11px] tracking-[0.18em] uppercase text-slate-500">密码</label>
<span class="text-[12px] text-primary font-semibold">找回密码</span>
<button id="forgotPasswordBtn" type="button" class="text-[12px] font-semibold text-primary transition hover:text-primaryDeep">找回密码</button>
</div>
<div class="relative">
<span class="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-slate-400 text-lg">lock</span>
@@ -207,7 +207,7 @@
</button>
</form>
{% else %}
<form method="post" action="{{ url_for('main.register') }}" class="space-y-5" novalidate data-auth-form>
<form method="post" action="{{ url_for('main.register') }}" class="space-y-4" novalidate data-auth-form>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div>
<label class="block text-[11px] tracking-[0.18em] uppercase text-slate-500 mb-2">用户名</label>
@@ -226,6 +226,19 @@
</button>
</div>
</div>
<div>
<label class="block text-[11px] tracking-[0.18em] uppercase text-slate-500 mb-2">验证码</label>
<div class="grid grid-cols-[1fr_92px_44px] gap-3 items-center">
<div class="relative">
<span class="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-slate-400 text-lg">verified_user</span>
<input name="captcha" required data-field-label="验证码" {{ 'disabled' if not allow_registration }} class="w-full pl-11 pr-4 py-3.5 rounded-xl bg-[#eceff3] border border-transparent focus:border-primary focus:ring-0 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" placeholder="请输入验证码" />
</div>
<div class="rounded-xl bg-blueSoft text-textMain border border-blue-100 h-[50px] flex items-center justify-center font-black tracking-[0.18em] italic">{{ captcha }}</div>
<a href="{{ url_for('main.register') }}" class="ui-btn ui-btn-field ui-btn-secondary px-0 {{ 'pointer-events-none opacity-50' if not allow_registration }}" aria-label="刷新验证码" aria-disabled="{{ 'true' if not allow_registration else 'false' }}">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</div>
<button {{ 'disabled' if not allow_registration }} class="ui-btn ui-btn-lg ui-btn-primary w-full mt-2">
注册
<span class="material-symbols-outlined text-lg">person_add</span>
@@ -410,6 +423,10 @@
toggle.setAttribute('aria-pressed', shouldShow ? 'true' : 'false');
});
});
document.getElementById('forgotPasswordBtn')?.addEventListener('click', () => {
showAppNotification('请联系管理员获取一次性重置链接后设置新密码。', 'info', '密码重置');
});
})();
</script>
</body>