|
11 | 11 |
|
12 | 12 | let currentLang = 'zh'; |
13 | 13 |
|
| 14 | + // 研究方向支柱(首页与研究页共用) |
| 15 | + const PILLARS = [ |
| 16 | + { key: 'reasoning', titleKey: 'pillar.reasoning.title', descKey: 'pillar.reasoning.desc' }, |
| 17 | + { key: 'multimodal', titleKey: 'pillar.multimodal.title', descKey: 'pillar.multimodal.desc' }, |
| 18 | + { key: 'embodied', titleKey: 'pillar.embodied.title', descKey: 'pillar.embodied.desc' }, |
| 19 | + { key: 'infra', titleKey: 'pillar.infra.title', descKey: 'pillar.infra.desc' }, |
| 20 | + { key: 'arch', titleKey: 'pillar.arch.title', descKey: 'pillar.arch.desc' }, |
| 21 | + { key: 'safety', titleKey: 'pillar.safety.title', descKey: 'pillar.safety.desc' } |
| 22 | + ]; |
| 23 | + |
14 | 24 | // 翻译辅助函数 |
15 | 25 | function t(key) { |
16 | 26 | return window.t ? window.t(key, currentLang) : key; |
|
174 | 184 |
|
175 | 185 | // 处理特定页面的锚点跳转 |
176 | 186 | if (route === 'positions' && params.section) { |
177 | | - setTimeout(() => { |
178 | | - const target = document.getElementById(params.section); |
179 | | - if (target) { |
180 | | - const offset = 120; // 导航栏高度 + 额外间距 |
181 | | - const bodyRect = document.body.getBoundingClientRect().top; |
182 | | - const elementRect = target.getBoundingClientRect().top; |
183 | | - const elementPosition = elementRect - bodyRect; |
184 | | - const offsetPosition = elementPosition - offset; |
185 | | - window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); |
186 | | - } |
187 | | - }, 50); |
| 187 | + setTimeout(() => scrollToId(params.section, 120), 50); |
188 | 188 | } |
189 | 189 |
|
190 | 190 | // 处理研究方向页面的滚动到最新亮点 |
191 | 191 | if (route === 'research' && params.scroll === 'highlights') { |
192 | | - setTimeout(() => { |
193 | | - const target = document.getElementById('research-highlights'); |
194 | | - if (target) { |
195 | | - const offset = 100; // 导航栏高度 + 额外间距 |
196 | | - const bodyRect = document.body.getBoundingClientRect().top; |
197 | | - const elementRect = target.getBoundingClientRect().top; |
198 | | - const elementPosition = elementRect - bodyRect; |
199 | | - const offsetPosition = elementPosition - offset; |
200 | | - window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); |
201 | | - } |
202 | | - }, 50); |
| 192 | + setTimeout(() => scrollToId('research-highlights', 100), 50); |
203 | 193 | } |
204 | 194 | } |
205 | 195 |
|
206 | | - // 滚动到指定区域的函数(用于按钮点击) |
207 | | - function scrollToSection(sectionId) { |
208 | | - const target = document.getElementById(sectionId); |
209 | | - if (target) { |
210 | | - const offset = 120; // 导航栏高度 + 额外间距 |
211 | | - const bodyRect = document.body.getBoundingClientRect().top; |
212 | | - const elementRect = target.getBoundingClientRect().top; |
213 | | - const elementPosition = elementRect - bodyRect; |
214 | | - const offsetPosition = elementPosition - offset; |
215 | | - window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); |
216 | | - } |
| 196 | + // 平滑滚动到指定元素(统一处理导航栏偏移,默认 100) |
| 197 | + function scrollToId(id, offset = 100) { |
| 198 | + const el = document.getElementById(id); |
| 199 | + if (!el) return; |
| 200 | + const top = el.getBoundingClientRect().top - document.body.getBoundingClientRect().top - offset; |
| 201 | + window.scrollTo({ top, behavior: 'smooth' }); |
| 202 | + } |
| 203 | + |
| 204 | + // 先切换路由,再滚动到目标元素(用于跨页跳转) |
| 205 | + function navigateAndScroll(route, id, offset = 100, delay = 100) { |
| 206 | + window.location.hash = route; |
| 207 | + setTimeout(() => scrollToId(id, offset), delay); |
217 | 208 | } |
218 | 209 |
|
219 | | - // 将滚动函数暴露到全局作用域 |
| 210 | + // 兼容按钮点击的滚动(导航栏偏移 120) |
| 211 | + function scrollToSection(id) { |
| 212 | + scrollToId(id, 120); |
| 213 | + } |
| 214 | + |
| 215 | + // 将滚动函数暴露到全局作用域(供内联 onclick 调用) |
| 216 | + window.scrollToId = scrollToId; |
| 217 | + window.navigateAndScroll = navigateAndScroll; |
220 | 218 | window.scrollToSection = scrollToSection; |
221 | 219 |
|
222 | 220 | function parseHash() { |
|
260 | 258 | `; |
261 | 259 | } |
262 | 260 |
|
263 | | - function renderHome() { |
264 | | - const pillars = [ |
265 | | - { key: 'reasoning', titleKey: 'pillar.reasoning.title', descKey: 'pillar.reasoning.desc' }, |
266 | | - { key: 'multimodal', titleKey: 'pillar.multimodal.title', descKey: 'pillar.multimodal.desc' }, |
267 | | - { key: 'embodied', titleKey: 'pillar.embodied.title', descKey: 'pillar.embodied.desc' }, |
268 | | - { key: 'infra', titleKey: 'pillar.infra.title', descKey: 'pillar.infra.desc' }, |
269 | | - { key: 'arch', titleKey: 'pillar.arch.title', descKey: 'pillar.arch.desc' }, |
270 | | - { key: 'safety', titleKey: 'pillar.safety.title', descKey: 'pillar.safety.desc' } |
271 | | - ]; |
| 261 | + // 渲染研究方向卡片网格;navigate=true 时先跳转到研究页再滚动到对应论文区块 |
| 262 | + function renderPillarsGrid(navigate) { |
| 263 | + return ` |
| 264 | + <div class="pillars-grid"> |
| 265 | + ${PILLARS.map(p => { |
| 266 | + const onclick = navigate |
| 267 | + ? `navigateAndScroll('research', 'pub-${p.key}')` |
| 268 | + : `scrollToId('pub-${p.key}')`; |
| 269 | + return ` |
| 270 | + <article class="pillar-card pillar-card-clickable" onclick="${onclick}"> |
| 271 | + <h3>${t(p.titleKey)}</h3> |
| 272 | + <p>${t(p.descKey)}</p> |
| 273 | + </article>`; |
| 274 | + }).join('')} |
| 275 | + </div>`; |
| 276 | + } |
272 | 277 |
|
| 278 | + function renderHome() { |
273 | 279 | return ` |
274 | 280 | ${renderHero()} |
275 | 281 | <section class="container sec"> |
276 | 282 | <h2>${t('research.title')}</h2> |
277 | | - <div class="pillars-grid"> |
278 | | - ${pillars.map(p => ` |
279 | | - <article class="pillar-card pillar-card-clickable" onclick="window.location.hash = 'research'; setTimeout(() => { const el = document.getElementById('pub-${p.key}'); if(el) { const offset = 100; const bodyRect = document.body.getBoundingClientRect().top; const elementRect = el.getBoundingClientRect().top; const elementPosition = elementRect - bodyRect; const offsetPosition = elementPosition - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }, 100);"> |
280 | | - <h3>${t(p.titleKey)}</h3> |
281 | | - <p>${t(p.descKey)}</p> |
282 | | - </article> |
283 | | - `).join('')} |
284 | | - </div> |
| 283 | + ${renderPillarsGrid(true)} |
285 | 284 | </section> |
286 | 285 | <section class="container sec"> |
287 | 286 | <h2>${t('people.title')}</h2> |
|
312 | 311 | <p>${t('resources.highlight.tools.desc')}</p> |
313 | 312 | <div class="hero-actions" style="margin-top: auto; width: 100%;"> |
314 | 313 | <a class="btn btn-outline" href="https://github.com/OpenMOSS" target="_blank">${t('resources.highlight.tools.btn1')}</a> |
315 | | - <a class="btn btn-outline" href="javascript:void(0)" onclick="window.location.hash='resources'; setTimeout(() => { const el = document.getElementById('projects'); if(el) { const offset = 100; const bodyRect = document.body.getBoundingClientRect().top; const elementRect = el.getBoundingClientRect().top; const elementPosition = elementRect - bodyRect; const offsetPosition = elementPosition - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }, 200);">${t('resources.highlight.tools.btn2')}</a> |
| 314 | + <a class="btn btn-outline" href="javascript:void(0)" onclick="navigateAndScroll('resources', 'projects', 100, 200)">${t('resources.highlight.tools.btn2')}</a> |
316 | 315 | </div> |
317 | 316 | </article> |
318 | 317 | <article class="people-card"> |
319 | 318 | <h3>${t('resources.highlight.courses.title')}</h3> |
320 | 319 | <p>${t('resources.highlight.courses.desc')}</p> |
321 | 320 | <div class="hero-actions" style="margin-top: auto; width: 100%;"> |
322 | | - <a class="btn btn-outline" href="javascript:void(0)" onclick="window.location.hash='resources'; setTimeout(() => { const el = document.getElementById('courses'); if(el) { const offset = 100; const bodyRect = document.body.getBoundingClientRect().top; const elementRect = el.getBoundingClientRect().top; const elementPosition = elementRect - bodyRect; const offsetPosition = elementPosition - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }, 200);">${t('resources.highlight.courses.btn')}</a> |
| 321 | + <a class="btn btn-outline" href="javascript:void(0)" onclick="navigateAndScroll('resources', 'courses', 100, 200)">${t('resources.highlight.courses.btn')}</a> |
323 | 322 | </div> |
324 | 323 | </article> |
325 | 324 | </div> |
|
338 | 337 | ]; |
339 | 338 |
|
340 | 339 | const tocLinks = sections.map(sec => |
341 | | - `<a href="javascript:void(0)" onclick="const el = document.getElementById('${sec.id}'); if(el) { const offset = 100; const bodyRect = document.body.getBoundingClientRect().top; const elementRect = el.getBoundingClientRect().top; const elementPosition = elementRect - bodyRect; const offsetPosition = elementPosition - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } return false;">${t(sec.titleKey)}</a>` |
| 340 | + `<a href="javascript:void(0)" onclick="scrollToId('${sec.id}'); return false;">${t(sec.titleKey)}</a>` |
342 | 341 | ).join(''); |
343 | 342 |
|
344 | 343 | return ` |
|
410 | 409 | ]; |
411 | 410 |
|
412 | 411 | const tocLinks = categories.map(cat => |
413 | | - `<a href="javascript:void(0)" onclick="const el = document.getElementById('${cat.id}'); if(el) { const offset = 100; const bodyRect = document.body.getBoundingClientRect().top; const elementRect = el.getBoundingClientRect().top; const elementPosition = elementRect - bodyRect; const offsetPosition = elementPosition - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } return false;">${t(cat.titleKey)}</a>` |
| 412 | + `<a href="javascript:void(0)" onclick="scrollToId('${cat.id}'); return false;">${t(cat.titleKey)}</a>` |
414 | 413 | ).join(''); |
415 | 414 |
|
416 | 415 | return ` |
|
459 | 458 | } |
460 | 459 |
|
461 | 460 | function renderResearch() { |
462 | | - const pillars = [ |
463 | | - { key: 'reasoning', titleKey: 'pillar.reasoning.title', descKey: 'pillar.reasoning.desc' }, |
464 | | - { key: 'multimodal', titleKey: 'pillar.multimodal.title', descKey: 'pillar.multimodal.desc' }, |
465 | | - { key: 'embodied', titleKey: 'pillar.embodied.title', descKey: 'pillar.embodied.desc' }, |
466 | | - { key: 'infra', titleKey: 'pillar.infra.title', descKey: 'pillar.infra.desc' }, |
467 | | - { key: 'arch', titleKey: 'pillar.arch.title', descKey: 'pillar.arch.desc' }, |
468 | | - { key: 'safety', titleKey: 'pillar.safety.title', descKey: 'pillar.safety.desc' } |
469 | | - ]; |
470 | | - |
471 | 461 | // 从 SPA_DATA 获取最新亮点内容,并处理多语言 |
472 | 462 | const highlights = (SPA_DATA.highlights || []).map(h => ({ |
473 | 463 | title: typeof h.title === 'object' ? (h.title[currentLang] || h.title.zh) : h.title, |
|
482 | 472 | <h1>${t('research.title')}</h1> |
483 | 473 | <p>${t('research.intro')}</p> |
484 | 474 | </div> |
485 | | - <div class="pillars-grid"> |
486 | | - ${pillars.map(p => ` |
487 | | - <article class="pillar-card pillar-card-clickable" onclick="const el = document.getElementById('pub-${p.key}'); if(el) { const offset = 100; const bodyRect = document.body.getBoundingClientRect().top; const elementRect = el.getBoundingClientRect().top; const elementPosition = elementRect - bodyRect; const offsetPosition = elementPosition - offset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); }"> |
488 | | - <h3>${t(p.titleKey)}</h3> |
489 | | - <p>${t(p.descKey)}</p> |
490 | | - </article> |
491 | | - `).join('')} |
492 | | - </div> |
| 475 | + ${renderPillarsGrid(false)} |
493 | 476 | </section> |
494 | 477 | |
495 | 478 | <section class="container sec" id="research-highlights"> |
|
509 | 492 | |
510 | 493 | <section class="container sec"> |
511 | 494 | <h2>${t('research.publications.title')}</h2> |
512 | | - ${pillars.map(p => { |
| 495 | + ${PILLARS.map(p => { |
513 | 496 | const pubs = SPA_DATA.publications[p.key] || []; |
514 | 497 | return ` |
515 | 498 | <div id="pub-${p.key}" class="publication-section"> |
|
0 commit comments