295 lines
12 KiB
HTML
295 lines
12 KiB
HTML
<!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>
|