Files
school-meal/backend/templates/registration/password_change_form.html
T
Chunyu d7553520ce feat: 现代化翻页组件 + 完整项目更新
- 新增 Pagination 通用组件:首页/上一页/页码/下一页/末页导航
- PickModal 和 SwapModal 集成分页功能,每页20项
- 搜索/筛选切换自动回到第1页
- 现代化CSS样式:hover浮起动效、主色高亮、移动端适配
- 新增 dashboard、admin 管理模板
- 菜系(cuisine)模型和迁移
- 构建脚本 build.py 支持 PyInstaller 打包
- 前端资源重新构建
2026-08-28 13:44:21 +08:00

297 lines
9.7 KiB
HTML

{% extends "admin/base.html" %}
{% load i18n static jazzmin %}
{% get_jazzmin_settings request as jazzmin_settings %}
{% get_jazzmin_ui_tweaks as jazzmin_ui %}
{% block title %}{% trans "Change password" %} | {{ jazzmin_settings.site_title }}{% endblock %}
{% block bodyclass %}password-change-page{% endblock %}
{% block extrastyle %}
<style>
.password-change-page .app-content {
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 32px;
padding-bottom: 48px;
}
.password-card {
width: 100%;
max-width: 480px;
background: var(--bs-card-bg, #fff);
border: 1px solid var(--bs-border-color, #dee2e6);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,.06), 0 6px 16px rgba(0,0,0,.04);
overflow: hidden;
}
.password-card-header {
display: flex;
align-items: center;
gap: 14px;
padding: 24px 28px 0;
}
.password-card-header .header-icon {
width: 44px;
height: 44px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #fff;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
flex-shrink: 0;
}
.password-card-header h3 {
font-size: 18px;
font-weight: 600;
margin: 0;
color: var(--bs-body-color, #212529);
}
.password-card-header .subtitle {
font-size: 13px;
color: var(--bs-secondary-color, #6c757d);
margin-top: 2px;
}
.password-card-body {
padding: 20px 28px 28px;
}
.password-card-body .form-label {
font-weight: 500;
font-size: 14px;
margin-bottom: 6px;
color: var(--bs-body-color, #212529);
}
.password-card-body .form-control {
border-radius: 6px;
padding: 10px 12px;
font-size: 14px;
transition: border-color .15s ease, box-shadow .15s ease;
}
.password-card-body .form-control:focus {
border-color: #818cf8;
box-shadow: 0 0 0 3px rgba(129,140,248,.2);
}
.input-group .form-control {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group .btn-toggle-pw {
border: 1px solid var(--bs-border-color, #dee2e6);
border-left: none;
border-radius: 0 6px 6px 0;
background: var(--bs-tertiary-bg, #f8f9fa);
color: var(--bs-secondary-color, #6c757d);
padding: 0 14px;
cursor: pointer;
transition: background .15s, color .15s;
display: flex;
align-items: center;
}
.input-group .btn-toggle-pw:hover {
background: #e9ecef;
color: var(--bs-body-color, #212529);
}
.pw-strength {
height: 4px;
border-radius: 2px;
background: #e9ecef;
margin-top: 8px;
overflow: hidden;
}
.pw-strength-bar {
height: 100%;
width: 0;
border-radius: 2px;
transition: width .25s ease, background .25s ease;
}
.pw-strength-text {
font-size: 12px;
margin-top: 4px;
color: var(--bs-secondary-color, #6c757d);
}
.errorlist {
list-style: none;
padding: 0;
margin: 4px 0 0;
}
.errorlist li {
font-size: 12px;
color: var(--bs-danger, #dc3545);
display: flex;
align-items: center;
gap: 4px;
}
.non-field-errors {
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 6px;
padding: 12px 14px;
margin-bottom: 20px;
color: var(--bs-danger, #dc3545);
font-size: 13px;
}
.btn-submit {
width: 100%;
padding: 10px;
font-size: 15px;
font-weight: 500;
border-radius: 6px;
border: none;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: #fff;
cursor: pointer;
transition: opacity .15s, transform .1s;
margin-top: 8px;
}
.btn-submit:hover { opacity: .9; }
.btn-submit:active { transform: scale(.98); }
.form-hint-row {
display: flex;
flex-wrap: wrap;
gap: 6px 16px;
margin-top: 4px;
}
.form-hint-row span {
font-size: 12px;
color: var(--bs-secondary-color, #6c757d);
display: flex;
align-items: center;
gap: 4px;
}
.form-hint-row .check { color: var(--bs-success, #198754); }
</style>
{% endblock %}
{% block page_content %}
<div class="app-content">
<div class="password-card">
<div class="password-card-header">
<div class="header-icon"><i class="fas fa-shield-halved"></i></div>
<div>
<h3>{% trans "Change password" %}</h3>
<div class="subtitle">{{ request.user }}</div>
</div>
</div>
<div class="password-card-body">
{% if form.errors %}
{% if form.non_field_errors %}
<div class="non-field-errors">
{% for error in form.non_field_errors %}{{ error }}{% endfor %}
</div>
{% endif %}
{% endif %}
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="id_old_password" class="form-label">{% trans "Old password" %}</label>
<div class="input-group">
<input type="password" name="old_password" id="id_old_password" class="form-control" autofocus required autocomplete="current-password">
<button type="button" class="btn-toggle-pw" onclick="togglePw(this)" aria-label="Toggle">
<i class="fas fa-eye"></i>
</button>
</div>
{% if form.old_password.errors %}<ul class="errorlist">{% for e in form.old_password.errors %}<li>{{ e }}</li>{% endfor %}</ul>{% endif %}
</div>
<div class="mb-3">
<label for="id_new_password1" class="form-label">{% trans "New password" %}</label>
<div class="input-group">
<input type="password" name="new_password1" id="id_new_password1" class="form-control" required autocomplete="new-password" oninput="checkStrength(this.value)">
<button type="button" class="btn-toggle-pw" onclick="togglePw(this)" aria-label="Toggle">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="pw-strength"><div class="pw-strength-bar" id="pwBar"></div></div>
<div class="pw-strength-text" id="pwText"></div>
<div class="form-hint-row" id="pwHints">
<span id="hintLen"><i class="fas fa-circle" style="font-size:6px"></i> &#33267;&#23567;8&#20301;</span>
<span id="hintUpper"><i class="fas fa-circle" style="font-size:6px"></i> &#21547;&#22823;&#20889;&#23383;&#27597;</span>
<span id="hintLower"><i class="fas fa-circle" style="font-size:6px"></i> &#21547;&#23567;&#20889;&#23383;&#27597;</span>
<span id="hintDigit"><i class="fas fa-circle" style="font-size:6px"></i> &#21547;&#25968;&#23383;</span>
</div>
{% if form.new_password1.errors %}<ul class="errorlist">{% for e in form.new_password1.errors %}<li>{{ e }}</li>{% endfor %}</ul>{% endif %}
</div>
<div class="mb-3">
<label for="id_new_password2" class="form-label">{% trans "Password confirmation" %}</label>
<div class="input-group">
<input type="password" name="new_password2" id="id_new_password2" class="form-control" required autocomplete="new-password" oninput="checkMatch()">
<button type="button" class="btn-toggle-pw" onclick="togglePw(this)" aria-label="Toggle">
<i class="fas fa-eye"></i>
</button>
</div>
<div id="matchHint" style="font-size:12px;margin-top:4px;color:var(--bs-secondary-color,#6c757d)"></div>
{% if form.new_password2.errors %}<ul class="errorlist">{% for e in form.new_password2.errors %}<li>{{ e }}</li>{% endfor %}</ul>{% endif %}
</div>
<button type="submit" class="btn-submit">{% trans "Change my password" %}</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% block extrajs %}
<script>
function togglePw(btn) {
var input = btn.parentElement.querySelector("input");
var icon = btn.querySelector("i");
if (input.type === "password") {
input.type = "text";
icon.classList.replace("fa-eye", "fa-eye-slash");
} else {
input.type = "password";
icon.classList.replace("fa-eye-slash", "fa-eye");
}
}
function checkStrength(pw) {
var bar = document.getElementById("pwBar");
var text = document.getElementById("pwText");
var score = 0;
if (pw.length >= 8) score++;
if (pw.length >= 12) score++;
if (/[A-Z]/.test(pw)) score++;
if (/[a-z]/.test(pw)) score++;
if (/[0-9]/.test(pw)) score++;
if (/[^A-Za-z0-9]/.test(pw)) score++;
var pct = pw.length === 0 ? 0 : Math.min(score / 6 * 100, 100);
bar.style.width = pct + "%";
var colors = ["#dc3545","#dc3545","#fd7e14","#ffc107","#198754","#198754","#198754"];
var labels = ["","\u5f31","\u4e00\u822c","\u826f\u597d","\u5f3a","\u5f88\u5f3a","\u5f88\u5f3a"];
var idx = Math.min(Math.floor(score), colors.length - 1);
bar.style.background = colors[idx];
text.textContent = pw.length ? labels[idx] : "";
text.style.color = colors[idx];
setHint("hintLen", pw.length >= 8);
setHint("hintUpper", /[A-Z]/.test(pw));
setHint("hintLower", /[a-z]/.test(pw));
setHint("hintDigit", /[0-9]/.test(pw));
checkMatch();
}
function setHint(id, ok) {
var el = document.getElementById(id);
var txt = el.textContent.trim();
if (ok) {
el.innerHTML = '<i class="fas fa-check check"></i> ' + txt;
} else {
el.innerHTML = '<i class="fas fa-circle" style="font-size:6px"></i> ' + txt;
}
}
function checkMatch() {
var hint = document.getElementById("matchHint");
var pw1 = document.getElementById("id_new_password1").value;
var pw2 = document.getElementById("id_new_password2").value;
if (!pw2) { hint.textContent = ""; return; }
if (pw1 === pw2) {
hint.innerHTML = '<span style="color:var(--bs-success,#198754)"><i class="fas fa-check-circle"></i> \u5bc6\u7801\u5339\u914d</span>';
} else {
hint.innerHTML = '<span style="color:var(--bs-danger,#dc3545)"><i class="fas fa-times-circle"></i> \u5bc6\u7801\u4e0d\u4e00\u81f4</span>';
}
}
</script>
{% endblock %}