const APP_BRAND_NAME = "S-lake高校雷达网"; const state = { route: "home", params: {}, user: JSON.parse(localStorage.getItem("slakeUser") || "null"), profile: JSON.parse(localStorage.getItem("slakeProfile") || "{}"), signedCourseIds: JSON.parse(localStorage.getItem("slakeSignedCourses") || "[1,2]"), signedActivityIds: JSON.parse(localStorage.getItem("slakeSignedActivities") || "[1]"), calendarMonth: "2026-05", activityCalendarMonth: "2026-05", selectedDate: "2026-05-15", signupSessionId: null, demands: [ { id: 1, type: "技术合作", title: "寻找工业视觉缺陷检测场景", status: "跟进中", date: "2026-05-18", description: "希望对接具备真实产线数据的企业,共同验证实验室算法。" }, { id: 2, type: "产业资源", title: "先进材料中试资源对接", status: "已受理", date: "2026-05-14", description: "需要寻找小批量中试平台,验证材料稳定性和制备工艺。" } ] }; const data = { banner: { title: "连接高校科研与产业资源", subtitle: "课程、活动、资讯与伙伴需求一站触达" }, courses: [ { id: 5, title: "高校科技成果转化实战营", category: "成果转化", status: "进行中", date: "2026-05-25", time: "09:30-17:30", location: "苏州工业园区 · 科创中心", teacher: "林清源 老师", university: "S-lake 先进技术发展中心", seats: 80, signed: 72, signupStartDate: "2026-05-01", signupEndDate: "2026-05-24", summary: "围绕高校成果筛选、产业需求拆解和项目路演,进行全天实战训练。", audience: ["高校科研团队负责人", "科技成果转化经理人", "产业投资与孵化服务人员"], agenda: ["成果筛选方法", "产业需求访谈", "路演材料打磨"] }, { id: 1, title: "AI 科研成果转化闭门课", category: "AI", status: "报名中", date: "2026-06-12", endDate: "2026-06-14", time: "14:00-17:00", location: "苏州工业园区 · 元禾会议中心", teacher: "陈知远 教授", university: "上海交通大学", seats: 48, signed: 31, signupStartDate: "2026-05-01", signupEndDate: "2026-06-11", promoUrl: "assets/course-detail-banner.svg", summary: "面向高校科研团队和产业伙伴,拆解 AI 技术从论文、样机到商业落地的关键路径。", audience: ["AI 方向高校老师与博士后团队", "关注 AI 项目落地的企业研发负责人", "科技成果转化与投资机构代表"], agenda: ["成果转化案例复盘", "产业需求匹配", "闭门交流"] }, { id: 2, title: "先进材料项目投融资工作坊", category: "材料", status: "报名中", date: "2026-06-20", endDate: "2026-06-21", time: "09:30-12:00", location: "南京大学苏州校区", teacher: "李明澈 教授", university: "南京大学", seats: 36, signed: 18, signupStartDate: "2026-05-10", signupEndDate: "2026-06-19", summary: "聚焦新材料项目早期验证、知识产权梳理和资本沟通材料准备。", audience: ["先进材料科研团队", "材料项目创业者", "关注硬科技投资的产业方与基金代表"], agenda: ["技术路线表达", "专利与样品验证", "融资路演点评"] }, { id: 3, title: "机器人感知技术应用沙龙", category: "机器人", status: "未开始", date: "2026-07-05", time: "19:00-21:00", location: "杭州未来科技城 · 智能制造中心", teacher: "王亦然 博士", university: "浙江大学", seats: 100, signed: 100, signupStartDate: "2026-05-01", signupEndDate: "2026-07-04", summary: "邀请高校实验室与企业研发负责人交流多模态感知、边缘计算和场景验证。", audience: ["机器人与智能制造方向科研人员", "企业算法、硬件与产品负责人", "关注机器人场景验证的产业伙伴"], agenda: ["前沿论文导读", "企业应用分享", "自由问答"] }, { id: 4, title: "高校成果转化基础训练营", category: "成果转化", status: "已结束", date: "2026-05-15", time: "10:00-12:00", location: "苏州工业园区 · 路演厅", teacher: "周行之 老师", university: "S-lake 先进技术发展中心", seats: 60, signed: 56, signupStartDate: "2026-04-01", signupEndDate: "2026-05-14", summary: "面向初次参与成果转化的科研团队,讲解需求识别、商业化路径和对接材料准备。", audience: ["首次参与成果转化的科研团队", "高校院系科研秘书与项目经理", "早期科技创业团队"], agenda: ["成果转化流程", "报名材料准备", "案例答疑"] }, { id: 6, title: "生物医药转化专题课", category: "生物医药", status: "报名中", date: "2026-06-24", time: "14:00-16:30", location: "上海张江科学会堂", teacher: "赵闻笛 教授", university: "复旦大学", seats: 50, signed: 22, signupStartDate: "2026-05-01", signupEndDate: "2026-06-23", summary: "围绕生物医药项目临床前验证、产业合作和转化路径展开专题分享。", audience: ["生物医药科研团队", "医疗器械与药企研发负责人", "关注生命科学项目的投资机构"], agenda: ["临床前验证", "产业合作路径", "转化案例讨论"] } ], activities: [ { id: 1, title: "长三角高校科技成果对接日", date: "2026-06-18", time: "13:30-18:00", location: "苏州国际科技园", status: "报名中", signupStartDate: "2026-05-01", signupEndDate: "2026-06-17", summary: "组织高校团队、投资机构、产业方集中对接,推动技术需求和科研成果精准匹配。", sessions: [ { id: 101, title: "上午对接专场", date: "2026-06-18", time: "13:30-16:00", venue: "苏州国际科技园 A厅", capacity: 80, signed: 72 }, { id: 102, title: "下午路演专场", date: "2026-06-18", time: "16:00-18:00", venue: "苏州国际科技园 B厅", capacity: 60, signed: 60 } ] }, { id: 2, title: "青年科学家伙伴交流晚宴", date: "2026-06-28", time: "18:30-20:30", location: "苏州金鸡湖会议中心", status: "邀请制", summary: "面向正式伙伴开放的小规模交流活动,促进跨校、跨学科合作。" }, { id: 3, title: "产业需求闭门沟通会", date: "2026-05-12", time: "14:00-16:00", location: "元禾控股会议室", status: "已结束", summary: "围绕企业真实技术需求,与高校老师进行小范围闭门沟通。" }, { id: 4, title: "高校青年 PI 技术路演", date: "2026-06-24", time: "15:00-17:30", location: "苏州工业园区 · 路演中心", status: "报名中", signupStartDate: "2026-05-15", signupEndDate: "2026-06-23", summary: "邀请高校青年 PI 展示前沿技术方向,与产业方、投资机构进行现场交流。", sessions: [ { id: 401, title: "路演上半场", date: "2026-06-24", time: "15:00-16:15", venue: "路演中心 1号厅", capacity: 50, signed: 22 }, { id: 402, title: "路演下半场", date: "2026-06-24", time: "16:15-17:30", venue: "路演中心 1号厅", capacity: 50, signed: 10 } ] } ], news: [ { id: 1, title: "高校雷达网完成首批 AI 方向老师画像更新", tag: "平台动态", date: "2026-05-20", summary: "围绕论文、项目、专利和产业合作意向,平台完成长三角首批重点老师画像更新。", content: "高校雷达网近期完成 AI 方向首批重点老师画像更新,覆盖多模态大模型、具身智能、智能制造等方向。后续平台将继续补充材料、生物医药和机器人方向数据。" }, { id: 2, title: "元禾先进技术发展中心启动伙伴需求共创机制", tag: "伙伴服务", date: "2026-05-16", summary: "正式伙伴可通过移动端发布技术、产业、资金、场地等类型需求。", content: "为提升高校科研团队和产业资源的连接效率,平台面向正式伙伴开放需求发布入口。需求提交后,运营团队将在后台进行分派和跟进。" } ] }; const app = document.querySelector("#app"); const title = document.querySelector("#pageTitle"); const tabbar = document.querySelector("#tabbar"); const topAction = document.querySelector("#topAction"); const backBtn = document.querySelector("#backBtn"); const courseSignupModal = document.querySelector("#courseSignupModal"); const signupSuccessModal = document.querySelector("#signupSuccessModal"); const signupSuccessTitle = document.querySelector("#signupSuccessTitle"); const signupSuccessMessage = document.querySelector("#signupSuccessMessage"); const signupModalTitle = document.querySelector("#signupModalTitle"); const signupCourseTitle = document.querySelector("#signupCourseTitle"); const signupCourseId = document.querySelector("#signupCourseId"); const signupType = document.querySelector("#signupType"); const signupPhoneError = document.querySelector("#signupPhoneError"); const demoToday = "2026-05-25"; const minCalendarMonth = "2026-05"; function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function go(route, params = {}) { state.route = route; state.params = params; history.pushState({ route, params }, "", `#${route}${params.id ? `/${params.id}` : ""}`); render(); } function replaceRoute(route, params = {}) { state.route = route; state.params = params; history.replaceState({ route, params }, "", `#${route}${params.id ? `/${params.id}` : ""}`); render(); } function findById(list, id) { return list.find((item) => String(item.id) === String(id)); } function toast(message) { openSignupSuccess(message, "操作成功"); } function profileAvatarSrc() { return state.profile?.avatar || "assets/profile-avatar.jpg"; } function openSignupSuccess(message = "已为你同步到报名日程", modalTitle = "报名成功") { signupSuccessTitle.textContent = modalTitle; signupSuccessMessage.textContent = message; signupSuccessModal.classList.add("visible"); signupSuccessModal.setAttribute("aria-hidden", "false"); } function closeSignupSuccess() { signupSuccessModal.classList.remove("visible"); signupSuccessModal.setAttribute("aria-hidden", "true"); } function parseDateOnly(dateText) { return new Date(`${dateText}T00:00:00`); } function isWithinSignupWindow(startText, endText, todayText = demoToday) { const today = parseDateOnly(todayText); if (startText && today < parseDateOnly(startText)) return false; if (endText && today > parseDateOnly(endText)) return false; return true; } function hasCourseCapacity(course) { const capacity = Number(course.seats) || 0; if (capacity <= 0) return true; return Number(course.signed) < capacity; } function isCourseSignupOpen(course) { if (!isWithinSignupWindow(course.signupStartDate, course.signupEndDate)) return false; if (!hasCourseCapacity(course)) return false; if (getCourseStatus(course) === "已结束") return false; return true; } function canShowCourseSignupButton(course) { return isCourseSignupOpen(course) && !state.signedCourseIds.includes(course.id); } function getActivityDisplayStatus(activity) { if (isWithinSignupWindow(activity.signupStartDate, activity.signupEndDate)) return "报名中"; return getActivityStatus(activity); } function isActivitySignupOpen(activity) { if (!isWithinSignupWindow(activity.signupStartDate, activity.signupEndDate)) return false; if (getActivityDisplayStatus(activity) !== "报名中") return false; return Array.isArray(activity.sessions) && activity.sessions.some((session) => hasSessionCapacity(session)); } function hasSessionCapacity(session) { const capacity = Number(session.capacity) || 0; if (capacity <= 0) return true; return Number(session.signed) < capacity; } function canShowActivitySignupButton(activity) { return isActivitySignupOpen(activity) && !state.signedActivityIds.includes(activity.id); } function openCourseSignup(id) { const course = findById(data.courses, id); if (!course || !canShowCourseSignupButton(course)) return; signupCourseId.value = id; signupType.value = "course"; state.signupSessionId = null; signupModalTitle.textContent = "课程报名"; signupCourseTitle.textContent = course.title; signupPhoneError.textContent = ""; hideSignupSessionBlock(); courseSignupModal.classList.add("visible"); courseSignupModal.setAttribute("aria-hidden", "false"); } function openActivitySignup(id) { const activity = findById(data.activities, id); if (!activity || !canShowActivitySignupButton(activity)) return; signupCourseId.value = id; signupType.value = "activity"; signupModalTitle.textContent = "活动报名"; signupCourseTitle.textContent = activity.title; signupPhoneError.textContent = ""; renderSignupSessionOptions(activity); state.signupSessionId = null; courseSignupModal.classList.add("visible"); courseSignupModal.setAttribute("aria-hidden", "false"); } function hideSignupSessionBlock() { const block = document.querySelector("#signupSessionBlock"); if (block) block.hidden = true; const err = document.querySelector("#signupSessionError"); if (err) err.textContent = ""; } function renderSignupSessionOptions(activity) { const block = document.querySelector("#signupSessionBlock"); const list = document.querySelector("#signupSessionOptions"); if (!block || !list) return; const sessions = activity.sessions || []; list.innerHTML = sessions.map((session) => { const full = !hasSessionCapacity(session); const active = state.signupSessionId === session.id; return ` `; }).join(""); block.hidden = sessions.length === 0; } function closeCourseSignup() { courseSignupModal.classList.remove("visible"); courseSignupModal.setAttribute("aria-hidden", "true"); signupPhoneError.textContent = ""; state.signupSessionId = null; hideSignupSessionBlock(); document.querySelector("#courseSignupForm").reset(); } function isValidMobile(phone) { return /^1[3-9]\d{9}$/.test(phone); } function tag(text, type = "") { return `${escapeHtml(text)}`; } function persistSignup(type) { if (type === "course") { localStorage.setItem("slakeSignedCourses", JSON.stringify(state.signedCourseIds)); } else { localStorage.setItem("slakeSignedActivities", JSON.stringify(state.signedActivityIds)); } } function getStatusByDate(dateText) { if (dateText === demoToday) return "进行中"; if (dateText < demoToday) return "已结束"; return "未开始"; } function getCourseEndDate(course) { return course.endDate || course.date; } function getActivityEndDate(activity) { return activity.endDate || activity.date; } function isCourseOnDate(course, dateText) { return course.date <= dateText && dateText <= getCourseEndDate(course); } function isActivityOnDate(activity, dateText) { return activity.date <= dateText && dateText <= getActivityEndDate(activity); } function daysBetween(startText, endText) { const start = new Date(`${startText}T00:00:00`); const end = new Date(`${endText}T00:00:00`); return Math.round((end - start) / 86400000); } function getCourseStatus(course) { if (course.date <= demoToday && demoToday <= getCourseEndDate(course)) return "进行中"; if (getCourseEndDate(course) < demoToday) return "已结束"; return "未开始"; } function getActivityStatus(activity) { if (activity.date <= demoToday && demoToday <= getActivityEndDate(activity)) return "进行中"; if (getActivityEndDate(activity) < demoToday) return "已结束"; return "未开始"; } function statusTag(status) { const map = { "未开始": "blue", "进行中": "green", "已结束": "" }; return tag(status, map[status] || ""); } function statusClass(status) { const map = { "未开始": "pending", "进行中": "active", "已结束": "done" }; return map[status] || "pending"; } function coverClass(category) { if (category.includes("AI")) return "cover-ai"; if (category.includes("材料")) return "cover-material"; if (category.includes("机器人")) return "cover-robot"; return "cover-transfer"; } function sortByCourseTime(courses) { return [...courses].sort((a, b) => { const aTime = new Date(`${a.date} ${a.time.split("-")[0]}`.replace(/-/g, "/")).getTime(); const bTime = new Date(`${b.date} ${b.time.split("-")[0]}`.replace(/-/g, "/")).getTime(); return aTime - bTime; }); } function sortByStatusThenTime(courses) { const order = { "进行中": 0, "未开始": 1, "已结束": 2 }; return [...courses].sort((a, b) => { const statusDelta = order[getCourseStatus(a)] - order[getCourseStatus(b)]; if (statusDelta) return statusDelta; const aTime = new Date(`${a.date} ${a.time.split("-")[0]}`.replace(/-/g, "/")).getTime(); const bTime = new Date(`${b.date} ${b.time.split("-")[0]}`.replace(/-/g, "/")).getTime(); return aTime - bTime; }); } function renderHome() { const availableCourses = data.courses.filter((course) => getCourseStatus(course) !== "已结束"); const hotCourses = sortByCourseTime(availableCourses).slice(0, 3).map(courseCard).join(""); const latestCourses = sortByCourseTime(availableCourses).slice(0, 3).map(courseCard).join(""); return `
09:11
${APP_BRAND_NAME}
${serviceItem("course", "课程列表", "", "courses")} ${serviceItem("activity", "活动列表", "", "activities")} ${serviceItem("calendar", "课程日历", "", "calendar")}

热门课程

${hotCourses}

最新课程

${latestCourses}
`; } function renderCalendarCells() { const [year, month] = state.calendarMonth.split("-").map(Number); const first = new Date(year, month - 1, 1); const start = new Date(year, month - 1, 1 - first.getDay()); const cells = Array.from({ length: 35 }, (_, index) => { const date = new Date(start); date.setDate(start.getDate() + index); const dateText = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; return { day: String(date.getDate()), dateText, muted: date.getMonth() !== month - 1, past: dateText < demoToday, courses: data.courses.filter((item) => isCourseOnDate(item, dateText)) }; }); return cells.map((cell, index) => { const weekStart = cells[Math.floor(index / 7) * 7].dateText; const weekEnd = cells[Math.floor(index / 7) * 7 + 6].dateText; const event = cell.courses.map((course) => { const visibleStart = course.date > weekStart ? course.date : weekStart; if (cell.dateText !== visibleStart) return ""; const visibleEnd = getCourseEndDate(course) < weekEnd ? getCourseEndDate(course) : weekEnd; const spanDays = daysBetween(visibleStart, visibleEnd) + 1; return `
${escapeHtml(course.title)}
`; }).join(""); const hasCourse = cell.courses.length > 0; const signedMark = cell.courses.some((course) => state.signedCourseIds.includes(course.id)) ? `` : ""; return ` `; }).join(""); } function renderCourseCalendarBlock() { const isMinMonth = state.calendarMonth <= minCalendarMonth; return `
${escapeHtml(state.calendarMonth.replace("-", "年"))}月
未开始 进行中 已结束
${renderCalendarCells()}
`; } function renderActivityCalendarCells() { const [year, month] = state.activityCalendarMonth.split("-").map(Number); const first = new Date(year, month - 1, 1); const start = new Date(year, month - 1, 1 - first.getDay()); const cells = Array.from({ length: 35 }, (_, index) => { const date = new Date(start); date.setDate(start.getDate() + index); const dateText = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; return { day: String(date.getDate()), dateText, muted: date.getMonth() !== month - 1, past: dateText < demoToday, activities: data.activities.filter((item) => isActivityOnDate(item, dateText)) }; }); return cells.map((cell, index) => { const weekStart = cells[Math.floor(index / 7) * 7].dateText; const weekEnd = cells[Math.floor(index / 7) * 7 + 6].dateText; const hasActivity = cell.activities.length > 0; const event = cell.activities.map((activity) => { const visibleStart = activity.date > weekStart ? activity.date : weekStart; if (cell.dateText !== visibleStart) return ""; const visibleEnd = getActivityEndDate(activity) < weekEnd ? getActivityEndDate(activity) : weekEnd; const spanDays = daysBetween(visibleStart, visibleEnd) + 1; return `
${escapeHtml(activity.title)}
`; }).join(""); const signedMark = cell.activities.some((activity) => state.signedActivityIds.includes(activity.id)) ? `` : ""; return ` `; }).join(""); } function renderActivityCalendarBlock() { const isMinMonth = state.activityCalendarMonth <= minCalendarMonth; return `
${escapeHtml(state.activityCalendarMonth.replace("-", "年"))}月
未开始 进行中 已结束
${renderActivityCalendarCells()}
`; } function serviceItem(icon, titleText, desc, route) { return ` `; } function serviceIcon(icon) { const icons = { course: ` `, activity: ` `, calendar: ` ` }; return icons[icon] || ""; } function courseCard(item) { const status = getCourseStatus(item); const signed = state.signedCourseIds.includes(item.id); return `
${escapeHtml(item.category)}
${escapeHtml(item.title)}
${statusTag(status)}
${escapeHtml(item.date)} ${escapeHtml(item.time)} ${escapeHtml(item.location)} ${escapeHtml(item.teacher)} · ${escapeHtml(item.university)}
${item.signed}${item.seats}
${canShowCourseSignupButton(item) ? `` : ""}
`; } function renderCourses() { const categories = ["全部", ...new Set(data.courses.map((course) => course.category).filter((category) => category !== "机器人"))]; const active = state.params.category || "全部"; const activeStatus = state.params.courseStatus || ""; const keyword = state.params.keyword || ""; const courses = (active === "全部" ? data.courses : data.courses.filter((course) => course.category === active)) .filter((course) => { const status = getCourseStatus(course); const matchStatus = activeStatus ? status === activeStatus : status !== "已结束"; const matchKeyword = !keyword || `${course.title}${course.teacher}${course.location}`.includes(keyword); return matchStatus && matchKeyword; }); const sortedCourses = sortByStatusThenTime(courses); const statuses = ["进行中", "未开始", "已结束"]; return `
${categories.map((item) => ``).join("")}
${statuses.map((item) => ``).join("")}
${sortedCourses.map(courseCard).join("")}
`; } function renderMyCourses() { const signed = data.courses.filter((item) => state.signedCourseIds.includes(item.id)); const statuses = ["全部", "未开始", "进行中", "已结束"]; const activeStatus = statuses.includes(state.params.myCourseStatus) ? state.params.myCourseStatus : "全部"; const courses = activeStatus === "全部" ? signed : signed.filter((item) => getCourseStatus(item) === activeStatus); return `
${statuses.map((item) => ``).join("")}
${courses.length ? courses.map((item) => `
${escapeHtml(item.category)}
${escapeHtml(item.title)}
${statusTag(getCourseStatus(item))}
${escapeHtml(item.date)} ${escapeHtml(item.time)} ${escapeHtml(item.location)} ${escapeHtml(item.teacher)} · ${escapeHtml(item.university)}
`).join("") : `
暂无${activeStatus === "全部" ? "已报名" : activeStatus}课程
`}
`; } function renderCourseDetailSignupButton(item) { if (state.signedCourseIds.includes(item.id)) { return ``; } if (!isCourseSignupOpen(item)) { return ""; } return ``; } function renderCourseDetail() { const item = findById(data.courses, state.params.id); if (!item) return `
课程不存在
`; const signupButton = renderCourseDetailSignupButton(item); if (item.promoUrl) { return `

${escapeHtml(item.title)}

${escapeHtml(item.title)}
${signupButton}
`; } return `

${escapeHtml(item.title)}

${escapeHtml(item.summary)}

课程安排

${scheduleItem("时间", `${item.date} ${item.time}`)} ${scheduleItem("地点", item.location)} ${scheduleItem("名额", `${item.signed}/${item.seats} 人`)} ${item.signupEndDate ? scheduleItem("报名截止", item.signupEndDate) : ""}

主讲师资

${escapeHtml(item.teacher)}
${escapeHtml(item.university)}
长期关注${escapeHtml(item.category)}方向科研成果转化与产业应用。

招生对象

${(item.audience || []).map((audience) => `
${escapeHtml(audience)}
`).join("")}
${signupButton}
`; } function infoRow(label, value) { return `
${escapeHtml(label)}${escapeHtml(value)}
`; } function scheduleItem(label, value) { return `
${escapeHtml(label)}:${escapeHtml(value)}
`; } function renderActivities() { const categories = ["全部", "路演对接", "伙伴交流", "产业需求"]; const active = state.params.activityCategory || "全部"; const activeStatus = state.params.activityStatus || ""; const keyword = state.params.activityKeyword || ""; const activities = data.activities.filter((item) => { const matchCategory = active === "全部" || item.title.includes(active.replace("交流", "")); const status = getActivityStatus(item); const matchStatus = activeStatus ? status === activeStatus : true; const matchKeyword = !keyword || `${item.title}${item.location}${item.summary}`.includes(keyword); return matchCategory && matchStatus && matchKeyword; }); const statuses = ["未开始", "进行中", "已结束"]; return `
${categories.map((item) => ``).join("")}
${statuses.map((item) => ``).join("")}
${activities.map(activityCard).join("")}
`; } function renderMyActivities() { const signed = data.activities.filter((item) => state.signedActivityIds.includes(item.id)); const statuses = ["全部", "未开始", "进行中", "已结束"]; const activeStatus = statuses.includes(state.params.myActivityStatus) ? state.params.myActivityStatus : "全部"; const activities = activeStatus === "全部" ? signed : signed.filter((item) => getActivityStatus(item) === activeStatus); return `
${statuses.map((item) => ``).join("")}
${activities.length ? activities.map((item) => `
${escapeHtml(item.title)}
${statusTag(getActivityStatus(item))}
${escapeHtml(item.summary)}
${escapeHtml(item.date)} ${escapeHtml(item.time)} · ${escapeHtml(item.location)}
`).join("") : `
暂无${activeStatus === "全部" ? "已报名" : activeStatus}活动
`}
`; } function activityCard(item) { const status = getActivityDisplayStatus(item); const statusTagHtml = status === "报名中" ? tag(status, "brand") : statusTag(status); return `
${statusTagHtml}
${escapeHtml(item.title)}
${escapeHtml(item.summary)}
${escapeHtml(item.date)} ${escapeHtml(item.time)} · ${escapeHtml(item.location)}
${canShowActivitySignupButton(item) ? `` : ""}
`; } function renderActivityDetailSignupButton(item) { if (state.signedActivityIds.includes(item.id)) { return ``; } if (!isActivitySignupOpen(item)) { return ""; } return ``; } function renderActivityDetail() { const item = findById(data.activities, state.params.id); if (!item) return `
活动不存在
`; const status = getActivityDisplayStatus(item); const signupButton = renderActivityDetailSignupButton(item); const sessions = (item.sessions || []).map((session) => { const full = !hasSessionCapacity(session); return `
${escapeHtml(session.title)}
${escapeHtml(session.date)} ${escapeHtml(session.time)} · ${escapeHtml(session.venue)}
${full ? "名额已满" : `剩余 ${Math.max(Number(session.capacity) - Number(session.signed), 0)} / ${session.capacity} 人`}
`; }).join(""); return `
${status === "报名中" ? tag(status, "brand") : statusTag(status)}
${escapeHtml(item.title)}
${escapeHtml(item.summary)}
${infoRow("日期", item.date)} ${infoRow("时间", item.time)} ${infoRow("地点", item.location)} ${item.signupEndDate ? infoRow("报名截止", item.signupEndDate) : ""}
${sessions ? `

活动场次

${sessions}
` : ""} ${signupButton} `; } function newsCard(item) { return `
${escapeHtml(item.tag)} ${escapeHtml(item.title)}
${escapeHtml(item.date)}
`; } function renderDiscover() { const categories = ["全部", "行业动态", "政策信息", "平台动态"]; const active = state.params.newsCategory || "全部"; const news = data.news.filter((item) => active === "全部" || item.tag === active); return `
${categories.map((item) => ``).join("")}
${news.map(newsCard).join("")}
`; } function renderNewsDetail() { const item = findById(data.news, state.params.id); if (!item) return `
资讯不存在
`; return `

${escapeHtml(item.title)}

${escapeHtml(item.date)}

${escapeHtml(item.content)}

`; } function renderCalendar() { return renderCourseCalendarBlock(); } function renderActivityCalendar() { return renderActivityCalendarBlock(); } function renderDemands() { return `

伙伴需求

正式伙伴可提交技术、产业、资金等协作需求
${state.demands.length ? `
${state.demands.map(demandCard).join("")}
` : `
暂无需求
`} `; } function demandCard(item) { return `
${tag(item.type, "brand")}${tag(item.status)}
${escapeHtml(item.title)}
${escapeHtml(item.description)}
${escapeHtml(item.date)}
`; } function renderDemandCreate() { return `
`; } function renderProfile() { const user = state.user || {}; const profile = state.profile || {}; const isPartner = state.user ? Boolean(state.user.isPartner) : true; const displayName = profile.name || user.nickName || "王清萍"; return `
王清萍头像
${escapeHtml(displayName)}
${menuItem("个人资料", "profileEdit", "profile")} ${menuItem("我的课程", "myCourses", "course")} ${menuItem("我的活动", "myActivities", "activity")} ${isPartner ? menuItem("需求发布", "demandCreate", "demand") : ""}
`; } function renderProfileEdit() { const profile = state.profile || {}; return `
`; } function renderAbout() { return `

关于我们

高校雷达网用于连接高校科研人才、课程活动与产业资源,帮助政企与高校团队更高效地完成成果转化对接。

${infoRow("联系电话", "0512-0000 0000")} ${infoRow("联系邮箱", "service@example.com")} ${infoRow("用户协议", "演示版本")} ${infoRow("隐私政策", "演示版本")}
`; } function profileMenuIcon(type) { const icons = { profile: "M7 5.5h10v13H7z M9.4 9h5.2 M9.4 12h5.2 M9.4 15h3.2", course: "M6 6.5c1.8-.9 3.7-.9 5.5 0v11c-1.8-.9-3.7-.9-5.5 0v-11z M12.5 6.5c1.8-.9 3.7-.9 5.5 0v11c-1.8-.9-3.7-.9-5.5 0v-11z", activity: "M7 5.5h10v13H7z M9 4v3 M15 4v3 M7 9h10 M10 12h4 M10 15h3", demand: "M12 5.5l1.8 3.7 4.1.6-3 2.9.7 4.1-3.6-1.9-3.6 1.9.7-4.1-3-2.9 4.1-.6L12 5.5z", star: "M12 5.4l1.9 3.8 4.2.6-3 2.9.7 4.2-3.8-2-3.8 2 .7-4.2-3-2.9 4.2-.6L12 5.4z", contact: "M7 15.5v-3.2a5 5 0 0 1 10 0v3.2 M7 15.5h2v2H7z M15 15.5h2v2h-2z M9 18.5h6" }; return ` `; } function menuItem(label, route, icon = "profile") { return ` `; } function renderLogin() { return `
S
${APP_BRAND_NAME}
连接长三角高校科研人才、课程活动与产业伙伴需求

登录演示

纯 HTML 版本不调用微信授权,点击后写入本地演示身份。
`; } function formatDate(dateText) { const date = new Date(dateText.replace(/-/g, "/")); return `${String(date.getMonth() + 1).padStart(2, "0")}.${String(date.getDate()).padStart(2, "0")}`; } function formatWeekday(dateText) { return ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][new Date(dateText.replace(/-/g, "/")).getDay()]; } const titles = { home: APP_BRAND_NAME, courses: "课程学习", courseDetail: "课程详情", myCourses: "我的课程", activities: "活动参与", activityDetail: "活动详情", myActivities: "我的活动", activityCalendar: "活动日历", discover: "发现", newsDetail: "资讯详情", calendar: "课程日历", demands: "伙伴需求", demandCreate: "发布需求", profile: "我的", profileEdit: "个人资料", about: "关于我们", login: "登录" }; const tabRoutes = ["home", "courses", "activities", "discover", "profile"]; const tabActiveRoute = { courseDetail: "courses", myCourses: "profile", calendar: "courses", activityDetail: "activities", myActivities: "profile", activityCalendar: "activities", newsDetail: "discover", demands: "profile", demandCreate: "profile", profileEdit: "profile", about: "profile", login: "profile" }; function render() { const route = state.route; document.body.dataset.route = route; document.body.classList.toggle("home-route", route === "home"); title.textContent = titles[route] || APP_BRAND_NAME; backBtn.classList.toggle("visible", !tabRoutes.includes(route)); tabbar.style.display = "flex"; topAction.textContent = state.user ? "已登录" : "登录"; const activeTab = tabActiveRoute[route] || route; document.querySelectorAll(".tab-item").forEach((item) => { item.classList.toggle("active", item.dataset.route === activeTab); }); const views = { home: renderHome, courses: renderCourses, courseDetail: renderCourseDetail, myCourses: renderMyCourses, activities: renderActivities, activityDetail: renderActivityDetail, myActivities: renderMyActivities, activityCalendar: renderActivityCalendar, discover: renderDiscover, newsDetail: renderNewsDetail, calendar: renderCalendar, demands: renderDemands, demandCreate: renderDemandCreate, profile: renderProfile, profileEdit: renderProfileEdit, about: renderAbout, login: renderLogin }; app.innerHTML = (views[route] || renderHome)(); app.scrollTop = 0; window.scrollTo({ top: 0, behavior: "auto" }); } function parseHash() { const [route = "home", id] = location.hash.replace(/^#/, "").split("/"); replaceRoute(route || "home", id ? { id } : {}); } document.addEventListener("click", (event) => { const goEl = event.target.closest("[data-go]"); if (goEl) { go(goEl.dataset.go); return; } const signupCourseEl = event.target.closest("[data-signup-course]"); if (signupCourseEl) { event.stopPropagation(); const id = Number(signupCourseEl.dataset.signupCourse); openCourseSignup(id); return; } const sessionEl = event.target.closest("[data-session-id]"); if (sessionEl && !sessionEl.disabled) { state.signupSessionId = Number(sessionEl.dataset.sessionId); const activity = findById(data.activities, Number(signupCourseId.value)); if (activity) renderSignupSessionOptions(activity); const err = document.querySelector("#signupSessionError"); if (err) err.textContent = ""; return; } if (event.target.closest("[data-close-signup]") || event.target === courseSignupModal) { closeCourseSignup(); return; } if (event.target.closest("[data-close-success]") || event.target === signupSuccessModal) { closeSignupSuccess(); return; } const signupActivityEl = event.target.closest("[data-signup-activity]"); if (signupActivityEl) { event.stopPropagation(); const id = Number(signupActivityEl.dataset.signupActivity); openActivitySignup(id); return; } const detailEl = event.target.closest("[data-detail]"); if (detailEl) { const map = { course: "courseDetail", activity: "activityDetail", news: "newsDetail" }; go(map[detailEl.dataset.detail], { id: detailEl.dataset.id }); return; } const categoryEl = event.target.closest("[data-category]"); if (categoryEl) { state.params.category = categoryEl.dataset.category; render(); return; } const courseStatusEl = event.target.closest("[data-course-status]"); if (courseStatusEl) { const status = courseStatusEl.dataset.courseStatus; state.params.courseStatus = state.params.courseStatus === status ? "" : status; render(); return; } const myCourseStatusEl = event.target.closest("[data-my-course-status]"); if (myCourseStatusEl) { state.params.myCourseStatus = myCourseStatusEl.dataset.myCourseStatus; render(); return; } const activityCategoryEl = event.target.closest("[data-activity-category]"); if (activityCategoryEl) { state.params.activityCategory = activityCategoryEl.dataset.activityCategory; render(); return; } const activityStatusEl = event.target.closest("[data-activity-status]"); if (activityStatusEl) { const status = activityStatusEl.dataset.activityStatus; state.params.activityStatus = state.params.activityStatus === status ? "" : status; render(); return; } const myActivityStatusEl = event.target.closest("[data-my-activity-status]"); if (myActivityStatusEl) { state.params.myActivityStatus = myActivityStatusEl.dataset.myActivityStatus; render(); return; } const newsCategoryEl = event.target.closest("[data-news-category]"); if (newsCategoryEl) { state.params.newsCategory = newsCategoryEl.dataset.newsCategory; render(); return; } const monthEl = event.target.closest("[data-month]"); if (monthEl) { const [year, month] = state.calendarMonth.split("-").map(Number); const next = new Date(year, month - 1 + Number(monthEl.dataset.month), 1); const nextMonth = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, "0")}`; if (nextMonth < minCalendarMonth) return; state.calendarMonth = nextMonth; state.selectedDate = `${state.calendarMonth}-01`; render(); return; } const activityMonthEl = event.target.closest("[data-activity-month]"); if (activityMonthEl) { const [year, month] = state.activityCalendarMonth.split("-").map(Number); const next = new Date(year, month - 1 + Number(activityMonthEl.dataset.activityMonth), 1); const nextMonth = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, "0")}`; if (nextMonth < minCalendarMonth) return; state.activityCalendarMonth = nextMonth; render(); return; } const dateEl = event.target.closest("[data-date]"); if (dateEl) { state.selectedDate = dateEl.dataset.date; const dayCourse = data.courses.find((item) => isCourseOnDate(item, state.selectedDate)); if (dayCourse) go("courseDetail", { id: dayCourse.id }); return; } const activityDateEl = event.target.closest("[data-activity-date]"); if (activityDateEl) { const dayActivity = data.activities.find((item) => isActivityOnDate(item, activityDateEl.dataset.activityDate)); if (dayActivity) go("activityDetail", { id: dayActivity.id }); return; } const signupEl = event.target.closest("[data-signup]"); if (signupEl) { openSignupSuccess(signupEl.dataset.signup === "activity" ? "活动已同步到你的活动日程" : "课程已同步到你的课程日程"); return; } const checkinEl = event.target.closest("[data-checkin]"); if (checkinEl) { toast("签到成功"); return; } const authEl = event.target.closest("[data-auth]"); if (authEl) { if (authEl.dataset.auth === "logout") { state.user = null; localStorage.removeItem("slakeUser"); toast("已退出登录"); render(); return; } state.user = { nickName: "高校伙伴", role: "正式伙伴", isPartner: true, openId: "mock-openid", unionId: "mock-unionid", organization: "S-lake 先进技术发展中心" }; localStorage.setItem("slakeUser", JSON.stringify(state.user)); toast("登录成功"); go("profile"); } }); document.addEventListener("input", (event) => { if (event.target.matches("#courseSignupForm [name='phone']")) { signupPhoneError.textContent = ""; } }); document.addEventListener("change", (event) => { if (!event.target.matches("#profileAvatarInput")) return; const [file] = event.target.files || []; if (!file || !file.type.startsWith("image/")) return; const reader = new FileReader(); reader.addEventListener("load", () => { state.profile = { ...(state.profile || {}), avatar: String(reader.result || "") }; const preview = document.querySelector("#profileAvatarPreview"); if (preview) preview.src = state.profile.avatar; }); reader.readAsDataURL(file); }); document.addEventListener("submit", (event) => { event.preventDefault(); const form = new FormData(event.target); if (event.target.id === "courseSearchForm") { state.params.keyword = String(form.get("keyword") || ""); render(); return; } if (event.target.id === "activitySearchForm") { state.params.activityKeyword = String(form.get("keyword") || ""); state.params.activityStatus = state.params.activityStatus || ""; render(); return; } if (event.target.id === "courseSignupForm") { const id = Number(form.get("courseId")); const type = String(form.get("signupType") || "course"); const phone = String(form.get("phone") || "").trim(); const sessionError = document.querySelector("#signupSessionError"); if (type === "activity") { const activity = findById(data.activities, id); if (!activity || !canShowActivitySignupButton(activity)) { if (sessionError) sessionError.textContent = "当前不可报名"; return; } const session = (activity.sessions || []).find((row) => row.id === state.signupSessionId); if (!session) { if (sessionError) sessionError.textContent = "请选择场次"; return; } if (!hasSessionCapacity(session)) { if (sessionError) sessionError.textContent = "该场次名额已满"; return; } if (sessionError) sessionError.textContent = ""; if (phone && !isValidMobile(phone)) { signupPhoneError.textContent = "请输入正确的手机号"; event.target.elements.phone.focus(); return; } signupPhoneError.textContent = ""; session.signed = Number(session.signed) + 1; if (!state.signedActivityIds.includes(id)) state.signedActivityIds.push(id); persistSignup("activity"); closeCourseSignup(); render(); openSignupSuccess("活动已同步到你的活动日程"); return; } const course = findById(data.courses, id); if (!course || !canShowCourseSignupButton(course)) return; if (!isValidMobile(phone)) { signupPhoneError.textContent = "请输入正确的手机号"; event.target.elements.phone.focus(); return; } signupPhoneError.textContent = ""; course.signed = Number(course.signed) + 1; if (!state.signedCourseIds.includes(id)) state.signedCourseIds.push(id); persistSignup("course"); closeCourseSignup(); render(); openSignupSuccess("课程已同步到你的课程日程"); return; } if (event.target.id === "profileForm") { state.profile = { ...(state.profile || {}), name: form.get("name"), company: form.get("company"), title: form.get("title"), research: form.get("research") }; localStorage.setItem("slakeProfile", JSON.stringify(state.profile)); toast("资料已保存"); go("profile"); return; } if (event.target.id !== "demandForm") return; state.demands.unshift({ id: Date.now(), type: form.get("type"), title: form.get("title"), description: form.get("description"), status: "已提交", date: "2026-05-25" }); toast("需求已提交"); go("demands"); }); tabbar.addEventListener("click", (event) => { const item = event.target.closest(".tab-item"); if (item) go(item.dataset.route); }); topAction.addEventListener("click", () => { if (!state.user) go("login"); }); backBtn.addEventListener("click", () => { history.length > 1 ? history.back() : go("home"); }); window.addEventListener("popstate", (event) => { if (event.state) { state.route = event.state.route; state.params = event.state.params || {}; render(); return; } parseHash(); }); parseHash();