You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

391 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>首届沪苏协同新兴产业青年科创大赛</title>
<link rel="icon" type="image/png" href="/favicon.png?v=20260829c">
<link rel="stylesheet" href="./assets/bootstrap.min.css" />
<link rel="stylesheet" href="./assets/prototype.css" />
</head>
<body class="prototype-page layout-shell">
<header class="navbar navbar-expand-lg layout-header">
<div class="container-fluid px-4 layout-header-inner">
<div class="header-brand">
<span class="navbar-brand mb-0 prototype-title">首届沪苏协同新兴产业青年科创大赛</span>
</div>
<div class="header-meta">
<span class="header-status">报名进行中</span>
<div class="header-deadline" aria-label="报名截止时间">
<span>截至</span>
<time datetime="2026-12-31T23:59:00+08:00">2026-12-31 23:59</time>
</div>
<div class="header-audience-actions">
<button id="headerMyRegistrationsButton" class="header-my-registrations d-none" type="button" aria-haspopup="dialog" aria-controls="myRegistrationsModal">我的报名</button>
<div class="dropdown header-user-dropdown">
<button id="headerUserToggle" class="header-user dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="true" aria-expanded="false" aria-label="打开用户菜单">
<span id="roleBadge" class="header-user-name">王清萍</span>
</button>
<ul class="dropdown-menu dropdown-menu-end header-user-menu" aria-labelledby="headerUserToggle">
<li id="profileMenuItem">
<a id="profileMenuLink" class="dropdown-item" href="javascript:void(0)" data-bs-toggle="modal" data-bs-target="#profileModal">
<span id="profileMenuLabel">个人信息</span>
</a>
</li>
<li class="header-user-menu-separator">
<a class="dropdown-item dropdown-item-danger" href="./login.html">
<span>退出登录</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</header>
<div id="mobileNavSection" class="container-fluid px-4 pt-2 pb-1 d-lg-none">
<div class="mobile-nav-wrap" id="mobileNav">
<button class="mobile-nav-link" data-page="./pages/user-form.html" data-role="user">赛事报名</button>
<button class="mobile-nav-link" data-page="./pages/audience-form.html" data-role="audience">观众报名</button>
<button class="mobile-nav-link" data-page="./pages/admin-report.html" data-role="admin">综合看板</button>
<button class="mobile-nav-link" data-page="./pages/admin-list.html" data-role="admin">项目管理</button>
<button class="mobile-nav-link" data-page="./pages/admin-reviewers.html" data-role="admin">评审员管理</button>
</div>
</div>
<div class="container-fluid px-4 py-3 layout-main">
<div class="row g-3">
<aside class="col-12 col-md-3 col-lg-2 layout-sider">
<div class="card">
<div class="card-body">
<div class="nav flex-column gap-2" id="sideNav">
<button class="nav-link text-start" data-page="./pages/user-form.html" data-role="user">赛事报名</button>
<button class="nav-link text-start" data-page="./pages/audience-form.html" data-role="audience">观众报名</button>
<button class="nav-link text-start" data-page="./pages/admin-report.html" data-role="admin">综合看板</button>
<button class="nav-link text-start" data-page="./pages/admin-list.html" data-role="admin">项目管理</button>
<button class="nav-link text-start" data-page="./pages/admin-reviewers.html" data-role="admin">评审员管理</button>
</div>
</div>
</div>
</aside>
<main class="col-12 col-md-9 col-lg-10">
<iframe class="main-iframe" id="mainFrame" title="主内容区"></iframe>
</main>
</div>
</div>
<footer id="layoutFooter" class="layout-footer">
<div class="layout-footer-inner">
<div class="layout-footer-brand">沪苏协同新兴产业科创大赛</div>
<div class="layout-footer-meta">
<span>© 苏州市人民政府驻上海联络处&nbsp;&nbsp;苏州恒泰控股集团有限公司</span>
</div>
</div>
</footer>
<div class="modal fade" id="profileModal" tabindex="-1" aria-labelledby="profileModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered profile-modal-dialog">
<div class="modal-content profile-modal-content">
<div class="modal-header profile-modal-header">
<h5 id="profileModalTitle" class="modal-title profile-modal-title">个人信息</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button>
</div>
<div class="modal-body profile-modal-body">
<div class="row g-3">
<div class="col-md-6">
<div class="profile-field">
<div class="profile-field-label">姓名</div>
<div class="profile-field-value">王清萍</div>
</div>
</div>
<div class="col-md-6">
<div class="profile-field">
<div class="profile-field-label">手机号</div>
<div class="profile-field-value">13800138000</div>
</div>
</div>
<div class="col-md-6">
<div class="profile-field">
<div class="profile-field-label">单位</div>
<div class="profile-field-value">沪苏协同新兴产业科创大赛组委会</div>
</div>
</div>
<div class="col-md-6">
<div class="profile-field">
<div class="profile-field-label">邮箱</div>
<div class="profile-field-value">wangqp@example.com</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="myRegistrationsModal" tabindex="-1" aria-labelledby="myRegistrationsModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered my-registrations-modal-dialog">
<div class="modal-content profile-modal-content">
<div class="modal-header profile-modal-header">
<div>
<h5 id="myRegistrationsModalTitle" class="modal-title profile-modal-title">我的报名</h5>
<p class="my-registrations-modal-subtitle">查看已提交的活动报名信息</p>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button>
</div>
<div id="myRegistrationsModalBody" class="modal-body profile-modal-body my-registrations-modal-body"></div>
</div>
</div>
</div>
<script src="./assets/bootstrap.bundle.min.js"></script>
<script>
const navButtons = Array.from(
document.querySelectorAll("#sideNav .nav-link, #mobileNav .mobile-nav-link")
);
const frame = document.getElementById("mainFrame");
const role = new URLSearchParams(window.location.search).get("role") || "user";
const roleBadge = document.getElementById("roleBadge");
const isAdmin = role === "admin";
const isReviewer = role === "reviewer";
const isAudience = role === "audience";
const isManager = isAdmin || isReviewer;
const defaultPage = isReviewer ? "./pages/admin-list.html?role=reviewer" : (isAdmin ? "./pages/admin-report.html" : (isAudience ? "./pages/audience-form.html" : "./pages/user-form.html"));
const headerStatus = document.querySelector(".header-status");
const headerDeadline = document.querySelector(".header-deadline");
const headerMyRegistrationsButton = document.getElementById("headerMyRegistrationsButton");
const headerUserToggle = document.getElementById("headerUserToggle");
const profileMenuItem = document.getElementById("profileMenuItem");
const profileMenuLink = document.getElementById("profileMenuLink");
const profileMenuLabel = document.getElementById("profileMenuLabel");
const logoutMenuItem = document.querySelector(".header-user-menu-separator");
const myRegistrationsModalElement = document.getElementById("myRegistrationsModal");
const myRegistrationsModalBody = document.getElementById("myRegistrationsModalBody");
const verifiedMobile = localStorage.getItem("verifiedMobile") || "13800138000";
const demoAudienceRegistrations = [
{
sessionId: "shanghai-preliminary",
registeredAt: "2026年8月12日 10:26"
}
];
let frameResizeObserver;
const audienceSessionDetails = {
"shanghai-preliminary": {
name: "上海预赛路演",
date: "2026年9月18日星期五",
address: "上海市浦东新区张江科学会堂"
},
"suzhou-preliminary": {
name: "苏州预赛路演",
date: "2026年10月30日星期五",
address: "苏州市工业园区苏州国际博览中心"
},
"shenzhen-preliminary": {
name: "深圳预赛路演",
date: "2027年1月8日星期五",
address: "深圳市南山区深圳湾科技生态园"
},
"suzhou-final": {
name: "苏州总决赛",
date: "2027年3月19日星期五",
address: "苏州市工业园区金鸡湖国际会议中心"
}
};
function escapeHtml(value) {
return String(value ?? "").replace(/[&<>'"]/g, function (character) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" }[character];
});
}
function renderMyRegistrationsModal() {
let registrations = [];
try {
const storedRegistrations = localStorage.getItem("audienceRegistrations:" + verifiedMobile);
registrations = storedRegistrations === null
? demoAudienceRegistrations
: (JSON.parse(storedRegistrations) || []);
} catch (error) {
registrations = demoAudienceRegistrations;
}
const validRegistrations = registrations.filter(function (registration) {
return audienceSessionDetails[registration.sessionId];
});
if (validRegistrations.length === 0) {
myRegistrationsModalBody.innerHTML =
'<div class="my-registrations-modal-empty">' +
'<span class="my-registrations-modal-empty-icon" aria-hidden="true">暂无</span>' +
'<strong>暂无报名记录</strong>' +
'<p>选择可报名赛事并提交后,报名信息将在这里展示。</p>' +
'</div>';
return;
}
myRegistrationsModalBody.innerHTML =
'<div class="my-registrations-modal-summary"><span>已报名场次</span><strong>' + validRegistrations.length + '<small>场</small></strong></div>' +
'<div class="my-registrations-modal-list">' +
validRegistrations.map(function (registration) {
const session = audienceSessionDetails[registration.sessionId];
return '<article class="my-registration-modal-card">' +
'<div class="my-registration-modal-card-head">' +
'<h6>' + escapeHtml(session.name) + '</h6>' +
'<span><i aria-hidden="true"></i>报名成功</span>' +
'</div>' +
'<dl>' +
'<div><dt>活动时间</dt><dd>' + escapeHtml(session.date) + '</dd></div>' +
'<div><dt>活动地点</dt><dd>' + escapeHtml(session.address) + '</dd></div>' +
'<div><dt>报名时间</dt><dd>' + escapeHtml(registration.registeredAt || "—") + '</dd></div>' +
'</dl>' +
'</article>';
}).join('') +
'</div>';
}
roleBadge.textContent = isAdmin ? "管理员" : (isReviewer ? "评审员" : (isAudience ? verifiedMobile : "王清萍"));
if (isReviewer) {
document.body.classList.add("reviewer-mode");
headerStatus?.classList.add("d-none");
headerDeadline?.classList.add("d-none");
profileMenuItem?.remove();
logoutMenuItem?.classList.remove("header-user-menu-separator");
}
if (isAdmin) {
document.body.classList.add("admin-mode");
headerStatus?.classList.add("d-none");
headerDeadline?.classList.add("d-none");
profileMenuItem?.remove();
logoutMenuItem?.classList.remove("header-user-menu-separator");
}
if (isAudience) {
headerStatus?.classList.add("d-none");
headerDeadline?.classList.add("d-none");
headerMyRegistrationsButton?.classList.remove("d-none");
profileMenuItem?.remove();
logoutMenuItem?.classList.remove("header-user-menu-separator");
headerMyRegistrationsButton?.addEventListener("click", function () {
renderMyRegistrationsModal();
bootstrap.Modal.getOrCreateInstance(myRegistrationsModalElement).show();
});
}
if (!isManager && !isAudience) {
profileMenuItem?.remove();
logoutMenuItem?.classList.remove("header-user-menu-separator");
}
if (!isManager) {
document.body.classList.add("user-mobile-no-menu");
const sideMenu = document.querySelector(".layout-sider");
const mainContent = document.querySelector(".layout-main main");
sideMenu?.classList.add("d-none");
mainContent?.classList.remove("col-md-9", "col-lg-10");
}
function closeHeaderUserMenu() {
if (!headerUserToggle || headerUserToggle.getAttribute("aria-expanded") !== "true") return;
bootstrap.Dropdown.getOrCreateInstance(headerUserToggle).hide();
}
function resizeFrameToContent() {
try {
const doc = frame.contentDocument;
if (!doc?.documentElement || !doc.body) return;
doc.documentElement.style.overflow = "visible";
doc.body.style.overflow = "visible";
const contentHeight = Math.ceil(
Array.from(doc.body.children).reduce((bottom, element) => {
const styles = doc.defaultView?.getComputedStyle(element);
if (!styles || styles.display === "none" || styles.position === "fixed") return bottom;
return Math.max(bottom, element.getBoundingClientRect().bottom);
}, 0)
);
const headerHeight = document.querySelector(".layout-header")?.offsetHeight || 0;
const mobileNavHeight = document.getElementById("mobileNavSection")?.offsetHeight || 0;
const footerHeight = document.getElementById("layoutFooter")?.offsetHeight || 0;
const main = document.querySelector(".layout-main");
const mainStyles = main ? getComputedStyle(main) : null;
const mainVerticalPadding = mainStyles
? parseFloat(mainStyles.paddingTop) + parseFloat(mainStyles.paddingBottom)
: 0;
const viewportHeight = window.visualViewport?.height || window.innerHeight;
const availableHeight = viewportHeight
- headerHeight
- mobileNavHeight
- footerHeight
- mainVerticalPadding;
frame.style.height = Math.max(360, contentHeight, availableHeight) + "px";
} catch (error) {
// The prototype pages are same-origin; safely ignore if that changes later.
}
}
function bindFrameDocument() {
try {
const doc = frame.contentDocument;
if (!doc?.documentElement || !doc.body) {
requestAnimationFrame(bindFrameDocument);
return;
}
doc.addEventListener("pointerdown", closeHeaderUserMenu);
frameResizeObserver?.disconnect();
frameResizeObserver = new doc.defaultView.ResizeObserver(resizeFrameToContent);
frameResizeObserver.observe(doc.documentElement);
frameResizeObserver.observe(doc.body);
resizeFrameToContent();
if (frame.contentWindow.location.hash === "#my-registrations") {
requestAnimationFrame(function () {
const target = doc.getElementById("my-registrations");
if (!target) return;
target.focus({ preventScroll: true });
window.scrollTo({
top: frame.getBoundingClientRect().top + window.scrollY + target.offsetTop - 16,
behavior: "smooth"
});
});
}
} catch (error) {
// The prototype pages are same-origin; safely ignore if that changes later.
}
}
frame.addEventListener("load", bindFrameDocument);
frame.src = defaultPage;
if (isReviewer) {
navButtons.forEach((btn) => {
if (btn.dataset.page === "./pages/admin-list.html") {
btn.textContent = "项目评审";
}
});
}
function setActive(page) {
navButtons.forEach((btn) => {
btn.classList.toggle("active", btn.dataset.page === page && !btn.classList.contains("d-none"));
});
}
navButtons.forEach((btn) => {
const forUser = btn.dataset.role === "user";
const forAdmin = btn.dataset.role === "admin";
const forAudience = btn.dataset.role === "audience";
const isReportEntry = btn.dataset.page === "./pages/admin-report.html";
const isReviewerManageEntry = btn.dataset.page === "./pages/admin-reviewers.html";
if ((isManager || isAudience) && forUser) btn.classList.add("d-none");
if (!isManager && forAdmin) btn.classList.add("d-none");
if (!isAudience && forAudience) btn.classList.add("d-none");
if (isReviewer && (isReportEntry || isReviewerManageEntry)) btn.classList.add("d-none");
btn.addEventListener("click", () => {
const targetPage = isReviewer && forAdmin ? (btn.dataset.page + "?role=reviewer") : btn.dataset.page;
frame.src = targetPage;
setActive(btn.dataset.page);
});
});
setActive(defaultPage);
window.addEventListener("resize", resizeFrameToContent);
window.addEventListener("orientationchange", resizeFrameToContent);
</script>
</body>
</html>