317 lines
14 KiB
HTML
317 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>个人中心</title>
|
||
<style>
|
||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: #f5f6fa; margin: 0; }
|
||
/* 侧边栏样式 */
|
||
.sidebar { position: fixed; top: 0; left: 0; width: 180px; height: 100vh; background: #1e1e2e; color: white; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); z-index: 1000; display: flex; flex-direction: column; align-items: center; }
|
||
.user-id-sidebar { text-align: center; margin-bottom: 0px; }
|
||
.sidebar h3 { margin-top: 0; font-size: 18px; color: #add8e6; text-align: center; margin-bottom: 20px; }
|
||
.navigation-links { width: 100%; margin-top: 60px; }
|
||
.sidebar a { display: block; color: #8be9fd; text-decoration: none; margin: 10px 0; font-size: 16px; padding: 15px; border-radius: 4px; transition: all 0.2s ease; }
|
||
.sidebar a:hover { color: #ff79c6; background-color: rgba(139, 233, 253, 0.2); }
|
||
|
||
/* 主内容区 */
|
||
.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: 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: 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); }
|
||
.image-item img { width: 100%; height: 150px; object-fit: cover; cursor: pointer; }
|
||
.image-item .info { padding: 10px; font-size: 12px; color: #888; text-align: center; }
|
||
|
||
.no-data { text-align: center; color: #999; padding: 40px; }
|
||
.form-group { margin-bottom: 14px; }
|
||
.form-group label { display:block; margin-bottom: 6px; font-weight: 600; color: #333; }
|
||
.form-group input { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; box-sizing: border-box; }
|
||
.btn { padding: 10px 14px; border: none; border-radius: 10px; cursor: pointer; background: #4f46e5; color: #fff; }
|
||
.msg { margin-top: 10px; font-size: 13px; }
|
||
.msg.error { color: #b91c1c; }
|
||
.msg.success { color: #166534; }
|
||
|
||
/* 图片放大模态框 */
|
||
.image-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 2000; }
|
||
.image-modal-content { max-width: 90%; max-height: 90%; border-radius: 8px; }
|
||
.image-modal-close { position: absolute; top: 20px; right: 30px; color: white; font-size: 40px; font-weight: bold; cursor: pointer; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 侧边栏 -->
|
||
<div class="sidebar">
|
||
<div class="user-id-sidebar">
|
||
<h3>你好,{{ username|default:"访客" }}</h3>
|
||
</div>
|
||
<div class="navigation-links">
|
||
<a href="{% url 'main:home' %}">返回主页</a>
|
||
<a id="logoutBtn" style="cursor:pointer;">退出登录</a>
|
||
{% csrf_token %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="main-content">
|
||
<div class="profile-card">
|
||
<div class="profile-header">
|
||
<div class="profile-info">
|
||
<h2>个人信息</h2>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
|
||
<div class="section-title">我的提交</div>
|
||
{% if achievements %}
|
||
<div class="image-grid">
|
||
{% for item in achievements %}
|
||
<div class="image-item">
|
||
{% if item.image_url %}
|
||
<img src="{{ item.image_url }}" alt="提交的图片" onclick="openModal(this.src)">
|
||
{% else %}
|
||
<div style="height: 150px; background: #eee; display: flex; align-items: center; justify-content: center; color: #ccc;">无图片</div>
|
||
{% endif %}
|
||
<div style="padding: 8px; text-align: center;">
|
||
<a href="{% url 'elastic:manage_page' %}?id={{ item.id }}" style="display: inline-block; padding: 4px 12px; background: #eef2ff; color: #4f46e5; text-decoration: none; border-radius: 4px; font-size: 12px; transition: background 0.2s;">管理此条</a>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="profile-card no-data">
|
||
<p>你还没有提交过任何图片。</p>
|
||
<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>
|
||
|
||
<!-- 图片放大模态框 -->
|
||
<div id="imageModal" class="image-modal">
|
||
<span class="image-modal-close" onclick="closeModal()">×</span>
|
||
<img id="modalImg" class="image-modal-content">
|
||
</div>
|
||
|
||
<script>
|
||
function getCookie(name){const v=`; ${document.cookie}`;const p=v.split(`; ${name}=`);if(p.length===2) return p.pop().split(';').shift();}
|
||
|
||
// 登出功能
|
||
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
||
if(!confirm('确定要退出登录吗?')) return;
|
||
const csrftoken = getCookie('csrftoken');
|
||
try {
|
||
const resp = await fetch('/accounts/logout/', {
|
||
method: 'POST',
|
||
headers: { 'X-CSRFToken': csrftoken || '' }
|
||
});
|
||
const data = await resp.json();
|
||
if (data.ok) window.location.href = data.redirect_url;
|
||
} catch (e) { alert('登出失败'); }
|
||
});
|
||
|
||
// 图片放大功能
|
||
function openModal(src) {
|
||
const modal = document.getElementById('imageModal');
|
||
const modalImg = document.getElementById('modalImg');
|
||
modal.style.display = "flex";
|
||
modalImg.src = src;
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('imageModal').style.display = "none";
|
||
}
|
||
|
||
window.onclick = function(event) {
|
||
const modal = document.getElementById('imageModal');
|
||
if (event.target == modal) closeModal();
|
||
}
|
||
|
||
const pwdForm = document.getElementById('pwdForm');
|
||
if (pwdForm) {
|
||
pwdForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
const msg = document.getElementById('pwdMsg');
|
||
msg.textContent = '';
|
||
msg.className = 'msg';
|
||
|
||
const pwd = (document.getElementById('newPassword').value || '').trim();
|
||
const cpwd = (document.getElementById('confirmPassword').value || '').trim();
|
||
if (pwd !== cpwd) {
|
||
msg.textContent = '密码和确认密码不匹配';
|
||
msg.className = 'msg error';
|
||
return;
|
||
}
|
||
if (pwd.length < 6) {
|
||
msg.textContent = '密码长度至少为6位';
|
||
msg.className = 'msg error';
|
||
return;
|
||
}
|
||
try {
|
||
const csrftoken = getCookie('csrftoken');
|
||
const resp = await fetch(`/elastic/users/{{ profile_user.user_id }}/update/`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrftoken || ''
|
||
},
|
||
body: JSON.stringify({ password: pwd })
|
||
});
|
||
const data = await resp.json();
|
||
if (resp.ok && data.status === 'success') {
|
||
msg.textContent = '修改成功';
|
||
msg.className = 'msg success';
|
||
document.getElementById('newPassword').value = '';
|
||
document.getElementById('confirmPassword').value = '';
|
||
} else {
|
||
msg.textContent = data.message || '操作失败';
|
||
msg.className = 'msg error';
|
||
}
|
||
} catch (err) {
|
||
msg.textContent = '操作失败';
|
||
msg.className = 'msg error';
|
||
}
|
||
});
|
||
}
|
||
|
||
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>
|