<!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 = "stylesheet" href = "../assets/bootstrap.min.css" / >
< link rel = "stylesheet" href = "../assets/prototype.css" / >
< script src = "../assets/prototype-scale.js?v=20260819-hero" > < / script >
< / head >
< body class = "prototype-page audience-page" >
< div class = "container-fluid audience-page-shell" >
< div class = "mb-3 form-page-header" >
< h5 class = "mb-0 form-page-title" > 观众报名< / h5 >
< / div >
< section class = "audience-session-section" aria-label = "赛事场次" >
< div id = "sessionOverview" class = "row g-3" > < / div >
< / section >
< div class = "audience-form-panel" >
< div class = "audience-form-body" >
< form id = "audienceForm" class = "row g-3" novalidate >
< div class = "col-12 col-md-6 audience-form-fifth" >
< label class = "form-label" for = "audienceName" > 个人姓名 < span class = "text-danger" > *< / span > < / label >
< input id = "audienceName" class = "form-control audience-entry-field" autocomplete = "name" placeholder = "请先选择赛事" required disabled / >
< div class = "invalid-feedback" > 请输入个人姓名< / div >
< / div >
< div class = "col-12 col-md-6 audience-form-fifth" >
< label class = "form-label" for = "audiencePhone" > 联系电话 < span class = "text-danger" > *< / span > < / label >
< input id = "audiencePhone" class = "form-control audience-entry-field" type = "tel" inputmode = "numeric" autocomplete = "tel" pattern = "^1[3-9]\d{9}$" placeholder = "请先选择赛事" required disabled / >
< div class = "invalid-feedback" > 请输入正确的11位手机号< / div >
< / div >
< div class = "col-12 col-md-6 audience-form-fifth" >
< label class = "form-label" for = "audienceCompany" > 所属单位< / label >
< input id = "audienceCompany" class = "form-control audience-entry-field" autocomplete = "organization" placeholder = "请先选择赛事" disabled / >
< / div >
< div class = "col-12 col-md-6 audience-form-fifth" >
< label class = "form-label" for = "audienceJob" > 个人职务< / label >
< input id = "audienceJob" class = "form-control audience-entry-field" autocomplete = "organization-title" placeholder = "请先选择赛事" disabled / >
< / div >
< div class = "col-12 col-md-6 audience-form-fifth" >
< label class = "form-label" for = "audiencePurpose" > 观赛目的 < span class = "text-danger" > *< / span > < / label >
< select id = "audiencePurpose" class = "form-select audience-entry-field" required disabled > < option value = "" > 请先选择赛事< / option > < option > 项目交流< / option > < option > 产业合作< / option > < option > 投资对接< / option > < option > 学习参访< / option > < option > 媒体报道< / option > < option > 其他< / option > < / select >
< div class = "invalid-feedback" > 请选择观赛目的< / div >
< / div >
< div class = "col-12 audience-form-actions" >
< button id = "audienceSubmit" class = "btn btn-primary audience-submit-btn" type = "submit" disabled > 请先选择赛事< / button >
< / div >
< / form >
< div id = "audienceSuccess" class = "alert alert-success audience-success d-none" role = "status" > < / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "audienceTicketModal" tabindex = "-1" aria-labelledby = "audienceTicketTitle" aria-hidden = "true" >
< div class = "modal-dialog modal-dialog-centered modal-sm" > < div class = "modal-content audience-ticket-card" >
< div class = "modal-header border-0" > < h5 id = "audienceTicketTitle" class = "modal-title" > 报名成功< / h5 > < button class = "btn-close" data-bs-dismiss = "modal" aria-label = "关闭" > < / button > < / div >
< div class = "modal-body text-center pt-0" >
< div class = "audience-ticket-success-mark" > ✓< / div > < p class = "audience-ticket-lead" > 电子入场票已生成< / p >
< span class = "ticket-event-badge" > 沪苏科创大赛< / span > < h6 id = "audienceTicketSession" class = "mt-3 mb-1" > < / h6 > < p id = "audienceTicketHolder" class = "text-secondary small mb-0" > < / p >
< div id = "audienceTicketQr" class = "ticket-qr" aria-label = "电子票二维码" > < / div > < code id = "audienceTicketCode" class = "ticket-code" > < / code >
< p class = "small text-secondary mt-3 mb-3" > 入场时请出示此码,每张票仅可核销一次< / p >
< button class = "btn btn-primary w-100" type = "button" data-bs-dismiss = "modal" > 完成< / button >
< / div >
< / div > < / div >
< / div >
< script src = "../assets/bootstrap.bundle.min.js" > < / script >
< script src = "../assets/registration-i18n.js?v=20260819-4" > < / script >
< script >
const audienceSessions = [
{
id: 'shanghai-preliminary',
name: '上海预赛路演',
signupStart: '2026-08-10',
signupEnd: '2026-09-10',
date: '2026年9月18日( 星期五) ',
address: '上海市浦东新区张江科学会堂',
capacity: 300,
registered: 214
},
{
id: 'suzhou-preliminary',
name: '苏州预赛路演',
signupStart: '2026-09-15',
signupEnd: '2026-10-20',
date: '2026年10月30日( 星期五) ',
address: '苏州市工业园区苏州国际博览中心',
capacity: 260,
registered: 198
},
{
id: 'shenzhen-preliminary',
name: '深圳预赛路演',
signupStart: '2026-11-01',
signupEnd: '2026-12-18',
date: '2026年12月25日( 星期五) ',
address: '深圳市南山区深圳湾科技生态园',
capacity: 200,
registered: 126
},
{
id: 'suzhou-final',
name: '苏州总决赛',
signupStart: '2027-02-01',
signupEnd: '2027-03-10',
date: '2027年3月19日( 星期五) ',
address: '苏州市工业园区金鸡湖国际会议中心',
capacity: 500,
registered: 347
}
];
const sessionOverview = document.getElementById('sessionOverview');
const entryFields = Array.from(document.querySelectorAll('.audience-entry-field'));
const submitButton = document.getElementById('audienceSubmit');
const isEnglishAudience = document.documentElement.lang === 'en';
const fieldPlaceholders = isEnglishAudience
? ['Enter your full name', 'Enter an 11-digit mobile number', 'Enter your organization', 'Enter your job title']
: ['请输入姓名', '请输入11位手机号', '请输入单位名称', '请输入职务'];
const verifiedMobile = localStorage.getItem('verifiedMobile') || '13800138000';
const registrationKey = 'audienceRegistrations:' + verifiedMobile;
const quotaKey = 'audienceSessionRegisteredCounts';
function createTicketCode(sessionId, mobile) {
const prefix = { 'shanghai-preliminary':'SH', 'suzhou-preliminary':'SZ', 'shenzhen-preliminary':'SZH', 'suzhou-final':'FIN' }[sessionId] || 'EV';
let hash = 0;
(sessionId + mobile).split('').forEach(function (char) { hash = (hash * 31 + char.charCodeAt(0)) % 1000000; });
return 'AUD-' + prefix + '-2026-' + String(hash).padStart(6, '0');
}
function makeTicketQr(seed) {
let bits = '';
for (let i = 0; i < 225 ; i + = 1 ) bits + = ( ( seed . charCodeAt ( i % seed . length ) * 7 + i * 13 ) % 11 < 5 ) ? ' < i > < / i > ' : '< b > < / b > ';
return bits;
}
function showTicket(registration, session) {
document.getElementById('audienceTicketSession').textContent = session.name;
document.getElementById('audienceTicketHolder').textContent = document.getElementById('audienceName').value + ' · ' + verifiedMobile;
document.getElementById('audienceTicketCode').textContent = registration.ticketCode;
document.getElementById('audienceTicketQr').innerHTML = makeTicketQr(registration.ticketCode);
bootstrap.Modal.getOrCreateInstance(document.getElementById('audienceTicketModal')).show();
}
function getRegistrations() {
try {
return JSON.parse(localStorage.getItem(registrationKey)) || [];
} catch (error) {
return [];
}
}
function getRegisteredCounts() {
try {
return JSON.parse(localStorage.getItem(quotaKey)) || {};
} catch (error) {
return {};
}
}
function remainingCount(session) {
const counts = getRegisteredCounts();
return Math.max(0, session.capacity - (counts[session.id] ?? session.registered));
}
function getShanghaiDate() {
const parts = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).formatToParts(new Date());
const values = {};
parts.forEach(function (part) {
values[part.type] = part.value;
});
return values.year + '-' + values.month + '-' + values.day;
}
function shortDate(date) {
return date.slice(5).replace('-', '.');
}
const today = getShanghaiDate();
let selectedSessionId = '';
function getSessionState(session) {
const isNotStarted = today < session.signupStart ;
const isExpired = today > session.signupEnd;
const isFull = remainingCount(session) === 0;
return {
isNotStarted: isNotStarted,
isExpired: isExpired,
isFull: isFull,
isAvailable: !isNotStarted & & !isExpired & & !isFull
};
}
function renderSessionOverview() {
const registrations = getRegistrations();
sessionOverview.innerHTML = audienceSessions.map(function (session) {
const state = getSessionState(session);
const isRegistered = registrations.some(function (item) { return item.sessionId === session.id; });
const remaining = remainingCount(session);
const isFinal = session.id === 'suzhou-final';
const statusText = isRegistered ? '已报名' : (state.isFull ? '已满额' : (state.isExpired ? '已截止' : (state.isNotStarted ? shortDate(session.signupStart) + ' 开放' : '报名中')));
const statusClass = isRegistered ? 'is-registered' : (state.isAvailable ? 'is-open' : (state.isFull ? 'is-full' : ''));
return '< div class = "col-12 col-md-6 col-xl-3" > ' +
'< article class = "audience-session-card' + (isFinal ? ' is-final' : '') + (isRegistered ? ' is-registered' : '') + '" role = "radio" tabindex = "0" aria-checked = "' + (selectedSessionId === session.id) + '" aria-disabled = "' + isRegistered + '" data-session-id = "' + session.id + '" > ' +
'< div class = "audience-session-card-head" > < div class = "audience-session-name-wrap" > < h6 > ' + session.name + '< / h6 > < / div > < span class = "audience-status-badge ' + statusClass + '" > ' + statusText + '< / span > < / div > ' +
'< div class = "audience-quota" > < strong > ' + remaining + '< / strong > < span > 人< / span > < small > 剩余可报名< / small > < / div > ' +
'< div class = "audience-session-meta" > < span > ' + session.date + '< / span > < span > ' + session.address + '< / span > < / div > ' +
'< div class = "audience-session-selected" > ✓ 已选择< / div > ' +
'< / article > < / div > ';
}).join('');
}
function selectSession(sessionId) {
const session = audienceSessions.find(function (item) { return item.id === sessionId; });
const isRegistered = getRegistrations().some(function (item) { return item.sessionId === sessionId; });
if (!session || isRegistered || !getSessionState(session).isAvailable) return;
selectedSessionId = sessionId;
entryFields.forEach(function (field, index) {
field.disabled = false;
if (field.tagName === 'SELECT') {
field.options[0].textContent = isEnglishAudience ? 'Please select a purpose' : '请选择观赛目的';
} else {
field.placeholder = fieldPlaceholders[index];
}
});
submitButton.disabled = false;
submitButton.textContent = isEnglishAudience ? 'Submit Registration' : '提交观众报名';
renderSessionOverview();
document.getElementById('audienceName').focus({ preventScroll: true });
}
renderSessionOverview();
sessionOverview.addEventListener('click', function (event) {
const card = event.target.closest('.audience-session-card');
if (card) selectSession(card.dataset.sessionId);
});
sessionOverview.addEventListener('keydown', function (event) {
const card = event.target.closest('.audience-session-card');
if (card & & (event.key === 'Enter' || event.key === ' ')) {
event.preventDefault();
selectSession(card.dataset.sessionId);
}
});
document.getElementById('audienceForm').addEventListener('submit', function (event) {
event.preventDefault();
this.classList.add('was-validated');
if (this.checkValidity()) {
const session = audienceSessions.find(function (item) { return item.id === selectedSessionId; });
if (!session || remainingCount(session) === 0) return;
const registrations = getRegistrations();
const isAlreadyRegistered = registrations.some(function (item) { return item.sessionId === session.id; });
let currentRegistration = registrations.find(function (item) { return item.sessionId === session.id; });
if (!isAlreadyRegistered) {
currentRegistration = {
sessionId: session.id,
registeredAt: new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', dateStyle: 'medium', timeStyle: 'short' }).format(new Date()),
ticketCode: createTicketCode(session.id, verifiedMobile),
holderName: document.getElementById('audienceName').value,
phone: verifiedMobile,
checkedIn: false
};
registrations.unshift(currentRegistration);
localStorage.setItem(registrationKey, JSON.stringify(registrations));
const counts = getRegisteredCounts();
counts[session.id] = (counts[session.id] ?? session.registered) + 1;
localStorage.setItem(quotaKey, JSON.stringify(counts));
}
const success = document.getElementById('audienceSuccess');
success.textContent = isAlreadyRegistered
? '您已报名“' + session.name + '”。'
: '报名成功!“' + session.name + '”的场次信息将通过手机号发送。';
success.classList.remove('d-none');
renderSessionOverview();
if (!currentRegistration.ticketCode) {
currentRegistration.ticketCode = createTicketCode(session.id, verifiedMobile);
localStorage.setItem(registrationKey, JSON.stringify(registrations));
}
showTicket(currentRegistration, session);
}
});
< / script >
< / body >
< / html >