[0.2.7.5][ci]
All checks were successful
CI / docker-ci (push) Successful in 32s

This commit is contained in:
DSQ
2026-03-18 21:56:39 +08:00
parent 71a0723a74
commit 5e38ebf856

View File

@@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>注册码申请管理</title>
<style>
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: #f5f6fa; }
.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; }
.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; }
.card { background: #fff; border-radius: 14px; box-shadow: 0 10px 24px rgba(31,35,40,0.08); padding: 24px; }
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.btn { padding: 8px 12px; border: none; border-radius: 10px; cursor: pointer; }
.btn-primary { background: #4f46e5; color: #fff; }
.btn-secondary { background: #64748b; color: #fff; }
.btn-danger { background: #ff4d4f; color: #fff; }
.muted { color: #6b7280; font-size: 12px; }
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
th, td { text-align: left; border-bottom: 1px solid #e5e7eb; padding: 10px 8px; vertical-align: top; font-size: 13px; }
tr:hover { background: #f8fafc; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; background: #eef2ff; color: #3730a3; }
.tag.pending { background: #fff7ed; color: #9a3412; }
.tag.approved { background: #dcfce7; color: #166534; }
.tag.rejected { background: #fee2e2; color: #991b1b; }
</style>
{% csrf_token %}
</head>
<body>
<div class="sidebar">
<h3>你好,{{ username|default:"管理员" }}</h3>
<div class="navigation-links">
<a href="{% url 'main:home' %}">返回主页</a>
<a id="logoutBtn" style="cursor:pointer;">退出登录</a>
<div id="logoutMsg" class="muted" style="margin-top:6px;"></div>
{% csrf_token %}
</div>
</div>
<div class="main-content">
<div class="card">
<div class="header">
<h2 style="margin:0;">注册码申请管理</h2>
<div style="display:flex; gap:10px; align-items:center;">
<select id="statusFilter" style="padding:8px 10px; border:1px solid #d1d5db; border-radius:10px;">
<option value="pending">待审核</option>
<option value="">全部</option>
<option value="approved">已同意</option>
<option value="rejected">已拒绝</option>
</select>
<button id="refreshBtn" class="btn btn-secondary" type="button">刷新</button>
</div>
</div>
<div class="muted">同意后,用户会获得“注册码管理”入口,且仅能使用自己新增的 key。</div>
<table>
<thead>
<tr>
<th style="width:120px;">用户</th>
<th>申请理由</th>
<th style="width:170px;">时间</th>
<th style="width:110px;">状态</th>
<th style="width:220px;">操作</th>
</tr>
</thead>
<tbody id="reqBody"></tbody>
</table>
<div id="pageMsg" class="muted" style="margin-top:12px;"></div>
</div>
</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 () => {
const msg = document.getElementById('logoutMsg');
msg.textContent = '';
const csrftoken = getCookie('csrftoken');
try {
const resp = await fetch('/accounts/logout/', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' },
body: JSON.stringify({})
});
const data = await resp.json();
if (data.ok) window.location.href = data.redirect_url;
} catch (e) { msg.textContent = '登出失败'; }
});
function fmtTime(t){
try{
const d = new Date(t);
if(String(d) !== 'Invalid Date'){
const pad = n=> String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
}catch(e){}
return t || '';
}
function renderStatus(s){
const v = String(s || 'pending');
const cls = (v === 'approved' || v === 'rejected') ? v : 'pending';
const text = v === 'approved' ? '已同意' : (v === 'rejected' ? '已拒绝' : '待审核');
return `<span class="tag ${cls}">${text}</span>`;
}
async function loadRequests(){
const status = document.getElementById('statusFilter').value;
const msg = document.getElementById('pageMsg');
msg.textContent = '加载中...';
const url = status ? `/accounts/registration-code/requests/list/?status=${encodeURIComponent(status)}` : '/accounts/registration-code/requests/list/';
try{
const resp = await fetch(url, { credentials: 'same-origin' });
const data = await resp.json();
if(!(resp.ok && data && data.ok)){
msg.textContent = (data && data.message) ? data.message : '加载失败';
return;
}
const body = document.getElementById('reqBody');
body.innerHTML = '';
const rows = data.data || [];
if(!rows.length){
msg.textContent = '暂无数据';
return;
}
msg.textContent = '';
rows.forEach(r=>{
const tr = document.createElement('tr');
const uname = (r.username || '') + (r.user_id !== undefined ? `${r.user_id}` : '');
const reason = String(r.reason || '').replace(/</g,'&lt;').replace(/>/g,'&gt;');
const created = fmtTime(r.created_at);
const statusHtml = renderStatus(r.status);
const id = r.request_id || r._id || '';
const ops = (String(r.status || 'pending') === 'pending')
? `<button class="btn btn-primary" data-act="approve" data-id="${id}">同意</button>
<button class="btn btn-danger" data-act="reject" data-id="${id}">拒绝</button>`
: `<button class="btn btn-secondary" data-act="view" data-id="${id}">查看</button>`;
tr.innerHTML = `<td>${uname}</td><td style="white-space:pre-wrap;">${reason}</td><td>${created}</td><td>${statusHtml}</td><td>${ops}</td>`;
body.appendChild(tr);
});
}catch(e){
msg.textContent = '加载失败';
}
}
async function decide(id, action){
const csrftoken = getCookie('csrftoken');
const note = '';
const resp = await fetch('/accounts/registration-code/requests/decide/', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' },
body: JSON.stringify({ request_id: id, action, note })
});
const data = await resp.json();
if(!(resp.ok && data && data.ok)){
alert((data && data.message) ? data.message : '操作失败');
return;
}
loadRequests();
}
document.getElementById('refreshBtn').addEventListener('click', loadRequests);
document.getElementById('statusFilter').addEventListener('change', loadRequests);
document.addEventListener('click', (e)=>{
const t = e.target;
if(!(t && t.dataset && t.dataset.id && t.dataset.act)) return;
const id = t.dataset.id;
const act = t.dataset.act;
if(act === 'approve'){
if(confirm('确定同意该申请吗?')) decide(id, 'approve');
}else if(act === 'reject'){
if(confirm('确定拒绝该申请吗?')) decide(id, 'reject');
}else if(act === 'view'){
return;
}
});
loadRequests();
</script>
</body>
</html>