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

5 months ago
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
4 weeks ago
<title>首届沪苏协同新兴产业青年科创大赛</title>
4 weeks ago
<link rel="icon" type="image/png" href="/favicon.png?v=20260829c">
5 months ago
<link rel="stylesheet" href="./assets/bootstrap.min.css" />
<link rel="stylesheet" href="./assets/prototype.css" />
</head>
1 month ago
<body class="prototype-page layout-shell">
5 months ago
<header class="navbar navbar-expand-lg layout-header">
1 month ago
<div class="container-fluid px-4 layout-header-inner">
<div class="header-brand">
4 weeks ago
<span class="navbar-brand mb-0 prototype-title">首届沪苏协同新兴产业青年科创大赛</span>
1 month ago
</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>
5 months ago
</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>
1 month ago
<button class="mobile-nav-link" data-page="./pages/audience-form.html" data-role="audience">观众报名</button>
5 months ago
<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">
1 month ago
<div class="row g-3">
5 months ago
<aside class="col-12 col-md-3 col-lg-2 layout-sider">
1 month ago
<div class="card">
5 months ago
<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>
1 month ago
<button class="nav-link text-start" data-page="./pages/audience-form.html" data-role="audience">观众报名</button>
5 months ago
<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">
1 month ago
<iframe class="main-iframe" id="mainFrame" title="主内容区"></iframe>
5 months ago
</main>
</div>
</div>
1 month ago
<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>
5 months ago
</footer>
1 month ago
<div class="modal fade" id="profileModal" tabindex="-1" aria-labelledby="profileModalTitle" aria-hidden="true">
5 months ago
<div class="modal-dialog modal-dialog-centered profile-modal-dialog">
<div class="modal-content profile-modal-content">
<div class="modal-header profile-modal-header">
1 month ago
<h5 id="profileModalTitle" class="modal-title profile-modal-title">个人信息</h5>
5 months ago
<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>
1 month ago
<div class="profile-field-value">沪苏协同新兴产业科创大赛组委会</div>
5 months ago
</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>
1 month ago
<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>
5 months ago
<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";
1 month ago
const isAudience = role === "audience";
5 months ago
const isManager = isAdmin || isReviewer;
1 month ago
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;
5 months ago
1 month ago
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 : "王清萍"));
5 months ago
if (isReviewer) {
document.body.classList.add("reviewer-mode");
1 month ago
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");
5 months ago
}
if (!isManager) {
document.body.classList.add("user-mobile-no-menu");
1 month ago
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");
5 months ago
}
1 month ago
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);
5 months ago
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";
1 month ago
const forAudience = btn.dataset.role === "audience";
5 months ago
const isReportEntry = btn.dataset.page === "./pages/admin-report.html";
const isReviewerManageEntry = btn.dataset.page === "./pages/admin-reviewers.html";
1 month ago
if ((isManager || isAudience) && forUser) btn.classList.add("d-none");
5 months ago
if (!isManager && forAdmin) btn.classList.add("d-none");
1 month ago
if (!isAudience && forAudience) btn.classList.add("d-none");
5 months ago
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);
1 month ago
window.addEventListener("resize", resizeFrameToContent);
window.addEventListener("orientationchange", resizeFrameToContent);
5 months ago
</script>
</body>
</html>