feat: update assessment portal
This commit is contained in:
+123
-104
@@ -1,110 +1,129 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "_pagination.html" import render_pagination %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>管理台</title>
|
||||
{% set active_page = "admin" %}
|
||||
{% block title %}管理台 | 供水管道健康评估系统{% endblock %}
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@700;800&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet" />
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#005EB8',
|
||||
primaryDeep: '#0c4188',
|
||||
page: '#f3f5f8',
|
||||
card: '#ffffff',
|
||||
line: '#e5e7eb',
|
||||
textMain: '#0f172a',
|
||||
textSub: '#64748b',
|
||||
blueSoft: '#eaf3ff',
|
||||
bluePanel: '#1d4f9a',
|
||||
outline: '#c7ced8',
|
||||
successSoft: '#e9f8ee',
|
||||
successText: '#16a34a',
|
||||
warnSoft: '#fff4e8',
|
||||
warnText: '#c2410c',
|
||||
dangerSoft: '#fff0f0',
|
||||
dangerText: '#dc2626',
|
||||
lowCard: '#f8fafc'
|
||||
},
|
||||
fontFamily: {
|
||||
headline: ['Manrope', 'Inter', 'sans-serif'],
|
||||
body: ['Inter', 'sans-serif']
|
||||
},
|
||||
boxShadow: {
|
||||
soft: '0 24px 24px -12px rgba(24,28,30,.06)',
|
||||
card: '0 10px 25px rgba(15, 23, 42, .06)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
vertical-align: middle;
|
||||
}
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
h1, h2, h3, h4 { font-family: 'Manrope', 'Inter', sans-serif; }
|
||||
.dot-grid {
|
||||
background-image: radial-gradient(circle at 1px 1px, rgba(148,163,184,.30) 1.2px, transparent 0);
|
||||
background-size: 42px 42px;
|
||||
}
|
||||
.panel-frame {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
.gradient-board {
|
||||
background: linear-gradient(180deg, #2455a3 0%, #123e7d 100%);
|
||||
}
|
||||
.spinner {
|
||||
width: 18px; height: 18px; border-radius: 9999px;
|
||||
border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
|
||||
animation: spin .75s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
{% block content %}
|
||||
<div class="mb-6 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-extrabold tracking-tight sm:text-4xl">管理台</h1>
|
||||
<p class="mt-2 text-sm text-textSub">管理系统注册状态,查看所有用户的上传文件和预测结果。</p>
|
||||
</div>
|
||||
<a href="{{ url_for('main.home') }}" class="ui-btn ui-btn-secondary">
|
||||
<span class="material-symbols-outlined text-lg">arrow_back</span>
|
||||
返回主页
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</head>
|
||||
<body class="bg-page min-h-screen p-8 text-textMain">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-3xl font-extrabold">管理员查看上传记录</h1>
|
||||
<a href="{{ url_for('main.home') }}" class="px-4 py-2 rounded-lg border border-slate-200 bg-white">返回主页</a>
|
||||
<section class="mb-6 rounded-lg border border-line bg-white p-5 shadow-panel">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-lg font-extrabold tracking-tight">用户注册</h2>
|
||||
<p class="mt-1 text-sm text-textSub">
|
||||
当前状态:<span id="registrationStatus" class="font-bold {{ 'text-successText' if registration_allowed else 'text-slate-600' }}">{{ '已开放' if registration_allowed else '已关闭' }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl border border-slate-200 overflow-hidden shadow-card">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-slate-50 text-slate-500">
|
||||
<tr>
|
||||
<th class="text-left px-4 py-3">用户</th>
|
||||
<th class="text-left px-4 py-3">原始文件</th>
|
||||
<th class="text-left px-4 py-3">上传时间</th>
|
||||
<th class="text-left px-4 py-3">下载</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in records %}
|
||||
<tr class="border-t border-slate-100">
|
||||
<td class="px-4 py-3">{{ record.user.username }}</td>
|
||||
<td class="px-4 py-3">{{ record.original_filename }}</td>
|
||||
<td class="px-4 py-3">{{ record.upload_time.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||
<td class="px-4 py-3 flex gap-3">
|
||||
<a class="text-primary" href="{{ url_for('main.download_file', record_id=record.id, file_type='original') }}">原始文件</a>
|
||||
<a class="text-primary" href="{{ url_for('main.download_file', record_id=record.id, file_type='prediction') }}">预测结果</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="px-4 py-8 text-center text-slate-500">暂无上传记录</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<form id="registrationForm" method="post" action="{{ url_for('main.update_registration_setting') }}" class="flex items-center gap-3">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<label class="inline-flex items-center gap-2 text-sm font-semibold text-slate-600">
|
||||
<input id="registrationToggle" type="checkbox" name="allow_registration" class="rounded border-slate-300 text-primary focus:ring-primary" {{ 'checked' if registration_allowed }}>
|
||||
允许自助注册
|
||||
</label>
|
||||
<button id="registrationSubmit" type="submit" class="ui-btn ui-btn-sm ui-btn-primary">
|
||||
<span class="material-symbols-outlined text-lg">save</span>
|
||||
<span id="registrationSubmitText">保存</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex min-h-[760px] flex-col overflow-hidden rounded-lg border border-line bg-white shadow-panel">
|
||||
<div class="border-b border-line px-5 py-4">
|
||||
<div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 class="text-lg font-extrabold">上传记录</h2>
|
||||
{% if pagination.total %}
|
||||
<span class="text-sm text-textSub">共 {{ pagination.total }} 条</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<div class="flex-1 overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="bg-slate-50 text-xs font-bold uppercase tracking-[0.12em] text-textSub">
|
||||
<tr>
|
||||
<th class="px-5 py-4 text-left">用户</th>
|
||||
<th class="px-5 py-4 text-left">原始文件</th>
|
||||
<th class="px-5 py-4 text-left">上传时间</th>
|
||||
<th class="px-5 py-4 text-left">下载</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-line">
|
||||
{% for record in records %}
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-5 py-4 font-semibold">{{ record.user.username }}</td>
|
||||
<td class="max-w-[420px] truncate px-5 py-4">{{ record.original_filename }}</td>
|
||||
<td class="px-5 py-4 text-textSub">{{ record.upload_time.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a class="font-bold text-primary" href="{{ url_for('main.download_file', record_id=record.id, file_type='original') }}">原始文件</a>
|
||||
<a class="font-bold text-primary" href="{{ url_for('main.download_file', record_id=record.id, file_type='prediction') }}">预测结果</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="px-5 py-12 text-center text-textSub">暂无上传记录</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ render_pagination(pagination, 'main.admin_dashboard') }}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
(() => {
|
||||
const form = document.getElementById('registrationForm');
|
||||
const toggle = document.getElementById('registrationToggle');
|
||||
const status = document.getElementById('registrationStatus');
|
||||
const submit = document.getElementById('registrationSubmit');
|
||||
const submitText = document.getElementById('registrationSubmitText');
|
||||
|
||||
if (!form || !toggle || !status || !submit || !submitText) return;
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
submit.disabled = true;
|
||||
submitText.textContent = '保存中';
|
||||
|
||||
try {
|
||||
const response = await fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: new FormData(form),
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
window.showAppNotification?.(data.error || '保存失败,请刷新页面后重试。', 'error', '保存失败');
|
||||
return;
|
||||
}
|
||||
|
||||
toggle.checked = Boolean(data.registration_allowed);
|
||||
status.textContent = data.status_label;
|
||||
status.classList.toggle('text-successText', data.registration_allowed);
|
||||
status.classList.toggle('text-slate-600', !data.registration_allowed);
|
||||
window.showAppNotification?.(data.message, 'info');
|
||||
} catch (error) {
|
||||
window.showAppNotification?.('请求失败,请检查后端服务是否正常。', 'error', '保存失败');
|
||||
} finally {
|
||||
submit.disabled = false;
|
||||
submitText.textContent = '保存';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user