This commit is contained in:
@@ -15,13 +15,14 @@
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content { margin-left: 220px; padding: 40px; }
|
||||
.profile-card { background: #fff; border-radius: 14px; box-shadow: 0 10px 24px rgba(31,35,40,0.08); padding: 30px; margin-bottom: 30px; }
|
||||
.profile-card { background: #fff; border-radius: 14px; box-shadow: 0 10px 24px rgba(31,35,40,0.08); padding: 30px; margin-bottom: 40px; }
|
||||
.rc-card { margin-top: 18px; }
|
||||
.profile-header { display: flex; align-items: center; margin-bottom: 20px; border-bottom: 1px solid #eee; padding-bottom: 20px; }
|
||||
.profile-info h2 { margin: 0; color: #1e1e2e; }
|
||||
.profile-info p { margin: 5px 0; color: #666; }
|
||||
.label { font-weight: bold; color: #333; margin-right: 10px; }
|
||||
|
||||
.section-title { font-size: 20px; font-weight: bold; margin-bottom: 20px; color: #1e1e2e; }
|
||||
.section-title { font-size: 20px; font-weight: bold; margin: 34px 0 24px; color: #1e1e2e; }
|
||||
.image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
|
||||
.image-item { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.05); transition: transform 0.2s; }
|
||||
.image-item:hover { transform: translateY(-5px); }
|
||||
@@ -66,34 +67,13 @@
|
||||
<div class="profile-details">
|
||||
<p><span class="label">用户名:</span> {{ profile_user.username }}</p>
|
||||
<p><span class="label">用户ID:</span> {{ profile_user.user_id }}</p>
|
||||
<p><span class="label">注册码:</span> {{ profile_user.registration_code|default:"无" }}</p>
|
||||
<p><span class="label">所属:</span> {{ profile_user.key|join:"、"|default:"未填写" }}</p>
|
||||
<p><span class="label">可管理级别:</span> {{ profile_user.manage_key|join:"、"|default:"无" }}</p>
|
||||
<p><span class="label">权限级别:</span> {{ permission_name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if permission_name != "管理员" and not profile_user.manage_key %}
|
||||
<div class="profile-card">
|
||||
<div class="profile-header">
|
||||
<div class="profile-info">
|
||||
<h2>修改密码</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="pwdForm">
|
||||
<div class="form-group">
|
||||
<label for="newPassword">新密码</label>
|
||||
<input type="password" id="newPassword" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">确认密码</label>
|
||||
<input type="password" id="confirmPassword" autocomplete="new-password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">保存</button>
|
||||
<div id="pwdMsg" class="msg"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="section-title">我的提交</div>
|
||||
{% if achievements %}
|
||||
<div class="image-grid">
|
||||
@@ -116,6 +96,50 @@
|
||||
<a href="{% url 'elastic:upload_page' %}" style="color: #2d8cf0; text-decoration: none;">去上传第一张图片吧!</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="profile-card rc-card">
|
||||
<div class="profile-header">
|
||||
<div class="profile-info">
|
||||
<h2>替换注册码</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="rcForm">
|
||||
<div class="form-group">
|
||||
<label for="newRegCode">新注册码</label>
|
||||
<input type="text" id="newRegCode" placeholder="输入新注册码后替换原有 key" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>预览</label>
|
||||
<div id="rcPreview" style="background:#f8fafc; border:1px solid #e5e7eb; border-radius:10px; padding:10px 12px; font-size:13px; color:#334155;">
|
||||
<div style="color:#64748b;">输入注册码后自动显示 key 预览</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn">替换</button>
|
||||
<div id="rcMsg" class="msg"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if permission_name != "管理员" and not profile_user.manage_key %}
|
||||
<div class="profile-card">
|
||||
<div class="profile-header">
|
||||
<div class="profile-info">
|
||||
<h2>修改密码</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form id="pwdForm">
|
||||
<div class="form-group">
|
||||
<label for="newPassword">新密码</label>
|
||||
<input type="password" id="newPassword" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">确认密码</label>
|
||||
<input type="password" id="confirmPassword" autocomplete="new-password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">保存</button>
|
||||
<div id="pwdMsg" class="msg"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 图片放大模态框 -->
|
||||
@@ -205,6 +229,88 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const rcForm = document.getElementById('rcForm');
|
||||
if (rcForm) {
|
||||
let rcPreviewTimer = null;
|
||||
let rcPreviewSeq = 0;
|
||||
const rcInput = document.getElementById('newRegCode');
|
||||
const rcPreview = document.getElementById('rcPreview');
|
||||
|
||||
async function refreshRcPreview(code) {
|
||||
const seq = ++rcPreviewSeq;
|
||||
if (!code) {
|
||||
rcPreview.innerHTML = '<div style="color:#64748b;">输入注册码后自动显示 key 预览</div>';
|
||||
return;
|
||||
}
|
||||
rcPreview.innerHTML = '<div style="color:#64748b;">正在查询...</div>';
|
||||
try {
|
||||
const resp = await fetch(`/accounts/profile/registration-code/preview/?code=${encodeURIComponent(code)}`, { method: 'GET', credentials: 'same-origin' });
|
||||
const data = await resp.json();
|
||||
if (seq !== rcPreviewSeq) return;
|
||||
if (!(resp.ok && data && data.ok)) {
|
||||
const msg = (data && data.message) ? data.message : '查询失败';
|
||||
rcPreview.innerHTML = `<div style="color:#b91c1c;">${msg}</div>`;
|
||||
return;
|
||||
}
|
||||
const keys = ((data.data || {}).keys || []).map(String).filter(Boolean);
|
||||
const manageKeys = ((data.data || {}).manage_keys || []).map(String).filter(Boolean);
|
||||
const keysText = keys.length ? keys.join('、') : '无';
|
||||
const manageText = manageKeys.length ? manageKeys.join('、') : '无';
|
||||
rcPreview.innerHTML = `<div><span style="font-weight:700;">key:</span>${keysText}</div><div style="margin-top:6px;"><span style="font-weight:700;">manage_key:</span>${manageText}</div>`;
|
||||
} catch (e) {
|
||||
if (seq !== rcPreviewSeq) return;
|
||||
rcPreview.innerHTML = '<div style="color:#b91c1c;">查询失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (rcInput) {
|
||||
rcInput.addEventListener('input', () => {
|
||||
const code = (rcInput.value || '').trim();
|
||||
if (rcPreviewTimer) window.clearTimeout(rcPreviewTimer);
|
||||
rcPreviewTimer = window.setTimeout(() => refreshRcPreview(code), 300);
|
||||
});
|
||||
refreshRcPreview((rcInput.value || '').trim());
|
||||
}
|
||||
|
||||
rcForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const msg = document.getElementById('rcMsg');
|
||||
msg.textContent = '';
|
||||
msg.className = 'msg';
|
||||
const code = (document.getElementById('newRegCode').value || '').trim();
|
||||
if (!code) {
|
||||
msg.textContent = '请输入注册码';
|
||||
msg.className = 'msg error';
|
||||
return;
|
||||
}
|
||||
if (!confirm('确定要替换注册码吗?该操作会替换你当前的 key。')) return;
|
||||
try {
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
const resp = await fetch('/accounts/profile/registration-code/replace/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
},
|
||||
body: JSON.stringify({ code })
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (resp.ok && data.ok) {
|
||||
msg.textContent = '替换成功';
|
||||
msg.className = 'msg success';
|
||||
window.location.reload();
|
||||
} else {
|
||||
msg.textContent = (data && data.message) ? data.message : '替换失败';
|
||||
msg.className = 'msg error';
|
||||
}
|
||||
} catch (err) {
|
||||
msg.textContent = '替换失败';
|
||||
msg.className = 'msg error';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,4 +13,10 @@ urlpatterns = [
|
||||
path("register/submit/", views.register_submit, name="register_submit"),
|
||||
path("email/send-code/", views.send_email_code, name="send_email_code"),
|
||||
path("profile/", views.profile_page, name="profile"),
|
||||
]
|
||||
path("profile/registration-code/replace/", views.replace_registration_code_view, name="replace_registration_code"),
|
||||
path("profile/registration-code/preview/", views.registration_code_preview_view, name="registration_code_preview"),
|
||||
path("registration-code/request/submit/", views.submit_registration_code_request_view, name="submit_registration_code_request"),
|
||||
path("registration-code/requests/", views.registration_code_requests_page, name="registration_code_requests_page"),
|
||||
path("registration-code/requests/list/", views.list_registration_code_requests_view, name="list_registration_code_requests"),
|
||||
path("registration-code/requests/decide/", views.decide_registration_code_request_view, name="decide_registration_code_request"),
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ from django.conf import settings
|
||||
|
||||
from .es_client import get_user_by_username
|
||||
from .crypto import get_public_key_spki_b64, rsa_oaep_decrypt_b64, aes_gcm_decrypt_b64, verify_password, generate_rsa_private_pem_b64, public_spki_b64_from_private_pem_b64, rsa_oaep_decrypt_b64_with_private_pem
|
||||
from elastic.es_connect import get_registration_code, get_user_by_username as es_get_user_by_username, get_all_users as es_get_all_users, write_user_data, update_user_by_id, get_user_by_id
|
||||
from elastic.es_connect import get_registration_code, get_user_by_username as es_get_user_by_username, get_all_users as es_get_all_users, write_user_data, update_user_by_id, get_user_by_id, create_registration_code_manage_request, find_pending_registration_code_manage_request, list_registration_code_manage_requests, decide_registration_code_manage_request, get_registration_code_manage_request
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -259,6 +259,7 @@ def register_submit(request):
|
||||
"email": email,
|
||||
"key": (rc.get("keys") if rc else []) or [],
|
||||
"manage_key": (rc.get("manage_keys") if rc else []) or [],
|
||||
"registration_code": (rc.get("code") if rc else None),
|
||||
})
|
||||
if not ok:
|
||||
return JsonResponse({"ok": False, "message": "注册失败"}, status=500)
|
||||
@@ -269,6 +270,169 @@ def register_submit(request):
|
||||
pass
|
||||
return JsonResponse({"ok": True, "redirect_url": "/accounts/login/"})
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def replace_registration_code_view(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
code = (payload.get("code") or "").strip()
|
||||
if not code:
|
||||
return JsonResponse({"ok": False, "message": "请输入注册码"}, status=400)
|
||||
rc = get_registration_code(code)
|
||||
if not rc:
|
||||
return JsonResponse({"ok": False, "message": "注册码无效"}, status=400)
|
||||
try:
|
||||
exp = rc.get("expires_at")
|
||||
now = __import__("datetime").datetime.now(__import__("datetime").timezone.utc)
|
||||
if hasattr(exp, 'isoformat'):
|
||||
exp_dt = exp
|
||||
else:
|
||||
exp_dt = __import__("datetime").datetime.fromisoformat(str(exp))
|
||||
if exp_dt <= now:
|
||||
return JsonResponse({"ok": False, "message": "注册码已过期"}, status=400)
|
||||
except Exception:
|
||||
pass
|
||||
keys = list(rc.get("keys") or [])
|
||||
manage_keys = list(rc.get("manage_keys") or [])
|
||||
ok = update_user_by_id(session_user_id, key=keys, manage_key=manage_keys, registration_code=code)
|
||||
if not ok:
|
||||
return JsonResponse({"ok": False, "message": "替换失败"}, status=500)
|
||||
return JsonResponse({"ok": True})
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def registration_code_preview_view(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
|
||||
code = (request.GET.get("code") or "").strip()
|
||||
if not code:
|
||||
return JsonResponse({"ok": False, "message": "请输入注册码"}, status=400)
|
||||
rc = get_registration_code(code)
|
||||
if not rc:
|
||||
return JsonResponse({"ok": False, "message": "注册码无效"}, status=400)
|
||||
try:
|
||||
exp = rc.get("expires_at")
|
||||
now = __import__("datetime").datetime.now(__import__("datetime").timezone.utc)
|
||||
if hasattr(exp, 'isoformat'):
|
||||
exp_dt = exp
|
||||
else:
|
||||
exp_dt = __import__("datetime").datetime.fromisoformat(str(exp))
|
||||
if exp_dt <= now:
|
||||
return JsonResponse({"ok": False, "message": "注册码已过期"}, status=400)
|
||||
except Exception:
|
||||
pass
|
||||
return JsonResponse(
|
||||
{
|
||||
"ok": True,
|
||||
"data": {
|
||||
"code": rc.get("code"),
|
||||
"keys": list(rc.get("keys") or []),
|
||||
"manage_keys": list(rc.get("manage_keys") or []),
|
||||
"expires_at": rc.get("expires_at"),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def submit_registration_code_request_view(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
|
||||
try:
|
||||
perm = int(request.session.get("permission", 1))
|
||||
except Exception:
|
||||
perm = 1
|
||||
if perm == 0:
|
||||
return JsonResponse({"ok": False, "message": "无权限"}, status=403)
|
||||
me = get_user_by_id(session_user_id) or {}
|
||||
if (me.get("manage_key") or []) or int(me.get("can_manage_registration_codes") or 0) == 1:
|
||||
return JsonResponse({"ok": False, "message": "无需申请"}, status=400)
|
||||
if str(me.get("registration_code") or "").strip():
|
||||
return JsonResponse({"ok": False, "message": "已有注册码,无法申请"}, status=400)
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
reason = (payload.get("reason") or "").strip()
|
||||
if not reason:
|
||||
return JsonResponse({"ok": False, "message": "请填写申请理由"}, status=400)
|
||||
pending = find_pending_registration_code_manage_request(session_user_id)
|
||||
if pending:
|
||||
return JsonResponse({"ok": True, "message": "已提交申请"})
|
||||
rid = create_registration_code_manage_request(session_user_id, me.get("username"), reason)
|
||||
if not rid:
|
||||
return JsonResponse({"ok": False, "message": "提交失败"}, status=500)
|
||||
return JsonResponse({"ok": True})
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_csrf_cookie
|
||||
def registration_code_requests_page(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
return redirect("/accounts/login/")
|
||||
try:
|
||||
perm = int(request.session.get("permission", 1))
|
||||
except Exception:
|
||||
perm = 1
|
||||
if perm != 0:
|
||||
return redirect("/main/home/")
|
||||
me = get_user_by_id(session_user_id) or {}
|
||||
return render(request, "accounts/registration_code_requests.html", {"username": me.get("username")})
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def list_registration_code_requests_view(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
|
||||
try:
|
||||
perm = int(request.session.get("permission", 1))
|
||||
except Exception:
|
||||
perm = 1
|
||||
if perm != 0:
|
||||
return JsonResponse({"ok": False, "message": "无权限"}, status=403)
|
||||
status = (request.GET.get("status") or "").strip() or None
|
||||
data = list_registration_code_manage_requests(status=status)
|
||||
return JsonResponse({"ok": True, "data": data})
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def decide_registration_code_request_view(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
|
||||
try:
|
||||
perm = int(request.session.get("permission", 1))
|
||||
except Exception:
|
||||
perm = 1
|
||||
if perm != 0:
|
||||
return JsonResponse({"ok": False, "message": "无权限"}, status=403)
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
request_id = (payload.get("request_id") or "").strip()
|
||||
action = (payload.get("action") or "").strip().lower()
|
||||
note = (payload.get("note") or "").strip()
|
||||
if not request_id or action not in ("approve", "reject"):
|
||||
return JsonResponse({"ok": False, "message": "参数错误"}, status=400)
|
||||
req = get_registration_code_manage_request(request_id)
|
||||
if not req:
|
||||
return JsonResponse({"ok": False, "message": "申请不存在"}, status=404)
|
||||
status = "approved" if action == "approve" else "rejected"
|
||||
ok = decide_registration_code_manage_request(request_id, status=status, reviewed_by=session_user_id, reviewer_note=note)
|
||||
if not ok:
|
||||
return JsonResponse({"ok": False, "message": "操作失败"}, status=500)
|
||||
if status == "approved":
|
||||
uid = req.get("user_id")
|
||||
update_user_by_id(uid, can_manage_registration_codes=1, registration_manage_keys=[])
|
||||
return JsonResponse({"ok": True})
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def send_email_code(request):
|
||||
@@ -327,4 +491,4 @@ def _send_smtp_email(to_email: str, code: str):
|
||||
pass
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
return False, str(e)
|
||||
|
||||
Reference in New Issue
Block a user