feat: update assessment portal
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>{% block title %}供水管道健康评估系统{% endblock %}</title>
|
||||
<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 = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#005EB8',
|
||||
primaryDeep: '#0c4188',
|
||||
page: '#f3f5f8',
|
||||
card: '#ffffff',
|
||||
line: '#e2e8f0',
|
||||
textMain: '#0f172a',
|
||||
textSub: '#64748b',
|
||||
blueSoft: '#eaf3ff',
|
||||
outline: '#c7ced8',
|
||||
successSoft: '#e9f8ee',
|
||||
successText: '#16a34a',
|
||||
dangerSoft: '#fff0f0',
|
||||
dangerText: '#dc2626'
|
||||
},
|
||||
fontFamily: {
|
||||
headline: ['Manrope', 'Inter', 'sans-serif'],
|
||||
body: ['Inter', 'sans-serif']
|
||||
},
|
||||
boxShadow: {
|
||||
soft: '0 18px 34px rgba(15, 23, 42, .06)',
|
||||
panel: '0 1px 2px rgba(15, 23, 42, .04)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
h1, h2, h3, h4 { font-family: 'Manrope', 'Inter', sans-serif; }
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ui-btn {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: .5rem;
|
||||
min-height: 44px;
|
||||
height: 44px;
|
||||
border-radius: .375rem;
|
||||
border: 1px solid transparent;
|
||||
padding: 0 1rem;
|
||||
font-size: .875rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.ui-btn-sm {
|
||||
min-height: 40px;
|
||||
height: 40px;
|
||||
padding: 0 .75rem;
|
||||
}
|
||||
.ui-btn-primary {
|
||||
background: #005EB8;
|
||||
color: #fff;
|
||||
box-shadow: 0 18px 34px rgba(15, 23, 42, .06);
|
||||
}
|
||||
.ui-btn-primary:hover { background: #0c4188; }
|
||||
.ui-btn-secondary {
|
||||
border-color: #e2e8f0;
|
||||
background: #fff;
|
||||
color: #334155;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
|
||||
}
|
||||
.ui-btn-secondary:hover {
|
||||
border-color: #005EB8;
|
||||
color: #005EB8;
|
||||
}
|
||||
.ui-btn:disabled,
|
||||
.ui-btn[aria-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
opacity: .7;
|
||||
}
|
||||
.ui-btn-primary:disabled { background: #cbd5e1; }
|
||||
.ui-btn-icon,
|
||||
.ui-btn .material-symbols-outlined {
|
||||
display: inline-flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
.ui-icon-btn {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex: 0 0 28px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: .375rem;
|
||||
line-height: 1;
|
||||
transition: background-color .15s ease, color .15s ease;
|
||||
}
|
||||
.ui-action-row {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: .75rem;
|
||||
border-radius: .375rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 0 .75rem;
|
||||
font-size: .875rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
transition: border-color .15s ease, color .15s ease;
|
||||
}
|
||||
.ui-action-row:hover {
|
||||
border-color: #005EB8;
|
||||
color: #005EB8;
|
||||
}
|
||||
.spinner {
|
||||
display: inline-flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
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 head_extra %}{% endblock %}
|
||||
</head>
|
||||
<body class="flex min-h-screen flex-col bg-page text-textMain antialiased">
|
||||
<header class="sticky top-0 z-30 border-b border-line bg-white/95 backdrop-blur">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="h-16 flex items-center justify-between gap-4">
|
||||
<a href="{{ url_for('main.home') }}" class="flex min-w-0 items-center gap-2 text-[16px] font-extrabold tracking-tight">
|
||||
<span class="material-symbols-outlined text-primary">water_drop</span>
|
||||
<span class="truncate">供水管道健康评估系统</span>
|
||||
</a>
|
||||
<nav class="hidden md:flex h-full items-center gap-1 text-sm font-semibold">
|
||||
<a href="{{ url_for('main.home') }}" class="flex h-full items-center px-3 {{ 'text-primary border-b-2 border-primary' if active_page == 'home' else 'text-slate-500 border-b-2 border-transparent hover:text-primary' }}">主页</a>
|
||||
<a href="{{ url_for('main.result_page') }}" class="flex h-full items-center px-3 {{ 'text-primary border-b-2 border-primary' if active_page == 'result' else 'text-slate-500 border-b-2 border-transparent hover:text-primary' }}">结果</a>
|
||||
<a href="{{ url_for('main.history_page') }}" class="flex h-full items-center px-3 {{ 'text-primary border-b-2 border-primary' if active_page == 'history' else 'text-slate-500 border-b-2 border-transparent hover:text-primary' }}">历史</a>
|
||||
<a href="{{ url_for('main.reference_page') }}" class="flex h-full items-center px-3 {{ 'text-primary border-b-2 border-primary' if active_page == 'reference' else 'text-slate-500 border-b-2 border-transparent hover:text-primary' }}">文档</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('main.admin_dashboard') }}" class="flex h-full items-center px-3 {{ 'text-primary border-b-2 border-primary' if active_page == 'admin' else 'text-slate-500 border-b-2 border-transparent hover:text-primary' }}">管理</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="hidden sm:flex items-center gap-2 text-sm font-semibold text-slate-600">
|
||||
<div class="h-8 w-8 rounded-full bg-primary text-white flex items-center justify-center text-xs">{{ current_user.username[:1]|upper }}</div>
|
||||
<span class="max-w-[120px] truncate">{{ current_user.username }}</span>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('main.logout') }}" data-clear-home-state>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="ui-btn ui-btn-sm ui-btn-secondary font-semibold text-slate-600" type="submit">退出</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="md:hidden border-t border-line bg-white">
|
||||
<div class="mx-auto max-w-7xl px-2 flex overflow-x-auto text-sm font-semibold">
|
||||
<a href="{{ url_for('main.home') }}" class="whitespace-nowrap px-3 py-3 {{ 'text-primary' if active_page == 'home' else 'text-slate-500' }}">主页</a>
|
||||
<a href="{{ url_for('main.result_page') }}" class="whitespace-nowrap px-3 py-3 {{ 'text-primary' if active_page == 'result' else 'text-slate-500' }}">结果</a>
|
||||
<a href="{{ url_for('main.history_page') }}" class="whitespace-nowrap px-3 py-3 {{ 'text-primary' if active_page == 'history' else 'text-slate-500' }}">历史</a>
|
||||
<a href="{{ url_for('main.reference_page') }}" class="whitespace-nowrap px-3 py-3 {{ 'text-primary' if active_page == 'reference' else 'text-slate-500' }}">文档</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('main.admin_dashboard') }}" class="whitespace-nowrap px-3 py-3 {{ 'text-primary' if active_page == 'admin' else 'text-slate-500' }}">管理</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{% with flashed_messages = get_flashed_messages(with_categories=true) %}
|
||||
<div id="alertBox" class="pointer-events-none fixed left-1/2 top-20 z-50 hidden w-[calc(100vw-2rem)] max-w-[430px] -translate-x-1/2 translate-y-3 rounded-lg border bg-white p-4 opacity-0 shadow-[0_18px_45px_rgba(15,23,42,.16)] transition-all duration-200 ease-out sm:top-24" role="status" aria-live="polite">
|
||||
<div class="flex items-start gap-3">
|
||||
<span id="alertIconWrap" class="flex h-9 w-9 shrink-0 items-center justify-center rounded-md">
|
||||
<span id="alertIcon" class="material-symbols-outlined text-xl">priority_high</span>
|
||||
</span>
|
||||
<div class="min-w-0 flex-1 pt-0.5">
|
||||
<div id="alertTitle" class="text-sm font-extrabold text-textMain"></div>
|
||||
<div id="alertMessage" class="mt-1 text-sm leading-5 text-textSub"></div>
|
||||
</div>
|
||||
<button id="alertClose" class="ui-icon-btn -mr-1 -mt-1 text-slate-400 hover:bg-slate-100 hover:text-slate-700" type="button" aria-label="关闭通知">
|
||||
<span class="material-symbols-outlined text-base">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.__flashMessages = {{ flashed_messages|tojson }};
|
||||
</script>
|
||||
{% endwith %}
|
||||
|
||||
<main class="mx-auto w-full max-w-7xl flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="border-t border-line bg-white">
|
||||
<div class="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between text-xs text-slate-500">
|
||||
<span>预测结果仅供参考</span>
|
||||
<span>© {{ now_year() }} 供水管道健康评估系统</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const alertBox = document.getElementById('alertBox');
|
||||
const alertIconWrap = document.getElementById('alertIconWrap');
|
||||
const alertIcon = document.getElementById('alertIcon');
|
||||
const alertTitle = document.getElementById('alertTitle');
|
||||
const alertMessage = document.getElementById('alertMessage');
|
||||
const alertClose = document.getElementById('alertClose');
|
||||
let alertTimer = null;
|
||||
let alertHideTimer = null;
|
||||
|
||||
if (!alertBox || !alertIconWrap || !alertIcon || !alertTitle || !alertMessage || !alertClose) return;
|
||||
|
||||
function hideAlert() {
|
||||
clearTimeout(alertTimer);
|
||||
clearTimeout(alertHideTimer);
|
||||
alertBox.classList.remove('opacity-100', 'translate-y-0');
|
||||
alertBox.classList.add('pointer-events-none', 'opacity-0', 'translate-y-3');
|
||||
alertHideTimer = setTimeout(() => {
|
||||
alertBox.classList.add('hidden');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function showAppNotification(message, type = 'info', title) {
|
||||
clearTimeout(alertTimer);
|
||||
clearTimeout(alertHideTimer);
|
||||
alertBox.classList.remove('hidden', 'border-red-200', 'border-blue-200');
|
||||
alertIconWrap.classList.remove('bg-dangerSoft', 'text-dangerText', 'bg-blueSoft', 'text-primary');
|
||||
|
||||
if (type === 'error') {
|
||||
alertBox.classList.add('border-red-200');
|
||||
alertIconWrap.classList.add('bg-dangerSoft', 'text-dangerText');
|
||||
alertIcon.textContent = 'priority_high';
|
||||
alertTitle.textContent = title || '操作未完成';
|
||||
} else {
|
||||
alertBox.classList.add('border-blue-200');
|
||||
alertIconWrap.classList.add('bg-blueSoft', 'text-primary');
|
||||
alertIcon.textContent = 'check_circle';
|
||||
alertTitle.textContent = title || '操作成功';
|
||||
}
|
||||
|
||||
alertMessage.textContent = message;
|
||||
requestAnimationFrame(() => {
|
||||
alertBox.classList.remove('pointer-events-none', 'opacity-0', 'translate-y-3');
|
||||
alertBox.classList.add('opacity-100', 'translate-y-0');
|
||||
});
|
||||
alertTimer = setTimeout(hideAlert, 10000);
|
||||
}
|
||||
|
||||
alertClose.addEventListener('click', hideAlert);
|
||||
window.showAppNotification = showAppNotification;
|
||||
window.hideAppNotification = hideAlert;
|
||||
|
||||
const flashedMessages = window.__flashMessages || [];
|
||||
if (flashedMessages.length) {
|
||||
const [category, message] = flashedMessages[flashedMessages.length - 1];
|
||||
showAppNotification(message, category === 'error' ? 'error' : 'info');
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-clear-home-state]').forEach((form) => {
|
||||
form.addEventListener('submit', () => {
|
||||
try {
|
||||
sessionStorage.removeItem('pipelineLifetime.homeState');
|
||||
} catch (err) {
|
||||
// Ignore storage failures during logout.
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user