cam / app /templates /base.html
cacode's picture
Upload 67 files
cf289c1 verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ page_title or app_name }} · {{ app_name }}</title>
<link rel="icon" href="/static/favicon.ico?v={{ static_asset_version('favicon.ico') }}" sizes="any" />
<link rel="stylesheet" href="/static/style.css?v={{ static_asset_version('style.css') }}" />
</head>
<body class="{% if admin %}admin-mode{% else %}user-mode{% endif %}">
<div class="page-backdrop"></div>
<div class="shell">
<header class="topbar">
<div class="brand-block">
<div class="brand-mark"></div>
<div>
<p class="eyebrow">Spring Check-In</p>
<h1 class="brand-title">{{ app_name }}</h1>
</div>
</div>
<nav class="topnav">
{% if user %}
<a href="/dashboard">活动广场</a>
<a href="/account">账号中心</a>
<a href="/logout">退出登录</a>
{% elif admin %}
<a href="/admin/dashboard">总览</a>
<a href="/admin/users">用户</a>
<a href="/admin/groups">小组</a>
<a href="/admin/activities">活动</a>
<a href="/admin/images">图片</a>
<a href="/admin/reviews">审核</a>
<a href="/account">账号中心</a>
{% if admin.role == 'superadmin' %}
<a href="/admin/admins">管理员</a>
{% endif %}
<a href="/admin/logout">退出</a>
{% endif %}
</nav>
</header>
{% include 'partials/flash.html' %}
<main class="content-shell">
{% block content %}{% endblock %}
</main>
</div>
{% if admin %}
<script>
(() => {
const PRESENCE_INTERVAL_MS = 5000;
const INITIAL_JITTER_MS = 1200;
let timerId = null;
let inFlight = false;
const shouldPing = () => !document.hidden && navigator.onLine;
const schedule = (delay = PRESENCE_INTERVAL_MS) => {
if (timerId) {
window.clearTimeout(timerId);
}
timerId = window.setTimeout(runPing, delay);
};
const runPing = async () => {
if (!shouldPing()) {
timerId = null;
return;
}
if (inFlight) {
schedule(1000);
return;
}
inFlight = true;
try {
await fetch('/api/presence/ping', {
method: 'POST',
keepalive: true,
credentials: 'same-origin',
cache: 'no-store',
headers: { 'X-Requested-With': 'fetch' },
});
} catch (error) {
console.debug('presence ping skipped', error);
} finally {
inFlight = false;
schedule();
}
};
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (timerId) {
window.clearTimeout(timerId);
timerId = null;
}
return;
}
runPing();
});
window.addEventListener('focus', () => {
if (!document.hidden) {
runPing();
}
});
window.addEventListener('online', runPing);
schedule(500 + Math.floor(Math.random() * INITIAL_JITTER_MS));
})();
</script>
{% endif %}
</body>
</html>