|
|
<!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">
|
|
|
<div class="container-fluid p-3 p-md-4">
|
|
|
<div class="mb-3 form-page-header">
|
|
|
<h5 class="mb-0 form-page-title" id="formPageTitle">赛事报名</h5>
|
|
|
</div>
|
|
|
|
|
|
<div class="card mb-3">
|
|
|
<div class="card-body">
|
|
|
<form id="applyForm" class="row g-3">
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">项目名称 <span class="text-danger">*</span></label>
|
|
|
<input class="form-control editable" id="projectName" required />
|
|
|
<div class="invalid-feedback">请填写项目名称</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">申请人姓名 <span class="text-danger">*</span></label>
|
|
|
<input class="form-control editable" id="playerName" value="" required />
|
|
|
<div class="invalid-feedback">请填写申请人姓名</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">注册邮箱 <span class="text-danger">*</span></label>
|
|
|
<input type="email" class="form-control editable" id="email" required />
|
|
|
<div class="invalid-feedback">请输入正确的邮箱格式</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">申请人电话 <span class="text-danger">*</span></label>
|
|
|
<input type="tel" class="form-control editable" id="mobile" value="13800138000" required />
|
|
|
<div class="invalid-feedback">请填写申请人电话</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">毕业院校 <span class="text-danger">*</span></label>
|
|
|
<input class="form-control editable" id="school" required />
|
|
|
<div class="invalid-feedback">请填写毕业院校</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">最高学历 <span class="text-danger">*</span></label>
|
|
|
<select class="form-select editable" id="degree" required>
|
|
|
<option value="">请选择</option>
|
|
|
<option>大专</option>
|
|
|
<option>本科</option>
|
|
|
<option>硕士</option>
|
|
|
<option>博士</option>
|
|
|
<option>其他</option>
|
|
|
</select>
|
|
|
<div class="invalid-feedback">请选择最高学历</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label" id="trackFieldLabel">细分赛道 <span class="text-danger">*</span></label>
|
|
|
<div class="track-custom-select w-100">
|
|
|
<select id="track" class="visually-hidden" required tabindex="-1" aria-hidden="true">
|
|
|
<option value="">请选择</option>
|
|
|
<option value="芯片与组件">芯片与组件</option>
|
|
|
<option value="制造与封装">制造与封装</option>
|
|
|
<option value="材料与设备">材料与设备</option>
|
|
|
<option value="场景与应用">场景与应用</option>
|
|
|
</select>
|
|
|
<div class="dropdown w-100">
|
|
|
<button type="button" class="form-select track-custom-toggle editable w-100 text-start" id="trackDropdownBtn" data-bs-toggle="dropdown" data-bs-display="static" aria-labelledby="trackFieldLabel" aria-expanded="false">
|
|
|
<span class="d-block"><span id="trackMainText" class="text-secondary">请选择</span><span id="trackNoteText" class="track-custom-note"></span></span>
|
|
|
</button>
|
|
|
<ul class="dropdown-menu w-100 border shadow-sm rounded-2 py-0 my-1 track-custom-menu">
|
|
|
<li><button type="button" class="dropdown-item track-pick border-0 w-100 text-start rounded-0" data-value=""><span class="text-secondary">请选择</span></button></li>
|
|
|
<li><button type="button" class="dropdown-item track-pick border-0 w-100 text-start rounded-0" data-value="芯片与组件"><span class="track-pick-inline"><span class="text-body">芯片与组件</span><span class="track-custom-note">光芯片、光电元器件、光模块等核心芯片与组件研发创新</span></span></button></li>
|
|
|
<li><button type="button" class="dropdown-item track-pick border-0 w-100 text-start rounded-0" data-value="制造与封装"><span class="track-pick-inline"><span class="text-body">制造与封装</span><span class="track-custom-note">光电子精密制造、器件封装耦合、量产工艺优化等制造封装技术攻关</span></span></button></li>
|
|
|
<li><button type="button" class="dropdown-item track-pick border-0 w-100 text-start rounded-0" data-value="材料与设备"><span class="track-pick-inline"><span class="text-body">材料与设备</span><span class="track-custom-note">光电功能新材料、光纤基材及光刻、镀膜、精密测试等专用设备研发</span></span></button></li>
|
|
|
<li><button type="button" class="dropdown-item track-pick border-0 w-100 text-start rounded-0" data-value="场景与应用"><span class="track-pick-inline"><span class="text-body">场景与应用</span><span class="track-custom-note">面向算力网络、移动通信、车载互联、工业传感、航空航天、医疗等领域的系统方案与应用创新</span></span></button></li>
|
|
|
</ul>
|
|
|
<div class="invalid-feedback">请选择细分赛道</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">企业名称 <span class="text-danger" data-optional-en>*</span></label>
|
|
|
<input class="form-control editable company-name-input" id="companyName" autocomplete="organization" required />
|
|
|
<div class="invalid-feedback">请填写企业名称</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">项目所在地 <span class="text-danger">*</span></label>
|
|
|
<select class="form-select editable" id="locationCountry" required>
|
|
|
<option value="">请选择</option>
|
|
|
<option value="中国">中国</option>
|
|
|
<option value="海外">海外</option>
|
|
|
</select>
|
|
|
<div class="invalid-feedback">请选择项目所在国家</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3 d-none" id="locationProvinceWrap">
|
|
|
<label class="form-label">省份 <span class="text-danger">*</span></label>
|
|
|
<select class="form-select editable" id="locationProvince" required>
|
|
|
<option value="">请选择</option>
|
|
|
</select>
|
|
|
<div class="invalid-feedback">请选择项目所在省份</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3 d-none" id="locationCityWrap">
|
|
|
<label class="form-label">城市 <span class="text-danger">*</span></label>
|
|
|
<select class="form-select editable" id="locationCity" required>
|
|
|
<option value="">请选择</option>
|
|
|
</select>
|
|
|
<div class="invalid-feedback">请选择项目所在城市</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3 d-none" id="overseaCountryWrap">
|
|
|
<label class="form-label">所在国 <span class="text-danger">*</span></label>
|
|
|
<input class="form-control editable" id="locationOverseaCountry" placeholder="如:新加坡、日本、美国" />
|
|
|
<div class="invalid-feedback">请选择“海外”后填写所在国家</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">参赛地点 <span class="text-danger">*</span></label>
|
|
|
<select class="form-select editable" id="eventLocation" required>
|
|
|
<option value="">请选择</option><option>上海</option><option>苏州</option><option>深圳</option>
|
|
|
</select>
|
|
|
<div class="invalid-feedback">请选择参赛地点</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">团队成员 <span class="text-danger" data-optional-en>*</span></label>
|
|
|
<input class="form-control editable" id="teamMembers" placeholder="多人请用顿号分隔" required />
|
|
|
<div class="invalid-feedback">请填写团队成员</div>
|
|
|
</div>
|
|
|
<div class="col-12 col-md-3">
|
|
|
<label class="form-label">推荐方</label>
|
|
|
<input class="form-control editable" id="referrer" placeholder="单位或推荐人名称" />
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12">
|
|
|
<label class="form-label">项目简介 <span class="text-danger">*</span></label>
|
|
|
<textarea class="form-control editable" rows="4" id="intro" required></textarea>
|
|
|
<div class="d-flex justify-content-between mt-1">
|
|
|
<small class="text-secondary prototype-subtitle">约200字左右</small>
|
|
|
<small id="countInfo" class="text-secondary">0 字</small>
|
|
|
</div>
|
|
|
<div class="invalid-feedback">请填写项目简介</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12">
|
|
|
<label class="form-label">上传商业计划书 <span class="text-danger">*</span></label>
|
|
|
<input type="file" class="form-control editable" id="planFile" accept=".pdf" />
|
|
|
<small class="text-secondary prototype-subtitle">包括企业概况与定位、核心产品/服务、市场规模和竞争情况、创新壁垒与差异化优势、核心团队介绍与优势、未来发展战略规划等,PDF格式,建议不超过20页。</small>
|
|
|
<div class="supporting-file-list mt-2 d-none" id="planFileList" aria-live="polite"></div>
|
|
|
<small class="text-secondary prototype-subtitle d-block" id="planFileSavedInfo"></small>
|
|
|
<div class="invalid-feedback" id="planFileFeedback">请上传商业计划书</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12">
|
|
|
<label class="form-label">其他佐证材料</label>
|
|
|
<input type="file" class="form-control editable" id="supportingFiles" accept=".pdf,.ppt,.pptx,.doc,.docx,.wps,.rar,.zip" multiple />
|
|
|
<small class="text-secondary prototype-subtitle d-block">可上传多个文件:企业营业执照扫描件;知识产权相关证明材料(专利证书、商标注册证、软件著作权等),其他佐证材料(产品或服务介绍、获奖证书、投融资证明等)、创始人或主要负责人学历学位等资质证明。</small>
|
|
|
<small class="text-secondary prototype-subtitle">单文件 20M 以内,支持:PDF、PPT、PPTX、DOC、DOCX、WPS、RAR、ZIP</small>
|
|
|
<div class="supporting-file-list mt-2 d-none" id="supportingFileList" aria-live="polite"></div>
|
|
|
<small class="text-secondary prototype-subtitle d-block" id="supportingFilesSavedInfo"></small>
|
|
|
<div class="invalid-feedback" id="supportingFilesFeedback">文件格式或大小不符合要求</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12">
|
|
|
<label class="form-label">参赛承诺书 <span class="text-danger">*</span></label>
|
|
|
<p class="small text-secondary mb-2">请点击按钮打开承诺书全文,阅读后在文末手写签名并点击「确认签署」。</p>
|
|
|
<div class="d-flex flex-wrap align-items-center gap-2 mb-1">
|
|
|
<button type="button" class="btn btn-outline-primary btn-sm editable" id="promiseOpenBtn" data-bs-toggle="modal" data-bs-target="#promiseSignModal">查看并签署参赛承诺书</button>
|
|
|
<span id="promiseSignedBadge" class="badge rounded-pill px-3 py-2 d-none promise-signed-badge">已完成签署</span>
|
|
|
</div>
|
|
|
<input type="hidden" id="promiseAck" value="" />
|
|
|
</div>
|
|
|
</form>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div id="userFormActions" class="d-flex gap-2 flex-wrap">
|
|
|
<button class="btn btn-outline-primary" id="saveBtn">暂存</button>
|
|
|
<button class="btn btn-success" id="submitBtn">提交报名</button>
|
|
|
<a class="btn btn-light d-none" id="detailBackBtn" href="./my-events.html">返回列表</a>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="modal fade" id="promiseSignModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
|
|
|
<div class="modal-dialog modal-dialog-centered promise-sign-modal">
|
|
|
<div class="modal-content promise-sign-sheet">
|
|
|
<div class="promise-modal-header">
|
|
|
<div>
|
|
|
<h1 class="promise-doc-heading text-center">光通信创新创业大赛赛事承诺书</h1>
|
|
|
<p class="promise-modal-subtitle">请完整阅读以下 4 项承诺,确认无误后在下方签名。</p>
|
|
|
</div>
|
|
|
<button type="button" class="btn-close promise-modal-x" data-bs-dismiss="modal" aria-label="关闭"></button>
|
|
|
</div>
|
|
|
<div class="modal-body promise-document-scroll">
|
|
|
<article class="promise-doc-paper" aria-label="赛事承诺书正文">
|
|
|
<div class="promise-doc-body">
|
|
|
<p>本团队(或个人)自愿报名参加大赛,为明确权利义务,维护赛事公平性与合法性,承诺严格履行以下条款:</p>
|
|
|
<h2>一、参赛保证</h2>
|
|
|
<p>1.1 提交的全部参赛材料真实、准确、完整,技术参数、研发进度、市场营收、产业链合作等数据来源合法可追溯,不存在造假、夸大宣传或刻意误导。</p>
|
|
|
<p>1.2 同意组委会要求补充证明材料,并配合真实性核验、知识产权权属溯源、技术资料复核及样机实测;拒绝配合或核查结果存疑时,组委会有权取消参赛资格及已获奖项。</p>
|
|
|
<p>1.3 参赛项目相关技术、产品、设计、代码、图纸、商标文案及商业模式等知识产权合法所有;使用第三方技术、开源代码或素材时已取得有效授权并遵守协议。</p>
|
|
|
<p>1.4 因知识产权侵权或权属纠纷产生的法律责任与经济损失由参赛方承担,主办方、承办方及组委会不承担连带责任。</p>
|
|
|
<h2>二、授权许可</h2>
|
|
|
<p>2.1 为赛事存档、行业宣传、成果推介及产业科普,参赛方无偿授权组委会及合作机构在赛事及配套活动中拍照、录像、录音,并使用参赛人员肖像及项目图文视频资料进行发布传播。</p>
|
|
|
<p>2.2 经准许入场的媒体、产业机构等第三方使用相关资料的权责由第三方与参赛方自行约定,组委会不对其独立传播行为承担责任。</p>
|
|
|
<h2>三、声明</h2>
|
|
|
<p>3.1 参赛方可在合规前提下分享参赛历程与技术成果,并对自行拍摄、剪辑和发布内容的真实性、知识产权、保密及秩序责任负责。</p>
|
|
|
<p>3.2 因参赛方发布内容造成索赔、处罚或名誉损失的,参赛方应处理纠纷并承担赔偿金、罚金、律师费、诉讼费及公关维权费等损失。</p>
|
|
|
<h2>四、承诺</h2>
|
|
|
<p>4.1 参赛方遵守管理条例、评审细则和赛程安排,尊重评审专家及最终结果,理性沟通赛事争议,共同维护赛事秩序。</p>
|
|
|
<p>本承诺书签字后即刻生效,约束全体参赛成员(个人参赛仅约束本人),受赛事举办地法律法规管辖。</p>
|
|
|
</div>
|
|
|
</article>
|
|
|
</div>
|
|
|
<div class="promise-sign-footer">
|
|
|
<div class="promise-sign-footer-content">
|
|
|
<div class="promise-doc-signblock">
|
|
|
<div class="promise-doc-signrow">
|
|
|
<span class="promise-doc-signlabel">参赛人签名:</span>
|
|
|
<div class="promise-sig-wrap">
|
|
|
<div class="promise-sig-box">
|
|
|
<canvas id="promiseSigCanvas" class="promise-sig-canvas" width="520" height="140" aria-label="手写签名区域"></canvas>
|
|
|
<span class="promise-sig-hint">请在框内手写签名</span>
|
|
|
</div>
|
|
|
<div class="promise-sig-tools">
|
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="promiseSigClear">清除签名</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="promise-doc-daterow">
|
|
|
<span class="promise-doc-dateline">日期:<strong id="promiseDateAuto"></strong></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="promise-sign-actions">
|
|
|
<button type="button" class="btn promise-close-btn" data-bs-dismiss="modal">关闭</button>
|
|
|
<button type="button" class="btn btn-primary" id="promiseConfirmBtn">确认签署</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="modal fade" id="noticeModal" tabindex="-1" aria-hidden="true">
|
|
|
<div class="modal-dialog modal-dialog-centered notice-modal-dialog">
|
|
|
<div class="modal-content notice-modal-content" id="noticeModalContent">
|
|
|
<div class="modal-header border-0 pb-0">
|
|
|
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="关闭"></button>
|
|
|
</div>
|
|
|
<div class="modal-body notice-modal-body">
|
|
|
<div class="notice-icon" id="noticeIcon" aria-hidden="true"></div>
|
|
|
<h5 class="notice-title" id="noticeTitle">提示</h5>
|
|
|
<p class="notice-text" id="noticeBody"></p>
|
|
|
</div>
|
|
|
<div class="modal-footer notice-modal-footer border-0" id="noticeModalFooter">
|
|
|
<button type="button" class="btn btn-light notice-cancel-btn" data-bs-dismiss="modal">取消</button>
|
|
|
<button type="button" class="btn btn-primary notice-ok-btn" data-bs-dismiss="modal">确定</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="modal fade" id="documentPreviewModal" tabindex="-1" aria-hidden="true">
|
|
|
<div class="modal-dialog modal-dialog-centered document-preview-dialog">
|
|
|
<div class="modal-content document-preview-shell">
|
|
|
<div class="document-preview-header">
|
|
|
<div class="document-preview-heading">
|
|
|
<span class="document-preview-type" id="documentPreviewType">PDF</span>
|
|
|
<div class="min-w-0">
|
|
|
<h2 id="documentPreviewTitle">文档预览</h2>
|
|
|
<p id="documentPreviewMeta">安全预览 · 仅供赛事审核使用</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button>
|
|
|
</div>
|
|
|
<div class="document-preview-toolbar" aria-label="文档工具栏">
|
|
|
<div class="document-page-control">
|
|
|
<button type="button" class="document-tool-btn" id="documentPrevPage" aria-label="上一页">上一页</button>
|
|
|
<span><strong id="documentCurrentPage">1</strong> / <span id="documentTotalPages">1</span></span>
|
|
|
<button type="button" class="document-tool-btn" id="documentNextPage" aria-label="下一页">下一页</button>
|
|
|
</div>
|
|
|
<div class="document-zoom-control">
|
|
|
<button type="button" class="document-tool-btn" id="documentZoomOut" aria-label="缩小">-</button>
|
|
|
<span id="documentZoomLabel">100%</span>
|
|
|
<button type="button" class="document-tool-btn" id="documentZoomIn" aria-label="放大">+</button>
|
|
|
<button type="button" class="document-tool-btn document-download-btn" id="documentDownloadBtn">下载</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="modal-body document-preview-stage" id="documentPreviewStage">
|
|
|
<div class="document-page-wrap" id="documentPageWrap">
|
|
|
<article class="document-paper" id="documentPaper" aria-label="文档正文"></article>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="document-preview-footer">
|
|
|
<span>文件内容已加载完成</span>
|
|
|
<button type="button" class="btn btn-primary px-4" 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 intro = document.getElementById("intro");
|
|
|
const countInfo = document.getElementById("countInfo");
|
|
|
const applyForm = document.getElementById("applyForm");
|
|
|
const planFile = document.getElementById("planFile");
|
|
|
const planFileList = document.getElementById("planFileList");
|
|
|
const planFileSavedInfo = document.getElementById("planFileSavedInfo");
|
|
|
const planFileFeedback = document.getElementById("planFileFeedback");
|
|
|
const supportingFiles = document.getElementById("supportingFiles");
|
|
|
const supportingFileList = document.getElementById("supportingFileList");
|
|
|
const supportingFilesSavedInfo = document.getElementById("supportingFilesSavedInfo");
|
|
|
const supportingFilesFeedback = document.getElementById("supportingFilesFeedback");
|
|
|
const promiseAck = document.getElementById("promiseAck");
|
|
|
const promiseOpenBtn = document.getElementById("promiseOpenBtn");
|
|
|
const promiseSignedBadge = document.getElementById("promiseSignedBadge");
|
|
|
const promiseSignModalEl = document.getElementById("promiseSignModal");
|
|
|
const promiseSigCanvas = document.getElementById("promiseSigCanvas");
|
|
|
const promiseSigHint = document.querySelector(".promise-sig-hint");
|
|
|
const promiseSigClear = document.getElementById("promiseSigClear");
|
|
|
const promiseConfirmBtn = document.getElementById("promiseConfirmBtn");
|
|
|
const promiseDateAuto = document.getElementById("promiseDateAuto");
|
|
|
const allowedExt = ["pdf", "ppt", "pptx", "doc", "docx", "wps", "rar", "zip"];
|
|
|
const maxFileSize = 20 * 1024 * 1024;
|
|
|
let planFileItems = [];
|
|
|
let supportingFileItems = [];
|
|
|
const pageParams = new URLSearchParams(window.location.search);
|
|
|
const isDetailMode = pageParams.get("mode") === "detail";
|
|
|
const submissionId = pageParams.get("id") || "P2026-0001";
|
|
|
const storageKey = isDetailMode ? "jscc_my_event_detail_v2" : "jscc_signup_form_draft_v1";
|
|
|
const noticeModal = new bootstrap.Modal(document.getElementById("noticeModal"));
|
|
|
const noticeTitle = document.getElementById("noticeTitle");
|
|
|
const noticeBody = document.getElementById("noticeBody");
|
|
|
const noticeIcon = document.getElementById("noticeIcon");
|
|
|
const noticeModalContent = document.getElementById("noticeModalContent");
|
|
|
const noticeModalFooter = document.getElementById("noticeModalFooter");
|
|
|
const trackSel = document.getElementById("track");
|
|
|
const trackBtn = document.getElementById("trackDropdownBtn");
|
|
|
const locationCountry = document.getElementById("locationCountry");
|
|
|
const locationProvince = document.getElementById("locationProvince");
|
|
|
const locationCity = document.getElementById("locationCity");
|
|
|
const locationOverseaCountry = document.getElementById("locationOverseaCountry");
|
|
|
const overseaCountryWrap = document.getElementById("overseaCountryWrap");
|
|
|
const locationProvinceWrap = document.getElementById("locationProvinceWrap");
|
|
|
const locationCityWrap = document.getElementById("locationCityWrap");
|
|
|
const documentPreviewModalEl = document.getElementById("documentPreviewModal");
|
|
|
const documentPreviewModal = new bootstrap.Modal(documentPreviewModalEl);
|
|
|
const documentPaper = document.getElementById("documentPaper");
|
|
|
const documentPageWrap = document.getElementById("documentPageWrap");
|
|
|
const documentPreviewTitle = document.getElementById("documentPreviewTitle");
|
|
|
const documentPreviewType = document.getElementById("documentPreviewType");
|
|
|
const documentCurrentPage = document.getElementById("documentCurrentPage");
|
|
|
const documentTotalPages = document.getElementById("documentTotalPages");
|
|
|
const documentZoomLabel = document.getElementById("documentZoomLabel");
|
|
|
let previewPage = 1;
|
|
|
let previewZoom = 1;
|
|
|
let previewPages = [];
|
|
|
let previewFileName = "";
|
|
|
const cityMap = {
|
|
|
"北京市": ["北京市"],
|
|
|
"天津市": ["天津市"],
|
|
|
"上海市": ["上海市"],
|
|
|
"重庆市": ["重庆市"],
|
|
|
"河北省": ["石家庄市", "唐山市", "秦皇岛市", "邯郸市", "邢台市", "保定市", "张家口市", "承德市", "沧州市", "廊坊市", "衡水市"],
|
|
|
"山西省": ["太原市", "大同市", "阳泉市", "长治市", "晋城市", "朔州市", "晋中市", "运城市", "忻州市", "临汾市", "吕梁市"],
|
|
|
"内蒙古自治区": ["呼和浩特市", "包头市", "乌海市", "赤峰市", "通辽市", "鄂尔多斯市", "呼伦贝尔市", "巴彦淖尔市", "乌兰察布市", "兴安盟", "锡林郭勒盟", "阿拉善盟"],
|
|
|
"辽宁省": ["沈阳市", "大连市", "鞍山市", "抚顺市", "本溪市", "丹东市", "锦州市", "营口市", "阜新市", "辽阳市", "盘锦市", "铁岭市", "朝阳市", "葫芦岛市"],
|
|
|
"吉林省": ["长春市", "吉林市", "四平市", "辽源市", "通化市", "白山市", "松原市", "白城市", "延边朝鲜族自治州"],
|
|
|
"黑龙江省": ["哈尔滨市", "齐齐哈尔市", "鸡西市", "鹤岗市", "双鸭山市", "大庆市", "伊春市", "佳木斯市", "七台河市", "牡丹江市", "黑河市", "绥化市", "大兴安岭地区"],
|
|
|
"江苏省": ["南京市", "无锡市", "徐州市", "常州市", "苏州市", "南通市", "连云港市", "淮安市", "盐城市", "扬州市", "镇江市", "泰州市", "宿迁市"],
|
|
|
"浙江省": ["杭州市", "宁波市", "温州市", "嘉兴市", "湖州市", "绍兴市", "金华市", "衢州市", "舟山市", "台州市", "丽水市"],
|
|
|
"安徽省": ["合肥市", "芜湖市", "蚌埠市", "淮南市", "马鞍山市", "淮北市", "铜陵市", "安庆市", "黄山市", "滁州市", "阜阳市", "宿州市", "六安市", "亳州市", "池州市", "宣城市"],
|
|
|
"福建省": ["福州市", "厦门市", "莆田市", "三明市", "泉州市", "漳州市", "南平市", "龙岩市", "宁德市"],
|
|
|
"江西省": ["南昌市", "景德镇市", "萍乡市", "九江市", "新余市", "鹰潭市", "赣州市", "吉安市", "宜春市", "抚州市", "上饶市"],
|
|
|
"山东省": ["济南市", "青岛市", "淄博市", "枣庄市", "东营市", "烟台市", "潍坊市", "济宁市", "泰安市", "威海市", "日照市", "临沂市", "德州市", "聊城市", "滨州市", "菏泽市"],
|
|
|
"河南省": ["郑州市", "开封市", "洛阳市", "平顶山市", "安阳市", "鹤壁市", "新乡市", "焦作市", "濮阳市", "许昌市", "漯河市", "三门峡市", "南阳市", "商丘市", "信阳市", "周口市", "驻马店市", "济源市"],
|
|
|
"湖北省": ["武汉市", "黄石市", "十堰市", "宜昌市", "襄阳市", "鄂州市", "荆门市", "孝感市", "荆州市", "黄冈市", "咸宁市", "随州市", "恩施土家族苗族自治州", "仙桃市", "潜江市", "天门市", "神农架林区"],
|
|
|
"湖南省": ["长沙市", "株洲市", "湘潭市", "衡阳市", "邵阳市", "岳阳市", "常德市", "张家界市", "益阳市", "郴州市", "永州市", "怀化市", "娄底市", "湘西土家族苗族自治州"],
|
|
|
"广东省": ["广州市", "韶关市", "深圳市", "珠海市", "汕头市", "佛山市", "江门市", "湛江市", "茂名市", "肇庆市", "惠州市", "梅州市", "汕尾市", "河源市", "阳江市", "清远市", "东莞市", "中山市", "潮州市", "揭阳市", "云浮市"],
|
|
|
"广西壮族自治区": ["南宁市", "柳州市", "桂林市", "梧州市", "北海市", "防城港市", "钦州市", "贵港市", "玉林市", "百色市", "贺州市", "河池市", "来宾市", "崇左市"],
|
|
|
"海南省": ["海口市", "三亚市", "三沙市", "儋州市", "五指山市", "琼海市", "文昌市", "万宁市", "东方市", "定安县", "屯昌县", "澄迈县", "临高县", "白沙黎族自治县", "昌江黎族自治县", "乐东黎族自治县", "陵水黎族自治县", "保亭黎族苗族自治县", "琼中黎族苗族自治县"],
|
|
|
"四川省": ["成都市", "自贡市", "攀枝花市", "泸州市", "德阳市", "绵阳市", "广元市", "遂宁市", "内江市", "乐山市", "南充市", "眉山市", "宜宾市", "广安市", "达州市", "雅安市", "巴中市", "资阳市", "阿坝藏族羌族自治州", "甘孜藏族自治州", "凉山彝族自治州"],
|
|
|
"贵州省": ["贵阳市", "六盘水市", "遵义市", "安顺市", "毕节市", "铜仁市", "黔西南布依族苗族自治州", "黔东南苗族侗族自治州", "黔南布依族苗族自治州"],
|
|
|
"云南省": ["昆明市", "曲靖市", "玉溪市", "保山市", "昭通市", "丽江市", "普洱市", "临沧市", "楚雄彝族自治州", "红河哈尼族彝族自治州", "文山壮族苗族自治州", "西双版纳傣族自治州", "大理白族自治州", "德宏傣族景颇族自治州", "怒江傈僳族自治州", "迪庆藏族自治州"],
|
|
|
"西藏自治区": ["拉萨市", "日喀则市", "昌都市", "林芝市", "山南市", "那曲市", "阿里地区"],
|
|
|
"陕西省": ["西安市", "铜川市", "宝鸡市", "咸阳市", "渭南市", "延安市", "汉中市", "榆林市", "安康市", "商洛市"],
|
|
|
"甘肃省": ["兰州市", "嘉峪关市", "金昌市", "白银市", "天水市", "武威市", "张掖市", "平凉市", "酒泉市", "庆阳市", "定西市", "陇南市", "临夏回族自治州", "甘南藏族自治州"],
|
|
|
"青海省": ["西宁市", "海东市", "海北藏族自治州", "黄南藏族自治州", "海南藏族自治州", "果洛藏族自治州", "玉树藏族自治州", "海西蒙古族藏族自治州"],
|
|
|
"宁夏回族自治区": ["银川市", "石嘴山市", "吴忠市", "固原市", "中卫市"],
|
|
|
"新疆维吾尔自治区": ["乌鲁木齐市", "克拉玛依市", "吐鲁番市", "哈密市", "昌吉回族自治州", "博尔塔拉蒙古自治州", "巴音郭楞蒙古自治州", "阿克苏地区", "克孜勒苏柯尔克孜自治州", "喀什地区", "和田地区", "伊犁哈萨克自治州", "塔城地区", "阿勒泰地区", "石河子市", "阿拉尔市", "图木舒克市", "五家渠市", "北屯市", "铁门关市", "双河市", "可克达拉市", "昆玉市", "胡杨河市", "新星市"],
|
|
|
"香港特别行政区": ["香港"],
|
|
|
"澳门特别行政区": ["澳门"],
|
|
|
"台湾省": ["台北市", "新北市", "桃园市", "台中市", "台南市", "高雄市", "新竹市", "嘉义市"]
|
|
|
};
|
|
|
const provinceOrder = [
|
|
|
"北京市", "天津市", "上海市", "重庆市",
|
|
|
"河北省", "山西省", "内蒙古自治区",
|
|
|
"辽宁省", "吉林省", "黑龙江省",
|
|
|
"江苏省", "浙江省", "安徽省", "福建省", "江西省", "山东省",
|
|
|
"河南省", "湖北省", "湖南省", "广东省", "广西壮族自治区", "海南省",
|
|
|
"四川省", "贵州省", "云南省", "西藏自治区",
|
|
|
"陕西省", "甘肃省", "青海省", "宁夏回族自治区", "新疆维吾尔自治区",
|
|
|
"香港特别行政区", "澳门特别行政区", "台湾省"
|
|
|
];
|
|
|
|
|
|
const detailDefaults = {
|
|
|
projectName: "高速硅光芯片研发平台",
|
|
|
playerName: "王清萍",
|
|
|
email: "wangqp@example.com",
|
|
|
mobile: "13800138000",
|
|
|
school: "武汉大学",
|
|
|
degree: "硕士",
|
|
|
track: "场景与应用",
|
|
|
companyName: "江苏示例科技有限公司",
|
|
|
locationCountry: "中国",
|
|
|
locationProvince: "江苏省",
|
|
|
locationCity: "苏州市",
|
|
|
locationOverseaCountry: "",
|
|
|
eventLocation: "苏州",
|
|
|
teamMembers: "王清萍、李明、张慧",
|
|
|
referrer: "苏州工业园区科技创新委员会",
|
|
|
intro: "聚焦高速硅光芯片与光通信系统研发,通过核心器件、封装工艺和系统方案协同创新,提升产品的传输性能与产业化能力。",
|
|
|
promiseSigned: true,
|
|
|
planFileNames: ["高速硅光芯片研发平台-商业计划书.pdf"],
|
|
|
supportingFileNames: ["企业营业执照.pdf", "核心专利证书.pdf"]
|
|
|
};
|
|
|
|
|
|
const previewDocuments = {
|
|
|
plan: [
|
|
|
`<div class="doc-cover"><p class="doc-kicker">首届沪苏协同新兴产业科创大赛</p><h1>高速硅光芯片研发平台<br>商业计划书</h1><div class="doc-cover-rule"></div><p>申报单位:江苏示例科技有限公司</p><p>项目负责人:王清萍</p><p>申报日期:2026 年 4 月</p></div>`,
|
|
|
`<h1>一、项目概述</h1><p>高速硅光芯片研发平台面向算力网络、数据中心互联与下一代光通信应用,聚焦硅光芯片设计、先进封装、测试验证和产业化服务。</p><p>项目依托自主研发的高速调制器、光耦合结构和低功耗驱动方案,形成从芯片设计到系统验证的完整技术链路,缩短创新产品从样片到量产的周期。</p><h2>核心目标</h2><ul><li>建设高速硅光芯片公共研发与中试平台;</li><li>突破高带宽、低功耗及高可靠性关键技术;</li><li>服务长三角光通信产业链协同创新。</li></ul>`,
|
|
|
`<h1>二、市场与产品</h1><p>随着人工智能算力集群和云基础设施持续扩张,高速光互联正由 400G 向 800G、1.6T 演进。项目重点推出面向数据中心的硅光引擎、光电共封装组件及配套测试服务。</p><table><tr><th>产品方向</th><th>目标场景</th><th>核心优势</th></tr><tr><td>800G 硅光引擎</td><td>数据中心互联</td><td>低功耗、高集成</td></tr><tr><td>光电共封装组件</td><td>算力集群</td><td>高带宽密度</td></tr><tr><td>研发测试服务</td><td>创新企业与高校</td><td>缩短验证周期</td></tr></table>`,
|
|
|
`<h1>三、团队与规划</h1><p>核心团队由光电子、集成电路、先进封装和产业运营人才组成,具备从技术研发到规模化交付的复合经验。</p><h2>发展计划</h2><p>第一阶段完成平台建设和样片验证;第二阶段导入重点客户并实现小批量交付;第三阶段形成标准化产品矩阵,拓展国内外市场。</p><div class="doc-signature"><p>江苏示例科技有限公司</p><p>2026 年 4 月 30 日</p></div>`
|
|
|
],
|
|
|
license: [
|
|
|
`<div class="license-document"><p class="license-label">营业执照</p><h1>营业执照</h1><p class="license-subtitle">(副本)</p><dl><dt>统一社会信用代码</dt><dd>91320594MA2EXAMPLE</dd><dt>名称</dt><dd>江苏示例科技有限公司</dd><dt>类型</dt><dd>有限责任公司</dd><dt>法定代表人</dt><dd>王清萍</dd><dt>注册资本</dt><dd>人民币 1000 万元</dd><dt>成立日期</dt><dd>2021 年 6 月 18 日</dd><dt>住所</dt><dd>苏州工业园区星湖街 328 号</dd><dt>经营范围</dt><dd>集成电路设计、光电子器件研发、技术服务与成果转化。</dd></dl><p class="license-seal">苏州市市场监督管理局<br><span>2026 年 3 月 12 日</span></p></div>`
|
|
|
],
|
|
|
patent: [
|
|
|
`<div class="patent-document"><p class="patent-country">中华人民共和国国家知识产权局</p><h1>发明专利证书</h1><p>发明名称:一种低功耗高速硅光调制器及其制备方法</p><p>发明人:王清萍 李明 张慧</p><p>专利号:ZL 2024 1 0123456.7</p><p>专利申请日:2024 年 3 月 18 日</p><p>专利权人:江苏示例科技有限公司</p><div class="patent-note">本发明经审查,符合《中华人民共和国专利法》规定,授予发明专利权,颁发本证书并在专利登记簿上予以登记。</div><p class="license-seal">国家知识产权局<br><span>2026 年 2 月 8 日</span></p></div>`
|
|
|
]
|
|
|
};
|
|
|
|
|
|
function documentKindForName(name) {
|
|
|
if (name.includes("营业执照")) return "license";
|
|
|
if (name.includes("专利")) return "patent";
|
|
|
return "plan";
|
|
|
}
|
|
|
|
|
|
function renderPreviewPage() {
|
|
|
documentPaper.innerHTML = previewPages[previewPage - 1] || "";
|
|
|
documentCurrentPage.textContent = String(previewPage);
|
|
|
documentTotalPages.textContent = String(previewPages.length);
|
|
|
documentPageWrap.style.setProperty("--document-zoom", String(previewZoom));
|
|
|
documentZoomLabel.textContent = Math.round(previewZoom * 100) + "%";
|
|
|
document.getElementById("documentPrevPage").disabled = previewPage <= 1;
|
|
|
document.getElementById("documentNextPage").disabled = previewPage >= previewPages.length;
|
|
|
document.getElementById("documentPreviewStage").scrollTo({ top: 0, behavior: "smooth" });
|
|
|
}
|
|
|
|
|
|
function openDocumentPreview(name) {
|
|
|
const kind = documentKindForName(name);
|
|
|
previewFileName = name;
|
|
|
previewPages = previewDocuments[kind];
|
|
|
previewPage = 1;
|
|
|
previewZoom = 1;
|
|
|
documentPreviewTitle.textContent = name;
|
|
|
documentPreviewType.textContent = name.split(".").pop().toUpperCase();
|
|
|
renderPreviewPage();
|
|
|
documentPreviewModal.show();
|
|
|
}
|
|
|
|
|
|
function renderSavedDocuments(listEl, names) {
|
|
|
listEl.classList.remove("d-none");
|
|
|
listEl.innerHTML = names.map((name) => (
|
|
|
`<button type="button" class="saved-document-link" data-preview-name="${escapeHtml(name)}" title="打开 ${escapeHtml(name)}">` +
|
|
|
`<span class="saved-document-badge">${escapeHtml(name.split(".").pop().toUpperCase())}</span>` +
|
|
|
`<span class="saved-document-name">${escapeHtml(name)}</span>` +
|
|
|
`<span class="saved-document-action">查看文档</span>` +
|
|
|
`</button>`
|
|
|
)).join("");
|
|
|
}
|
|
|
|
|
|
if (isDetailMode) {
|
|
|
document.getElementById("formPageTitle").textContent = "项目详情";
|
|
|
document.getElementById("saveBtn").textContent = "保存";
|
|
|
document.getElementById("submitBtn").textContent = "重新提交";
|
|
|
document.getElementById("detailBackBtn").classList.remove("d-none");
|
|
|
}
|
|
|
|
|
|
function initProvinceOptions(selectedProvince = "") {
|
|
|
if (!locationProvince) return;
|
|
|
locationProvince.innerHTML = '<option value="">请选择</option>' + provinceOrder.map((p) => '<option value="' + p + '">' + p + "</option>").join("");
|
|
|
if (selectedProvince && provinceOrder.includes(selectedProvince)) {
|
|
|
locationProvince.value = selectedProvince;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function fillLocationCities(province, selectedCity = "") {
|
|
|
if (!locationCity) return;
|
|
|
const cities = cityMap[province] || [];
|
|
|
locationCity.innerHTML = '<option value="">请选择</option>' + cities.map((city) => '<option value="' + city + '">' + city + "</option>").join("");
|
|
|
if (selectedCity && cities.includes(selectedCity)) {
|
|
|
locationCity.value = selectedCity;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function syncLocationFields() {
|
|
|
if (!locationCountry || !locationProvince || !locationCity) return;
|
|
|
const isChina = locationCountry.value === "中国";
|
|
|
const isOversea = locationCountry.value === "海外";
|
|
|
if (isChina) {
|
|
|
initProvinceOptions(locationProvince.value);
|
|
|
fillLocationCities(locationProvince.value, locationCity.value);
|
|
|
} else if (isOversea) {
|
|
|
locationProvince.value = "";
|
|
|
locationCity.innerHTML = '<option value="">海外无需填写城市</option>';
|
|
|
locationCity.value = "";
|
|
|
if (locationOverseaCountry) {
|
|
|
locationOverseaCountry.required = true;
|
|
|
}
|
|
|
if (overseaCountryWrap) {
|
|
|
overseaCountryWrap.classList.remove("d-none");
|
|
|
}
|
|
|
} else {
|
|
|
locationProvince.value = "";
|
|
|
locationCity.innerHTML = '<option value="">请选择</option>';
|
|
|
locationCity.value = "";
|
|
|
if (locationOverseaCountry) {
|
|
|
locationOverseaCountry.value = "";
|
|
|
locationOverseaCountry.required = false;
|
|
|
}
|
|
|
if (overseaCountryWrap) {
|
|
|
overseaCountryWrap.classList.add("d-none");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
locationProvince.disabled = !isChina;
|
|
|
locationCity.disabled = !isChina;
|
|
|
locationProvince.required = isChina;
|
|
|
locationCity.required = isChina;
|
|
|
if (locationProvinceWrap) locationProvinceWrap.classList.toggle("d-none", !isChina);
|
|
|
if (locationCityWrap) locationCityWrap.classList.toggle("d-none", !isChina);
|
|
|
locationProvince.closest(".col-md-3").querySelector(".text-danger").classList.toggle("d-none", !isChina);
|
|
|
locationCity.closest(".col-md-3").querySelector(".text-danger").classList.toggle("d-none", !isChina);
|
|
|
}
|
|
|
|
|
|
if (locationCountry) {
|
|
|
locationCountry.addEventListener("change", () => {
|
|
|
syncLocationFields();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (locationProvince) {
|
|
|
locationProvince.addEventListener("change", () => {
|
|
|
fillLocationCities(locationProvince.value);
|
|
|
if (locationCity) locationCity.value = "";
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function syncTrackField() {
|
|
|
if (!trackSel || !trackBtn) return;
|
|
|
const main = document.getElementById("trackMainText");
|
|
|
const note = document.getElementById("trackNoteText");
|
|
|
const notes = {
|
|
|
"芯片与组件": "光芯片、光电元器件、光模块等核心芯片与组件研发创新",
|
|
|
"制造与封装": "光电子精密制造、器件封装耦合、量产工艺优化等制造封装技术攻关",
|
|
|
"材料与设备": "光电功能新材料、光纤基材及光刻、镀膜、精密测试等专用设备研发",
|
|
|
"场景与应用": "面向算力网络、移动通信、车载互联、工业传感、航空航天、医疗等领域的系统方案与应用创新"
|
|
|
};
|
|
|
const v = trackSel.value;
|
|
|
if (!v) {
|
|
|
main.textContent = "请选择";
|
|
|
main.className = "text-secondary";
|
|
|
note.textContent = "";
|
|
|
} else {
|
|
|
main.textContent = v;
|
|
|
main.className = "text-body";
|
|
|
note.textContent = notes[v] || "";
|
|
|
}
|
|
|
trackBtn.classList.remove("is-invalid");
|
|
|
}
|
|
|
|
|
|
document.querySelectorAll("#applyForm .track-pick").forEach((pick) => {
|
|
|
pick.addEventListener("click", () => {
|
|
|
trackSel.value = pick.getAttribute("data-value") || "";
|
|
|
trackSel.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
syncTrackField();
|
|
|
const inst = bootstrap.Dropdown.getInstance(trackBtn);
|
|
|
if (inst) inst.hide();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
let promiseSigCtx = null;
|
|
|
let promiseSigCssW = 520;
|
|
|
let promiseSigCssH = 140;
|
|
|
let promiseSigDrawing = false;
|
|
|
let promiseSigHasInk = false;
|
|
|
let promiseSignatureDataUrl = "";
|
|
|
|
|
|
function formatCnDate(d) {
|
|
|
return d.getFullYear() + "年" + (d.getMonth() + 1) + "月" + d.getDate() + "日";
|
|
|
}
|
|
|
|
|
|
function updatePromiseSignedUI() {
|
|
|
const done = promiseAck.value === "1";
|
|
|
promiseSignedBadge.classList.toggle("d-none", !done);
|
|
|
promiseOpenBtn.textContent = isDetailMode && done ? "查看《首届沪苏协同新兴产业科创大赛承诺书》" : (done ? "重新查看承诺书" : "查看并签署参赛承诺书");
|
|
|
}
|
|
|
|
|
|
function layoutPromiseCanvas() {
|
|
|
if (!promiseSigCanvas) return;
|
|
|
const wrap = promiseSigCanvas.closest(".promise-sig-wrap");
|
|
|
const dpr = window.devicePixelRatio || 1;
|
|
|
const canvasStyle = window.getComputedStyle(promiseSigCanvas);
|
|
|
promiseSigCssW = Math.max(280, Math.floor(wrap.clientWidth || 520));
|
|
|
promiseSigCssH = Math.max(76, Math.floor(parseFloat(canvasStyle.height) || 110));
|
|
|
promiseSigCanvas.style.width = promiseSigCssW + "px";
|
|
|
promiseSigCanvas.style.height = promiseSigCssH + "px";
|
|
|
promiseSigCanvas.width = Math.floor(promiseSigCssW * dpr);
|
|
|
promiseSigCanvas.height = Math.floor(promiseSigCssH * dpr);
|
|
|
promiseSigCtx = promiseSigCanvas.getContext("2d");
|
|
|
promiseSigCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
promiseSigCtx.scale(dpr, dpr);
|
|
|
promiseSigCtx.fillStyle = "#ffffff";
|
|
|
promiseSigCtx.fillRect(0, 0, promiseSigCssW, promiseSigCssH);
|
|
|
promiseSigCtx.strokeStyle = "#111111";
|
|
|
promiseSigCtx.lineWidth = 2;
|
|
|
promiseSigCtx.lineCap = "round";
|
|
|
promiseSigCtx.lineJoin = "round";
|
|
|
promiseSigHasInk = false;
|
|
|
if (promiseSigHint) promiseSigHint.classList.remove("d-none");
|
|
|
}
|
|
|
|
|
|
function restorePromiseSignature(dataUrl) {
|
|
|
if (!dataUrl || !promiseSigCtx) return;
|
|
|
const signatureImage = new Image();
|
|
|
signatureImage.onload = () => {
|
|
|
promiseSigCtx.clearRect(0, 0, promiseSigCssW, promiseSigCssH);
|
|
|
promiseSigCtx.fillStyle = "#ffffff";
|
|
|
promiseSigCtx.fillRect(0, 0, promiseSigCssW, promiseSigCssH);
|
|
|
promiseSigCtx.drawImage(signatureImage, 0, 0, promiseSigCssW, promiseSigCssH);
|
|
|
promiseSigHasInk = true;
|
|
|
if (promiseSigHint) promiseSigHint.classList.add("d-none");
|
|
|
};
|
|
|
signatureImage.src = dataUrl;
|
|
|
}
|
|
|
|
|
|
function promiseSigPos(ev) {
|
|
|
const rect = promiseSigCanvas.getBoundingClientRect();
|
|
|
const t = ev.touches && ev.touches[0];
|
|
|
const cx = t ? t.clientX : ev.clientX;
|
|
|
const cy = t ? t.clientY : ev.clientY;
|
|
|
return { x: cx - rect.left, y: cy - rect.top };
|
|
|
}
|
|
|
|
|
|
function bindPromiseCanvas() {
|
|
|
if (!promiseSigCanvas || promiseSigCanvas.dataset.bound) return;
|
|
|
promiseSigCanvas.dataset.bound = "1";
|
|
|
|
|
|
const start = (ev) => {
|
|
|
if (ev.type === "touchstart") ev.preventDefault();
|
|
|
promiseSigDrawing = true;
|
|
|
const p = promiseSigPos(ev);
|
|
|
promiseSigCtx.beginPath();
|
|
|
promiseSigCtx.moveTo(p.x, p.y);
|
|
|
};
|
|
|
const move = (ev) => {
|
|
|
if (!promiseSigDrawing) return;
|
|
|
if (ev.type === "touchmove") ev.preventDefault();
|
|
|
const p = promiseSigPos(ev);
|
|
|
promiseSigCtx.lineTo(p.x, p.y);
|
|
|
promiseSigCtx.stroke();
|
|
|
promiseSigHasInk = true;
|
|
|
if (promiseSigHint) promiseSigHint.classList.add("d-none");
|
|
|
};
|
|
|
const end = () => {
|
|
|
promiseSigDrawing = false;
|
|
|
};
|
|
|
|
|
|
promiseSigCanvas.addEventListener("mousedown", start);
|
|
|
promiseSigCanvas.addEventListener("mousemove", move);
|
|
|
window.addEventListener("mouseup", end);
|
|
|
promiseSigCanvas.addEventListener("touchstart", start, { passive: false });
|
|
|
promiseSigCanvas.addEventListener("touchmove", move, { passive: false });
|
|
|
promiseSigCanvas.addEventListener("touchend", end);
|
|
|
promiseSigCanvas.addEventListener("touchcancel", end);
|
|
|
}
|
|
|
|
|
|
if (promiseSignModalEl) {
|
|
|
promiseSignModalEl.addEventListener("shown.bs.modal", () => {
|
|
|
if (promiseDateAuto) promiseDateAuto.textContent = formatCnDate(new Date());
|
|
|
requestAnimationFrame(() => {
|
|
|
layoutPromiseCanvas();
|
|
|
restorePromiseSignature(promiseSignatureDataUrl);
|
|
|
bindPromiseCanvas();
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (promiseSigClear) {
|
|
|
promiseSigClear.addEventListener("click", () => {
|
|
|
promiseSignatureDataUrl = "";
|
|
|
layoutPromiseCanvas();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (promiseConfirmBtn) {
|
|
|
promiseConfirmBtn.addEventListener("click", () => {
|
|
|
if (!promiseSigHasInk) {
|
|
|
showNotice("请先在签名区域内手写签名后再确认。", "请确认", "warning");
|
|
|
return;
|
|
|
}
|
|
|
promiseSignatureDataUrl = promiseSigCanvas.toDataURL("image/png");
|
|
|
localStorage.setItem("jscc_submitted_signature_" + submissionId, promiseSignatureDataUrl);
|
|
|
promiseAck.value = "1";
|
|
|
updatePromiseSignedUI();
|
|
|
const inst = bootstrap.Modal.getInstance(promiseSignModalEl);
|
|
|
if (inst) inst.hide();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const refreshCount = () => { countInfo.textContent = intro.value.length + " 字"; };
|
|
|
intro.addEventListener("input", refreshCount);
|
|
|
refreshCount();
|
|
|
|
|
|
function formatFileSize(bytes) {
|
|
|
if (bytes >= 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1).replace(/\.0$/, "") + "M";
|
|
|
if (bytes >= 1024) return Math.ceil(bytes / 1024) + "K";
|
|
|
return bytes + "B";
|
|
|
}
|
|
|
|
|
|
function escapeHtml(text) {
|
|
|
return String(text).replace(/[&<>"']/g, (char) => ({
|
|
|
"&": "&",
|
|
|
"<": "<",
|
|
|
">": ">",
|
|
|
'"': """,
|
|
|
"'": "'"
|
|
|
})[char]);
|
|
|
}
|
|
|
|
|
|
function createPreviewUrl(file) {
|
|
|
return URL.createObjectURL(file);
|
|
|
}
|
|
|
|
|
|
function renderFileList(listEl, items) {
|
|
|
if (!items.length) {
|
|
|
listEl.innerHTML = "";
|
|
|
listEl.classList.add("d-none");
|
|
|
return;
|
|
|
}
|
|
|
listEl.classList.remove("d-none");
|
|
|
listEl.innerHTML = items.map((item) => (
|
|
|
'<div class="supporting-file-item" data-id="' + item.id + '">' +
|
|
|
'<div class="supporting-file-meta">' +
|
|
|
'<span class="supporting-file-name" title="' + escapeHtml(item.file.name) + '">' + escapeHtml(item.file.name) + '</span>' +
|
|
|
'<span class="supporting-file-size">' + formatFileSize(item.file.size) + '</span>' +
|
|
|
'</div>' +
|
|
|
'<div class="supporting-file-actions">' +
|
|
|
'<a class="btn btn-sm btn-outline-primary supporting-file-preview" href="' + item.previewUrl + '" target="_blank" rel="noopener">预览</a>' +
|
|
|
'<button type="button" class="btn btn-sm btn-outline-secondary supporting-file-remove">删除</button>' +
|
|
|
'</div>' +
|
|
|
'</div>'
|
|
|
)).join("");
|
|
|
}
|
|
|
|
|
|
function addFiles(files, target) {
|
|
|
const items = target === "plan" ? planFileItems : supportingFileItems;
|
|
|
const input = target === "plan" ? planFile : supportingFiles;
|
|
|
const savedInfo = target === "plan" ? planFileSavedInfo : supportingFilesSavedInfo;
|
|
|
const listEl = target === "plan" ? planFileList : supportingFileList;
|
|
|
Array.from(files).forEach((file) => {
|
|
|
items.push({
|
|
|
id: String(Date.now()) + "-" + Math.random().toString(16).slice(2),
|
|
|
file,
|
|
|
previewUrl: createPreviewUrl(file)
|
|
|
});
|
|
|
});
|
|
|
input.value = "";
|
|
|
savedInfo.textContent = "";
|
|
|
renderFileList(listEl, items);
|
|
|
if (target === "plan") {
|
|
|
validatePlanFile();
|
|
|
} else {
|
|
|
validateSupportingFiles();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function removeFile(id, target) {
|
|
|
let items = target === "plan" ? planFileItems : supportingFileItems;
|
|
|
const listEl = target === "plan" ? planFileList : supportingFileList;
|
|
|
const item = items.find((entry) => entry.id === id);
|
|
|
if (item) URL.revokeObjectURL(item.previewUrl);
|
|
|
items = items.filter((entry) => entry.id !== id);
|
|
|
if (target === "plan") {
|
|
|
planFileItems = items;
|
|
|
renderFileList(listEl, planFileItems);
|
|
|
validatePlanFile();
|
|
|
} else {
|
|
|
supportingFileItems = items;
|
|
|
renderFileList(listEl, supportingFileItems);
|
|
|
validateSupportingFiles();
|
|
|
}
|
|
|
applyForm.classList.add("was-validated");
|
|
|
}
|
|
|
|
|
|
function validateFileItems(items, input, feedback, missingMessage, optional = false) {
|
|
|
if (!items.length) {
|
|
|
input.setCustomValidity(optional ? "" : "missing");
|
|
|
feedback.textContent = missingMessage;
|
|
|
return optional;
|
|
|
}
|
|
|
const invalidExtFile = items.map((item) => item.file).find((file) => {
|
|
|
const ext = (file.name.split(".").pop() || "").toLowerCase();
|
|
|
return !allowedExt.includes(ext);
|
|
|
});
|
|
|
if (invalidExtFile) {
|
|
|
input.setCustomValidity("ext");
|
|
|
feedback.textContent = "“" + invalidExtFile.name + "”格式不支持,请上传 PDF/PPT/PPTX/DOC/DOCX/WPS/RAR/ZIP";
|
|
|
return false;
|
|
|
}
|
|
|
const oversizedFile = items.map((item) => item.file).find((file) => file.size > maxFileSize);
|
|
|
if (oversizedFile) {
|
|
|
input.setCustomValidity("size");
|
|
|
feedback.textContent = "“" + oversizedFile.name + "”大小不能超过20M";
|
|
|
return false;
|
|
|
}
|
|
|
input.setCustomValidity("");
|
|
|
feedback.textContent = missingMessage;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function validatePlanFile() {
|
|
|
const ok = validateFileItems(planFileItems, planFile, planFileFeedback, "请上传商业计划书");
|
|
|
if (!ok) return false;
|
|
|
const invalid = planFileItems.find((item) => item.file.name.split(".").pop().toLowerCase() !== "pdf");
|
|
|
if (invalid) {
|
|
|
planFile.setCustomValidity("format");
|
|
|
planFileFeedback.textContent = "商业计划书仅支持 PDF 格式";
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function validateSupportingFiles() {
|
|
|
return validateFileItems(supportingFileItems, supportingFiles, supportingFilesFeedback, "文件格式或大小不符合要求", true);
|
|
|
}
|
|
|
|
|
|
function validateForm() {
|
|
|
validatePlanFile();
|
|
|
validateSupportingFiles();
|
|
|
applyForm.classList.add("was-validated");
|
|
|
let ok = applyForm.checkValidity();
|
|
|
if (trackBtn && trackSel) {
|
|
|
trackBtn.classList.toggle("is-invalid", !trackSel.validity.valid);
|
|
|
}
|
|
|
if (!ok) return false;
|
|
|
if (promiseAck.value !== "1") {
|
|
|
showNotice("请先打开参赛承诺书,阅读后手写签名并点击「确认签署」。", "请确认", "warning");
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function showNotice(message, title = "提示", type = "warning", hideActions = false) {
|
|
|
noticeTitle.textContent = title;
|
|
|
noticeBody.textContent = message;
|
|
|
noticeModalFooter.classList.toggle("d-none", hideActions);
|
|
|
noticeModalContent.classList.remove("notice-success", "notice-warning");
|
|
|
const isSuccess = type === "success";
|
|
|
noticeModalContent.classList.add(isSuccess ? "notice-success" : "notice-warning");
|
|
|
noticeIcon.innerHTML = isSuccess
|
|
|
? '<svg viewBox="0 0 24 24" width="36" height="36" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.8"/><path d="M7 12.2l3.1 3.1L17.2 8.8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
|
: '<svg viewBox="0 0 24 24" width="36" height="36" fill="none" aria-hidden="true"><path d="M12 3.6L2.9 19.2a1 1 0 0 0 .86 1.5h16.48a1 1 0 0 0 .86-1.5L12 3.6z" stroke="currentColor" stroke-width="1.8"/><path d="M12 9v5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="16.8" r="1.1" fill="currentColor"/></svg>';
|
|
|
noticeModal.show();
|
|
|
}
|
|
|
|
|
|
function getDraftData() {
|
|
|
return {
|
|
|
playerName: document.getElementById("playerName").value,
|
|
|
projectName: document.getElementById("projectName").value,
|
|
|
companyName: document.getElementById("companyName").value,
|
|
|
email: document.getElementById("email").value,
|
|
|
mobile: document.getElementById("mobile").value,
|
|
|
locationCountry: document.getElementById("locationCountry").value,
|
|
|
locationProvince: document.getElementById("locationProvince").value,
|
|
|
locationCity: document.getElementById("locationCity").value,
|
|
|
locationOverseaCountry: document.getElementById("locationOverseaCountry").value,
|
|
|
track: document.getElementById("track").value,
|
|
|
intro: intro.value,
|
|
|
school: document.getElementById("school").value,
|
|
|
degree: document.getElementById("degree").value,
|
|
|
eventLocation: document.getElementById("eventLocation").value,
|
|
|
teamMembers: document.getElementById("teamMembers").value,
|
|
|
referrer: document.getElementById("referrer").value,
|
|
|
promiseSigned: promiseAck.value === "1",
|
|
|
promiseSignatureDataUrl: promiseSignatureDataUrl,
|
|
|
planFileNames: planFileItems.map((item) => item.file.name),
|
|
|
supportingFileNames: supportingFileItems.map((item) => item.file.name)
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function saveDraft() {
|
|
|
const draft = getDraftData();
|
|
|
localStorage.setItem(storageKey, JSON.stringify(draft));
|
|
|
if (draft.planFileNames.length) {
|
|
|
planFileSavedInfo.textContent = "已保存商业计划书名称:" + draft.planFileNames.join("、");
|
|
|
} else {
|
|
|
planFileSavedInfo.textContent = "";
|
|
|
}
|
|
|
if (draft.supportingFileNames.length) {
|
|
|
supportingFilesSavedInfo.textContent = "已保存其他佐证材料名称:" + draft.supportingFileNames.join("、");
|
|
|
} else {
|
|
|
supportingFilesSavedInfo.textContent = "";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function restoreDraft() {
|
|
|
const raw = localStorage.getItem(storageKey);
|
|
|
if (!raw && !isDetailMode) return;
|
|
|
try {
|
|
|
const draft = raw ? { ...detailDefaults, ...JSON.parse(raw) } : detailDefaults;
|
|
|
document.getElementById("playerName").value = isDetailMode ? (draft.playerName || "") : "";
|
|
|
document.getElementById("projectName").value = draft.projectName || "";
|
|
|
document.getElementById("companyName").value = draft.companyName || "";
|
|
|
document.getElementById("email").value = draft.email || "";
|
|
|
document.getElementById("mobile").value = draft.mobile || "13800138000";
|
|
|
document.getElementById("locationCountry").value = draft.locationCountry || "";
|
|
|
syncLocationFields();
|
|
|
document.getElementById("locationProvince").value = draft.locationProvince || "";
|
|
|
fillLocationCities(draft.locationProvince || "", draft.locationCity || "");
|
|
|
document.getElementById("locationCity").value = draft.locationCity || "";
|
|
|
document.getElementById("locationOverseaCountry").value = draft.locationOverseaCountry || "";
|
|
|
document.getElementById("track").value = draft.track || "";
|
|
|
syncTrackField();
|
|
|
intro.value = draft.intro || "";
|
|
|
document.getElementById("school").value = draft.school || "";
|
|
|
document.getElementById("degree").value = draft.degree || "";
|
|
|
document.getElementById("eventLocation").value = draft.eventLocation || "";
|
|
|
document.getElementById("teamMembers").value = draft.teamMembers || "";
|
|
|
document.getElementById("referrer").value = draft.referrer || "";
|
|
|
refreshCount();
|
|
|
const restoredPlanFileNames = draft.planFileNames || (draft.planFileName ? [draft.planFileName] : []);
|
|
|
if (restoredPlanFileNames.length) {
|
|
|
if (isDetailMode) {
|
|
|
renderSavedDocuments(planFileList, restoredPlanFileNames);
|
|
|
planFileSavedInfo.textContent = "点击文件名可直接查看完整文档内容";
|
|
|
} else {
|
|
|
planFileSavedInfo.textContent = "上次保存商业计划书名称:" + restoredPlanFileNames.join("、") + "(浏览器限制,需重新选择后预览或删除)";
|
|
|
}
|
|
|
}
|
|
|
if (draft.supportingFileNames && draft.supportingFileNames.length) {
|
|
|
if (isDetailMode) {
|
|
|
renderSavedDocuments(supportingFileList, draft.supportingFileNames);
|
|
|
supportingFilesSavedInfo.textContent = "材料已通过系统安全检查";
|
|
|
} else {
|
|
|
supportingFilesSavedInfo.textContent = "上次保存其他佐证材料名称:" + draft.supportingFileNames.join("、") + "(浏览器限制,需重新选择后预览或删除)";
|
|
|
}
|
|
|
}
|
|
|
if (draft.promiseSigned || draft.promiseChecked) {
|
|
|
promiseSignatureDataUrl = draft.promiseSignatureDataUrl || "";
|
|
|
promiseAck.value = "1";
|
|
|
updatePromiseSignedUI();
|
|
|
}
|
|
|
} catch (err) {
|
|
|
localStorage.removeItem(storageKey);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
document.getElementById("saveBtn").addEventListener("click", () => {
|
|
|
saveDraft();
|
|
|
showNotice(isDetailMode ? "已保存项目信息" : "已保存草稿(按当前已填写信息暂存)", "保存成功", "success", true);
|
|
|
});
|
|
|
|
|
|
document.getElementById("submitBtn").addEventListener("click", () => {
|
|
|
if (!validateForm()) return;
|
|
|
saveDraft();
|
|
|
if (promiseSignatureDataUrl) {
|
|
|
localStorage.setItem("jscc_submitted_signature_" + submissionId, promiseSignatureDataUrl);
|
|
|
}
|
|
|
showNotice(isDetailMode ? "已重新提交报名材料,请等待评审。" : "已提交报名(原型演示)", isDetailMode ? "重新提交成功" : "提交成功", "success");
|
|
|
});
|
|
|
|
|
|
planFile.addEventListener("change", () => {
|
|
|
addFiles(planFile.files, "plan");
|
|
|
applyForm.classList.add("was-validated");
|
|
|
});
|
|
|
|
|
|
supportingFiles.addEventListener("change", () => {
|
|
|
addFiles(supportingFiles.files, "supporting");
|
|
|
applyForm.classList.add("was-validated");
|
|
|
});
|
|
|
|
|
|
planFileList.addEventListener("click", (ev) => {
|
|
|
const removeBtn = ev.target.closest(".supporting-file-remove");
|
|
|
if (!removeBtn) return;
|
|
|
const item = removeBtn.closest(".supporting-file-item");
|
|
|
if (!item) return;
|
|
|
removeFile(item.getAttribute("data-id"), "plan");
|
|
|
});
|
|
|
|
|
|
supportingFileList.addEventListener("click", (ev) => {
|
|
|
const previewBtn = ev.target.closest("[data-preview-name]");
|
|
|
if (previewBtn) {
|
|
|
openDocumentPreview(previewBtn.getAttribute("data-preview-name"));
|
|
|
return;
|
|
|
}
|
|
|
const removeBtn = ev.target.closest(".supporting-file-remove");
|
|
|
if (!removeBtn) return;
|
|
|
const item = removeBtn.closest(".supporting-file-item");
|
|
|
if (!item) return;
|
|
|
removeFile(item.getAttribute("data-id"), "supporting");
|
|
|
});
|
|
|
|
|
|
planFileList.addEventListener("click", (ev) => {
|
|
|
const previewBtn = ev.target.closest("[data-preview-name]");
|
|
|
if (previewBtn) openDocumentPreview(previewBtn.getAttribute("data-preview-name"));
|
|
|
});
|
|
|
|
|
|
document.getElementById("documentPrevPage").addEventListener("click", () => {
|
|
|
if (previewPage > 1) { previewPage -= 1; renderPreviewPage(); }
|
|
|
});
|
|
|
document.getElementById("documentNextPage").addEventListener("click", () => {
|
|
|
if (previewPage < previewPages.length) { previewPage += 1; renderPreviewPage(); }
|
|
|
});
|
|
|
document.getElementById("documentZoomOut").addEventListener("click", () => {
|
|
|
previewZoom = Math.max(0.7, previewZoom - 0.1); renderPreviewPage();
|
|
|
});
|
|
|
document.getElementById("documentZoomIn").addEventListener("click", () => {
|
|
|
previewZoom = Math.min(1.4, previewZoom + 0.1); renderPreviewPage();
|
|
|
});
|
|
|
document.getElementById("documentDownloadBtn").addEventListener("click", () => {
|
|
|
const content = documentPaper.innerText;
|
|
|
const blob = new Blob([previewFileName + "\n\n" + content], { type: "text/plain;charset=utf-8" });
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const a = document.createElement("a");
|
|
|
a.href = url;
|
|
|
a.download = previewFileName.replace(/\.[^.]+$/, "") + "-预览内容.txt";
|
|
|
a.click();
|
|
|
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
|
});
|
|
|
|
|
|
restoreDraft();
|
|
|
syncLocationFields();
|
|
|
syncTrackField();
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|