-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
247 lines (227 loc) · 12.2 KB
/
Copy pathapp.js
File metadata and controls
247 lines (227 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
const stageData = [
{
code: "01",
title: "新锐起步:先建立可持续的教学实践",
description: "此阶段的重点不是过早冲刺高级成果奖,而是形成清晰的教学问题、可复现的改革行动和连续的学习证据。",
badge: "建议先进入课堂教学竞赛与校级项目",
tasks: ["建立一门代表性课程档案", "记录学生学习证据", "参与校级或院级教学研究"]
},
{
code: "02",
title: "骨干凝练:把稳定实践提炼为教学成果",
description: "已经有连续多年的教学改革积累,当前任务是从大量素材中找出一条清晰的问题链、方法链和证据链。",
badge: "与上海市教学成果奖高度匹配",
tasks: ["回溯至少 4 年的改革历程", "确定成果的核心教学问题", "建立成效与推广证据矩阵"]
},
{
code: "03",
title: "示范引领:让成果从个人经验走向团队共识",
description: "拥有成熟的课程或教学模式,需要通过团队共建、跨学科协同和院校级应用,证明成果具有稳定性与可复制性。",
badge: "适合团队申报与重点成果培育",
tasks: ["组建结构合理的成果团队", "完成跨课程或跨院系应用", "构建同行评价与示范案例"]
},
{
code: "04",
title: "成果辐射:把教学创新变成可共享的公共资源",
description: "成果已经获得认可,进一步的目标是输出课程标准、教学资源、师资培训和可迁移的实施框架,扩大影响范围。",
badge: "进入更高层级成果奖与教学改革平台",
tasks: ["建立对外可用的成果资源库", "形成区域或行业推广网络", "追踪成果应用后的长期影响"]
}
];
const routeData = [
{
number: "01",
label: "DISCOVERY",
title: "从多年教学实践中,找到真正值得申报的主线",
description: "不要从撰写申报书开始。先盘点你解决过的核心教学问题、经历的关键迭代以及已经产生的变化。",
output: "一页成果地图",
evidence: "改革年表、课程档案、关键事件",
tip: "判断标准:用一句话说清“面对什么问题,你改变了什么”。"
},
{
number: "02",
label: "EVIDENCE",
title: "让每一个成效判断,都找得到对应的证据",
description: "按照“问题—行动—变化—影响”建立证据目录,区分过程证据、学习成效证据和示范推广证据。",
output: "成果证据矩阵",
evidence: "学习数据、作品、问卷、评价与应用证明",
tip: "弱证据不会因数量多而变强;优先保留能够直接支撑核心主张的材料。"
},
{
number: "03",
label: "SYNTHESIS",
title: "把分散的做法,凝练为可识别的教学模型",
description: "用简洁稳定的概念组织成果,说明模型的理念、结构、运行机制与适用边界,避免只做工作汇报。",
output: "成果模型图与核心文本",
evidence: "实施流程、代表性课例、创新点对照",
tip: "好的成果名称能够提示其解决方案,而不只是领域和对象。"
},
{
number: "04",
label: "INTERNAL REVIEW",
title: "在校内遴选前,完成一次真实的同行预评审",
description: "从专业价值、成果创新、实践成效和推广价值四个方向反向检验材料,让不熟悉项目的同行也能读懂。",
output: "预评审意见与修订版",
evidence: "同行意见、疑问清单、材料一致性检查",
tip: "邀请一位“不熟悉你项目”的专家阅读,更容易发现隐含前提和表达断层。"
},
{
number: "05",
label: "EXPERT REVIEW",
title: "用统一叙事,让文本、证据与展示相互支持",
description: "申报书、成果报告、附件与答辩应指向同一个核心主张,不要在不同材料中重复制造新概念。",
output: "正式申报包与答辩框架",
evidence: "定稿文件、关键图表、问答演练记录",
tip: "专家能记住的通常不是材料数量,而是一个鲜明的问题和一条可信的证据链。"
},
{
number: "06",
label: "IMPACT",
title: "申报不是终点,把成果继续转化为可共享的资源",
description: "无论评审结果如何,都应继续追踪应用情况、修订实施框架,并把有效做法转化为课程资源、师资培训或教学标准。",
output: "成果推广与迭代计划",
evidence: "应用地图、培训记录、后续效果数据",
tip: "真正的教学成果会在获奖之后继续生长。"
}
];
const stageDetail = document.querySelector("#stageDetail");
const stageLine = document.querySelector("#stageLine");
const stageNodes = [...document.querySelectorAll(".stage-node")];
function renderStage(index) {
const item = stageData[index];
stageDetail.innerHTML = `
<div class="stage-code">${item.code}</div>
<div>
<h3>${item.title}</h3>
<p>${item.description}</p>
<span class="fit-badge">${item.badge}</span>
</div>
<ul>${item.tasks.map((task) => `<li>${task}</li>`).join("")}</ul>
`;
stageNodes.forEach((node, nodeIndex) => {
const selected = nodeIndex === index;
node.classList.toggle("active", selected);
node.setAttribute("aria-selected", String(selected));
});
stageLine.style.width = `${(index / (stageNodes.length - 1)) * 100}%`;
}
stageNodes.forEach((node) => node.addEventListener("click", () => renderStage(Number(node.dataset.stage))));
renderStage(1);
const routeDetail = document.querySelector("#routeDetail");
const routeCounter = document.querySelector("#routeCounter");
const routeSteps = [...document.querySelectorAll(".route-step")];
function renderRoute(index) {
const item = routeData[index];
routeDetail.dataset.number = item.number;
routeDetail.innerHTML = `
<span>${item.label} / STEP ${item.number}</span>
<h3>${item.title}</h3>
<p>${item.description}</p>
<div class="route-detail-grid">
<div><small>THIS STEP OUTPUT</small><strong>${item.output}</strong></div>
<div><small>KEY EVIDENCE</small><strong>${item.evidence}</strong></div>
</div>
<div class="route-tip">路径提示:${item.tip}</div>
`;
routeCounter.textContent = `${String(index + 1).padStart(2, "0")} / 06`;
routeSteps.forEach((step, stepIndex) => {
const selected = stepIndex === index;
step.classList.toggle("active", selected);
step.setAttribute("aria-selected", String(selected));
});
}
routeSteps.forEach((step) => step.addEventListener("click", () => renderRoute(Number(step.dataset.step))));
renderRoute(0);
const eligibilityChecks = [...document.querySelectorAll(".eligibility-check")];
const eligibilityResult = document.querySelector("#eligibilityResult");
const eligibilityCopy = [
["请完成上方三项自检", "我们会根据你已具备的条件,给出下一步准备建议。"],
["建议先完成成果培育", "你已有一项基础,现在更适合补足长期实践、效果证据或推广应用。"],
["已具备申报准备的主要基础", "可进入成果盘点与证据建档,同时请学校管理部门进行资格预审。"],
["建议正式进入申报路径", "你的实践、证据与推广基础较完整,下一步是组织成果主线与申报团队。"]
];
function updateEligibility() {
const score = eligibilityChecks.filter((item) => item.checked).length;
eligibilityResult.querySelector(".result-score").innerHTML = `${score}<small>/3</small>`;
eligibilityResult.querySelector("strong").textContent = eligibilityCopy[score][0];
eligibilityResult.querySelector("p").textContent = eligibilityCopy[score][1];
}
eligibilityChecks.forEach((check) => check.addEventListener("change", updateEligibility));
const materialChecks = [...document.querySelectorAll(".material-check")];
const completionRing = document.querySelector("#completionRing");
const completionNumber = document.querySelector("#completionNumber");
function updateMaterials() {
const complete = materialChecks.filter((item) => item.checked).length;
const percent = Math.round((complete / materialChecks.length) * 100);
completionNumber.textContent = `${percent}%`;
completionRing.style.background = `conic-gradient(var(--red) ${percent}%, var(--paper-deep) ${percent}%)`;
localStorage.setItem("faculty-material-progress", JSON.stringify(materialChecks.map((item) => item.checked)));
}
try {
const stored = JSON.parse(localStorage.getItem("faculty-material-progress") || "[]");
materialChecks.forEach((check, index) => { check.checked = Boolean(stored[index]); });
} catch { /* Use a clean checklist if local storage contains invalid data. */ }
materialChecks.forEach((check) => check.addEventListener("change", updateMaterials));
document.querySelector("#resetMaterials").addEventListener("click", () => {
materialChecks.forEach((check) => { check.checked = false; });
updateMaterials();
});
updateMaterials();
document.querySelectorAll("[data-scroll]").forEach((button) => {
button.addEventListener("click", () => document.querySelector(button.dataset.scroll)?.scrollIntoView({ behavior: "smooth" }));
});
const chapterLinks = [...document.querySelectorAll(".chapter-rail a")];
const chapters = [...document.querySelectorAll(".chapter")];
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) entry.target.classList.add("visible");
});
}, { threshold: 0.12 });
document.querySelectorAll(".reveal").forEach((item) => revealObserver.observe(item));
const chapterObserver = new IntersectionObserver((entries) => {
const visible = entries.filter((entry) => entry.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
if (!visible) return;
chapterLinks.forEach((link) => link.classList.toggle("active", link.getAttribute("href") === `#${visible.target.id}`));
}, { rootMargin: "-25% 0px -55%", threshold: [0.05, 0.25, 0.5] });
chapters.forEach((chapter) => chapterObserver.observe(chapter));
window.addEventListener("scroll", () => {
const scrollable = document.documentElement.scrollHeight - window.innerHeight;
const percent = scrollable > 0 ? (window.scrollY / scrollable) * 100 : 0;
document.querySelector("#scrollProgress").style.width = `${percent}%`;
document.querySelector("#chapterProgress").style.width = `${percent}%`;
}, { passive: true });
const assistantPanel = document.querySelector("#assistantPanel");
const assistantTrigger = document.querySelector("#assistantTrigger");
const assistantReply = document.querySelector("#assistantReply");
function setAssistant(open) {
assistantPanel.classList.toggle("open", open);
assistantPanel.setAttribute("aria-hidden", String(!open));
assistantTrigger.setAttribute("aria-expanded", String(open));
}
assistantTrigger.addEventListener("click", () => setAssistant(!assistantPanel.classList.contains("open")));
document.querySelector("#assistantClose").addEventListener("click", () => setAssistant(false));
const guideReplies = {
stage: ["先从“发展定位”的四个阶段中选择最接近你现状的一项。不必只根据教龄,更要看你当前是在累积实践、凝练成果还是扩大影响。", "#orientation"],
route: ["从“成果盘点”开始,先不写申报书。用一页纸列出核心问题、四年改革轨迹和已有证据,你就会看到成果主线。", "#route"],
material: ["请直接进入“证据工程”,逐项勾选。进度会保存在你的浏览器中,下次打开仍然保留。", "#materials"]
};
document.querySelectorAll("[data-guide]").forEach((button) => {
button.addEventListener("click", () => {
const [reply, selector] = guideReplies[button.dataset.guide];
assistantReply.textContent = reply;
setTimeout(() => document.querySelector(selector)?.scrollIntoView({ behavior: "smooth" }), 280);
});
});
const toast = document.querySelector("#toast");
let toastTimer;
document.querySelectorAll("[data-toast]").forEach((button) => {
button.addEventListener("click", () => {
toast.textContent = button.dataset.toast;
toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove("show"), 2600);
});
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") setAssistant(false);
});