finally finish all website check in.
This commit is contained in:
@@ -45,43 +45,42 @@ const {
|
||||
transition: all var(--duration) var(--easing);
|
||||
}
|
||||
|
||||
/* 动画定义 */
|
||||
.animate-wrapper[data-animation="fadeIn"] {
|
||||
/* 动画定义 - 只在触发时应用动画 */
|
||||
.animate-wrapper.animate-visible[data-animation="fadeIn"] {
|
||||
animation-name: fadeIn;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="fadeInUp"] {
|
||||
.animate-wrapper.animate-visible[data-animation="fadeInUp"] {
|
||||
animation-name: fadeInUp;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="fadeInDown"] {
|
||||
.animate-wrapper.animate-visible[data-animation="fadeInDown"] {
|
||||
animation-name: fadeInDown;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="slideInLeft"] {
|
||||
.animate-wrapper.animate-visible[data-animation="slideInLeft"] {
|
||||
animation-name: slideInLeft;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="slideInRight"] {
|
||||
.animate-wrapper.animate-visible[data-animation="slideInRight"] {
|
||||
animation-name: slideInRight;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="scaleIn"] {
|
||||
.animate-wrapper.animate-visible[data-animation="scaleIn"] {
|
||||
animation-name: scaleIn;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="rotateIn"] {
|
||||
.animate-wrapper.animate-visible[data-animation="rotateIn"] {
|
||||
animation-name: rotateIn;
|
||||
}
|
||||
|
||||
.animate-wrapper[data-animation="bounceIn"] {
|
||||
.animate-wrapper.animate-visible[data-animation="bounceIn"] {
|
||||
animation-name: bounceIn;
|
||||
}
|
||||
|
||||
/* 触发状态 */
|
||||
/* 触发状态 - 动画完成后的最终状态 */
|
||||
.animate-wrapper.animate-visible {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
animation-fill-mode: both;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@@ -200,8 +199,21 @@ const {
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
const element = entry.target as HTMLElement;
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animate-visible');
|
||||
// 确保元素在进入视口时正确触发动画
|
||||
if (!element.classList.contains('animate-visible')) {
|
||||
// 重置动画状态以确保延迟正确工作
|
||||
element.style.animationName = 'none';
|
||||
element.offsetHeight; // 强制重绘
|
||||
element.style.animationName = '';
|
||||
|
||||
// 添加可见类触发动画
|
||||
element.classList.add('animate-visible');
|
||||
}
|
||||
} else {
|
||||
// 可选:元素离开视口时重置动画(支持重复动画)
|
||||
// element.classList.remove('animate-visible');
|
||||
}
|
||||
});
|
||||
}, {
|
||||
@@ -230,4 +242,10 @@ const {
|
||||
initScrollAnimations();
|
||||
initClickAnimations();
|
||||
});
|
||||
|
||||
// 页面导航时重新初始化(适用于SPA或动态内容)
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
initScrollAnimations();
|
||||
initClickAnimations();
|
||||
});
|
||||
</script>
|
||||
@@ -138,17 +138,17 @@ const revealAttributes = reveal
|
||||
|
||||
/* 尺寸样式 */
|
||||
.container-small {
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container-medium {
|
||||
max-width: 600px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container-large {
|
||||
max-width: 800px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,22 +4,30 @@ export interface Props {
|
||||
description?: string;
|
||||
navigationItems?: Array<{ label: string; href: string; icon?: string }>;
|
||||
className?: string;
|
||||
pageTitle?: string; // 页面标题,优先于通用标题
|
||||
showPageTitle?: boolean; // 是否显示页面标题而不是网站标题
|
||||
}
|
||||
|
||||
import GlowButton from './common/GlowButton.astro';
|
||||
|
||||
const {
|
||||
title = 'Jiao77 - AI & Technology Explorer',
|
||||
pageTitle,
|
||||
showPageTitle = false,
|
||||
description,
|
||||
navigationItems,
|
||||
className = ''
|
||||
} = Astro.props;
|
||||
|
||||
// 决定显示的标题
|
||||
const displayTitle = showPageTitle && pageTitle ? pageTitle : title;
|
||||
// 决定点击行为:如果显示页面标题则返回顶部,否则返回首页
|
||||
const titleClickAction = showPageTitle ? 'scroll-to-top' : 'home';
|
||||
|
||||
const defaultNavigation = [
|
||||
{ label: '首页', href: '/', icon: 'fas fa-home' },
|
||||
{ label: '报告导航', href: '/report', icon: 'fas fa-chart-line' },
|
||||
{ label: '关于我', href: '/about', icon: 'fas fa-user-astronaut' },
|
||||
{ label: '组件测试', href: '/components-demo', icon: 'fas fa-user-astronaut' },
|
||||
];
|
||||
|
||||
const links = navigationItems && navigationItems.length > 0 ? navigationItems : defaultNavigation;
|
||||
@@ -28,9 +36,15 @@ const links = navigationItems && navigationItems.length > 0 ? navigationItems :
|
||||
<header id="site-header" class={`site-header ${className}`}>
|
||||
<div class="header-inner">
|
||||
<div class="header-brand">
|
||||
<a href="/" class="brand-link" aria-label="返回首页">
|
||||
<h1 class="brand-title">{title}</h1>
|
||||
</a>
|
||||
{titleClickAction === 'scroll-to-top' ? (
|
||||
<button class="brand-link scroll-to-top-btn" aria-label="返回页面顶部">
|
||||
<h1 class="brand-title">{displayTitle}</h1>
|
||||
</button>
|
||||
) : (
|
||||
<a href="/" class="brand-link" aria-label="返回首页">
|
||||
<h1 class="brand-title">{displayTitle}</h1>
|
||||
</a>
|
||||
)}
|
||||
{description && (
|
||||
<p class="brand-description">{description}</p>
|
||||
)}
|
||||
@@ -148,6 +162,24 @@ const links = navigationItems && navigationItems.length > 0 ? navigationItems :
|
||||
|
||||
.brand-link {
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scroll-to-top-btn {
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.scroll-to-top-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.scroll-to-top-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
---
|
||||
// 单元格内容类型:可以是简单字符串、数字、布尔值,或包含样式的对象
|
||||
export type CellContent = string | number | boolean | {
|
||||
content: string;
|
||||
style?: 'highlight' | 'warning' | 'error' | 'success' | 'primary';
|
||||
strong?: boolean;
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
headers?: string[];
|
||||
rows?: (string | number | boolean)[][];
|
||||
rows?: CellContent[][];
|
||||
caption?: string;
|
||||
className?: string;
|
||||
striped?: boolean;
|
||||
@@ -16,6 +23,30 @@ const {
|
||||
striped = false,
|
||||
bordered = true
|
||||
} = Astro.props;
|
||||
|
||||
// 辅助函数:获取单元格样式类
|
||||
function getCellClass(cell: CellContent): string {
|
||||
if (typeof cell === 'object' && cell !== null && 'style' in cell) {
|
||||
return cell.style ? `${cell.style}-cell` : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// 辅助函数:获取单元格内容
|
||||
function getCellContent(cell: CellContent): string {
|
||||
if (typeof cell === 'object' && cell !== null && 'content' in cell) {
|
||||
return cell.content;
|
||||
}
|
||||
return String(cell);
|
||||
}
|
||||
|
||||
// 辅助函数:判断是否需要加粗
|
||||
function isStrong(cell: CellContent): boolean {
|
||||
if (typeof cell === 'object' && cell !== null && 'strong' in cell) {
|
||||
return cell.strong ?? false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
---
|
||||
|
||||
<div class={`glass-table-container ${className}`}>
|
||||
@@ -36,7 +67,13 @@ const {
|
||||
{rows.map(row => (
|
||||
<tr>
|
||||
{row.map(cell => (
|
||||
<td>{cell}</td>
|
||||
<td class={getCellClass(cell)}>
|
||||
{isStrong(cell) ? (
|
||||
<strong set:html={getCellContent(cell)} />
|
||||
) : (
|
||||
<span set:html={getCellContent(cell)} />
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
@@ -76,7 +113,7 @@ const {
|
||||
caption-side: top;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
color: #2c4a6b;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
@@ -90,13 +127,13 @@ const {
|
||||
}
|
||||
|
||||
.glass-table th {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #1e40af;
|
||||
background: rgba(91, 119, 142, 0.1);
|
||||
color: #011a2d;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border-bottom: 2px solid rgba(59, 130, 246, 0.2);
|
||||
border-bottom: 2px solid rgba(91, 119, 142, 0.3);
|
||||
}
|
||||
|
||||
.glass-table td {
|
||||
@@ -119,12 +156,12 @@ const {
|
||||
}
|
||||
|
||||
.glass-table tbody tr:hover {
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
background: rgba(91, 119, 142, 0.1);
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.glass-table tbody tr:hover td {
|
||||
color: #1e40af;
|
||||
color: #011a2d;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@@ -145,7 +182,19 @@ const {
|
||||
}
|
||||
}
|
||||
|
||||
/* 特殊单元格样式 */
|
||||
/* 特殊单元格样式 - 莫兰蒂蓝色系 */
|
||||
.glass-table .primary-cell {
|
||||
background: rgba(91, 119, 142, 0.15);
|
||||
color: #011a2d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.glass-table .success-cell {
|
||||
background: rgba(91, 119, 142, 0.1);
|
||||
color: #166534;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.glass-table .highlight-cell {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #166534;
|
||||
|
||||
841
src/components/common/TOCSidebar.astro
Normal file
841
src/components/common/TOCSidebar.astro
Normal file
@@ -0,0 +1,841 @@
|
||||
---
|
||||
export interface Props {
|
||||
title?: string;
|
||||
toggleLabel?: string;
|
||||
targetSelector?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title = '目录',
|
||||
toggleLabel = '目录',
|
||||
targetSelector = '[data-report-content]'
|
||||
} = Astro.props;
|
||||
|
||||
const sidebarId = `toc-sidebar-${Math.random().toString(36).slice(2, 10)}`;
|
||||
---
|
||||
|
||||
<!-- 侧边栏容器 -->
|
||||
<aside id={sidebarId} class="toc-sidebar" data-toc-sidebar aria-hidden="false" data-target-selector={targetSelector}>
|
||||
<!-- 切换按钮 -->
|
||||
<button class="toc-toggle" type="button" aria-label={toggleLabel} data-toc-toggle>
|
||||
<svg class="toc-toggle__icon" viewBox="0 0 24 24" width="20" height="20">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M3 12h18M3 6h18M3 18h18"/>
|
||||
</svg>
|
||||
<span class="toc-toggle__label">{toggleLabel}</span>
|
||||
</button>
|
||||
|
||||
<!-- 背景遮罩 (仅移动端) -->
|
||||
<div class="toc-backdrop" data-toc-backdrop></div>
|
||||
|
||||
<!-- 侧边栏面板 -->
|
||||
<div class="toc-panel" data-toc-panel>
|
||||
<header class="toc-header">
|
||||
<h3 class="toc-title">{title}</h3>
|
||||
<button class="toc-close" type="button" aria-label="关闭目录" data-toc-close>
|
||||
<svg viewBox="0 0 24 24" width="18" height="18">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<nav class="toc-nav">
|
||||
<ul class="toc-list" data-toc-list></ul>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<script is:inline define:vars={{ sidebarId, targetSelector }}>
|
||||
// @ts-nocheck
|
||||
// 变量由 Astro define:vars 注入: sidebarId, targetSelector
|
||||
|
||||
const __initTOC = () => {
|
||||
const sidebar = document.getElementById(sidebarId);
|
||||
if (!sidebar) return;
|
||||
|
||||
const tocList = sidebar.querySelector('[data-toc-list]');
|
||||
const toggleBtn = sidebar.querySelector('[data-toc-toggle]');
|
||||
const closeBtn = sidebar.querySelector('[data-toc-close]');
|
||||
const backdrop = sidebar.querySelector('[data-toc-backdrop]');
|
||||
const panel = sidebar.querySelector('[data-toc-panel]');
|
||||
|
||||
if (!tocList || !toggleBtn || !closeBtn || !backdrop || !panel) return;
|
||||
|
||||
// 响应式断点
|
||||
const mediaQuery = window.matchMedia('(min-width: 1024px)');
|
||||
|
||||
// 生成ID的工具函数
|
||||
const generateId = (text) => {
|
||||
return text.toLowerCase()
|
||||
.replace(/[\p{Extended_Pictographic}\u2600-\u27BF]/gu, '') // 移除emoji
|
||||
.replace(/[^\w\u4e00-\u9fa5\s-]/g, '') // 保留文字、数字、中文、空格、连字符
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
};
|
||||
|
||||
// 构建目录树
|
||||
const buildTOC = () => {
|
||||
const targetRoot = document.querySelector(targetSelector) || document.body;
|
||||
const headings = Array.from(targetRoot.querySelectorAll('h2, h3, h4'))
|
||||
.filter(h => h.textContent?.trim());
|
||||
|
||||
if (!headings.length) return;
|
||||
|
||||
// 为标题添加ID
|
||||
const existingIds = new Set();
|
||||
headings.forEach(heading => {
|
||||
if (!heading.id) {
|
||||
let baseId = generateId(heading.textContent.trim()) || 'section';
|
||||
let finalId = baseId;
|
||||
let counter = 1;
|
||||
|
||||
while (existingIds.has(finalId) || document.getElementById(finalId)) {
|
||||
finalId = `${baseId}-${counter++}`;
|
||||
}
|
||||
|
||||
heading.id = finalId;
|
||||
existingIds.add(finalId);
|
||||
} else {
|
||||
existingIds.add(heading.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 构建嵌套结构
|
||||
const tocData = [];
|
||||
const stack = [];
|
||||
|
||||
headings.forEach(heading => {
|
||||
const level = parseInt(heading.tagName.charAt(1));
|
||||
const text = heading.textContent.trim();
|
||||
const id = heading.id;
|
||||
|
||||
const item = {
|
||||
level,
|
||||
text,
|
||||
id,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 找到合适的父级
|
||||
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
if (stack.length === 0) {
|
||||
tocData.push(item);
|
||||
} else {
|
||||
stack[stack.length - 1].children.push(item);
|
||||
}
|
||||
|
||||
stack.push(item);
|
||||
});
|
||||
|
||||
// 渲染TOC
|
||||
renderTOC(tocData);
|
||||
setupScrollSpy(headings);
|
||||
};
|
||||
|
||||
// 渲染TOC HTML
|
||||
const renderTOC = (items, isNested = false) => {
|
||||
if (!items.length) return '';
|
||||
|
||||
const html = items.map(item => {
|
||||
const hasChildren = item.children.length > 0;
|
||||
const isCollapsed = item.level >= 4; // h4及以下默认折叠
|
||||
|
||||
return `
|
||||
<li class="toc-item toc-item--level-${item.level}" data-level="${item.level}">
|
||||
<div class="toc-item-content">
|
||||
${hasChildren ?
|
||||
`<button class="toc-expand" type="button" aria-expanded="${!isCollapsed}" data-expand>
|
||||
<svg class="toc-expand-icon" viewBox="0 0 24 24" width="14" height="14">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M9 18l6-6-6-6"/>
|
||||
</svg>
|
||||
</button>` :
|
||||
'<span class="toc-spacer"></span>'
|
||||
}
|
||||
<a href="#${item.id}" class="toc-link" data-target="${item.id}" data-level="${item.level}">
|
||||
${item.text}
|
||||
</a>
|
||||
</div>
|
||||
${hasChildren ?
|
||||
`<ul class="toc-sublist ${isCollapsed ? 'toc-sublist--collapsed' : ''}">
|
||||
${renderTOC(item.children, true)}
|
||||
</ul>` : ''
|
||||
}
|
||||
</li>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (!isNested) {
|
||||
tocList.innerHTML = html;
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
// 侧边栏状态管理 - 修复移动端问题
|
||||
const setSidebarOpen = (isOpen) => {
|
||||
sidebar.classList.toggle('toc-sidebar--open', isOpen);
|
||||
sidebar.classList.toggle('toc-sidebar--closed', !isOpen);
|
||||
sidebar.setAttribute('aria-hidden', String(!isOpen));
|
||||
toggleBtn.setAttribute('aria-expanded', String(isOpen));
|
||||
|
||||
// 确保面板动画正确执行
|
||||
if (!mediaQuery.matches) {
|
||||
if (isOpen) {
|
||||
panel.style.transform = 'translateY(0)';
|
||||
requestAnimationFrame(() => {
|
||||
panel.style.transform = 'translateY(0)';
|
||||
});
|
||||
} else {
|
||||
panel.style.transform = 'translateY(100%)';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 响应式处理
|
||||
const handleMediaChange = () => {
|
||||
if (mediaQuery.matches) {
|
||||
// 桌面端:默认展开
|
||||
setSidebarOpen(true);
|
||||
sidebar.classList.add('toc-sidebar--desktop');
|
||||
} else {
|
||||
// 移动端:默认收起
|
||||
setSidebarOpen(false);
|
||||
sidebar.classList.remove('toc-sidebar--desktop');
|
||||
}
|
||||
};
|
||||
|
||||
// 事件监听 - 修复点击无效的问题
|
||||
const handleToggleClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('Toggle clicked'); // 调试用
|
||||
const isOpen = sidebar.classList.contains('toc-sidebar--open');
|
||||
setSidebarOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleCloseClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('Close clicked'); // 调试用
|
||||
setSidebarOpen(false);
|
||||
};
|
||||
|
||||
const handleBackdropClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setSidebarOpen(false);
|
||||
};
|
||||
|
||||
// 强制重新绑定事件(移除可能存在的旧监听器)
|
||||
const newToggleBtn = toggleBtn.cloneNode(true);
|
||||
const newCloseBtn = closeBtn.cloneNode(true);
|
||||
const newBackdrop = backdrop.cloneNode(true);
|
||||
|
||||
toggleBtn.parentNode.replaceChild(newToggleBtn, toggleBtn);
|
||||
closeBtn.parentNode.replaceChild(newCloseBtn, closeBtn);
|
||||
backdrop.parentNode.replaceChild(newBackdrop, backdrop);
|
||||
|
||||
// 重新获取元素并绑定事件
|
||||
const currentToggleBtn = sidebar.querySelector('[data-toc-toggle]');
|
||||
const currentCloseBtn = sidebar.querySelector('[data-toc-close]');
|
||||
const currentBackdrop = sidebar.querySelector('[data-toc-backdrop]');
|
||||
|
||||
currentToggleBtn.addEventListener('click', handleToggleClick, { passive: false });
|
||||
currentCloseBtn.addEventListener('click', handleCloseClick, { passive: false });
|
||||
currentBackdrop.addEventListener('click', handleBackdropClick, { passive: false });
|
||||
|
||||
// 折叠/展开处理
|
||||
tocList.addEventListener('click', (e) => {
|
||||
const expandBtn = e.target.closest('[data-expand]');
|
||||
if (expandBtn) {
|
||||
e.preventDefault();
|
||||
const sublist = expandBtn.closest('.toc-item').querySelector('.toc-sublist');
|
||||
if (sublist) {
|
||||
const isCollapsed = sublist.classList.contains('toc-sublist--collapsed');
|
||||
sublist.classList.toggle('toc-sublist--collapsed', !isCollapsed);
|
||||
expandBtn.setAttribute('aria-expanded', String(isCollapsed));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const link = e.target.closest('.toc-link');
|
||||
if (link) {
|
||||
e.preventDefault();
|
||||
const targetId = link.getAttribute('data-target');
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
history.replaceState(null, '', `#${targetId}`);
|
||||
}
|
||||
|
||||
// 移动端点击后关闭侧边栏
|
||||
if (!mediaQuery.matches) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 滚动高亮
|
||||
let activeLink = null;
|
||||
|
||||
const setActiveLink = (id) => {
|
||||
if (activeLink) {
|
||||
activeLink.classList.remove('toc-link--active');
|
||||
}
|
||||
|
||||
// 修复:使用正确的属性选择器
|
||||
const newActiveLink = tocList.querySelector(`[data-target="${id}"]`);
|
||||
if (newActiveLink) {
|
||||
newActiveLink.classList.add('toc-link--active');
|
||||
activeLink = newActiveLink;
|
||||
|
||||
// 自动展开父级
|
||||
let parent = newActiveLink.closest('.toc-sublist');
|
||||
while (parent) {
|
||||
parent.classList.remove('toc-sublist--collapsed');
|
||||
const expandBtn = parent.previousElementSibling?.querySelector('[data-expand]');
|
||||
if (expandBtn) {
|
||||
expandBtn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
parent = parent.closest('.toc-item')?.parentElement?.closest('.toc-sublist');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setupScrollSpy = (headings) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const visibleEntries = entries
|
||||
.filter(entry => entry.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
|
||||
|
||||
if (visibleEntries.length > 0) {
|
||||
setActiveLink(visibleEntries[0].target.id);
|
||||
}
|
||||
}, {
|
||||
rootMargin: '-20% 0px -70% 0px',
|
||||
threshold: [0, 0.25, 0.5, 0.75, 1]
|
||||
});
|
||||
|
||||
headings.forEach(heading => observer.observe(heading));
|
||||
};
|
||||
|
||||
// 键盘事件
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && sidebar.classList.contains('toc-sidebar--open')) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化
|
||||
buildTOC();
|
||||
mediaQuery.addEventListener('change', handleMediaChange);
|
||||
handleMediaChange();
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', __initTOC, { once: true });
|
||||
} else {
|
||||
__initTOC();
|
||||
}
|
||||
|
||||
// Astro 过渡后重新初始化
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
__initTOC();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 基础布局 */
|
||||
.toc-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 50;
|
||||
pointer-events: none;
|
||||
/* 底部安全距离(同时考虑安全区域) */
|
||||
--toc-safe-bottom: clamp(12px, 2vh, 24px);
|
||||
--toc-safe-area-bottom: env(safe-area-inset-bottom, 16px);
|
||||
}
|
||||
|
||||
.toc-sidebar--open {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* 切换按钮 */
|
||||
.toc-toggle {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 2rem;
|
||||
background: rgba(248, 250, 255, 0.18);
|
||||
backdrop-filter: blur(30px) saturate(150%);
|
||||
border: 1px solid rgba(148, 163, 184, 0.28);
|
||||
color: #011a2d;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 20px 48px rgba(15, 23, 42, 0.18);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
pointer-events: auto;
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.toc-toggle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(120deg, rgba(255, 255, 255, 0.12), transparent 60%),
|
||||
radial-gradient(80% 160% at 120% -40%, rgba(59, 130, 246, 0.18), transparent 70%);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toc-toggle:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(248, 250, 255, 0.25);
|
||||
border-color: rgba(148, 163, 184, 0.35);
|
||||
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.22);
|
||||
}
|
||||
|
||||
.toc-toggle__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.toc-toggle__label {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* 背景遮罩 */
|
||||
.toc-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.toc-sidebar--open .toc-backdrop {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 侧边栏面板 */
|
||||
.toc-panel {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 80vh;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(174, 206, 221, 0.16),
|
||||
rgba(177, 217, 212, 0.12),
|
||||
rgba(178, 197, 213, 0.12)
|
||||
);
|
||||
backdrop-filter: blur(16px) saturate(130%);
|
||||
border: 1px solid rgba(91, 119, 142, 0.28);
|
||||
/* 显示底部边框,并通过 bottom 安全距离与屏幕底部留白 */
|
||||
border-bottom: 1px solid rgba(91, 119, 142, 0.28);
|
||||
border-radius: 1.5rem 1.5rem 0 0;
|
||||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.16);
|
||||
transform: translateY(100%);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.toc-panel::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(120deg, rgba(255, 255, 255, 0.12), transparent 60%),
|
||||
radial-gradient(80% 160% at 120% -40%, rgba(59, 130, 246, 0.18), transparent 70%);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.toc-sidebar--open .toc-panel {
|
||||
transform: translateY(0);
|
||||
bottom: calc(var(--toc-safe-bottom) + var(--toc-safe-area-bottom));
|
||||
}
|
||||
|
||||
.toc-panel:hover {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(174, 206, 221, 0.2),
|
||||
rgba(177, 217, 212, 0.16),
|
||||
rgba(178, 197, 213, 0.16)
|
||||
);
|
||||
border-color: rgba(91, 119, 142, 0.35);
|
||||
box-shadow: 0 20px 48px rgba(15, 23, 42, 0.2);
|
||||
}
|
||||
|
||||
/* 面板头部 */
|
||||
.toc-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.5rem 1.5rem 1rem;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: #011a2d;
|
||||
}
|
||||
|
||||
.toc-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||
border-radius: 50%;
|
||||
background: rgba(248, 250, 255, 0.15);
|
||||
backdrop-filter: blur(20px) saturate(120%);
|
||||
color: #011a2d;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toc-close::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), transparent 50%);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toc-close:hover {
|
||||
background: rgba(248, 250, 255, 0.25);
|
||||
border-color: rgba(148, 163, 184, 0.35);
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.15);
|
||||
}
|
||||
|
||||
.toc-close svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* 导航区域 */
|
||||
.toc-nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem 1.5rem 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar-track {
|
||||
background: rgba(148, 163, 184, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(148, 163, 184, 0.4);
|
||||
}
|
||||
|
||||
/* 目录列表 */
|
||||
.toc-list,
|
||||
.toc-sublist {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toc-sublist {
|
||||
margin-left: 1rem;
|
||||
margin-top: 0.25rem;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.toc-sublist--collapsed {
|
||||
max-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 目录项 */
|
||||
.toc-item {
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.toc-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.toc-expand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toc-expand:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #011a2d;
|
||||
}
|
||||
|
||||
.toc-expand[aria-expanded="true"] .toc-expand-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toc-expand-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.toc-spacer {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 目录链接 */
|
||||
.toc-link {
|
||||
flex: 1;
|
||||
display: block;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
/* 根据层级设置样式 */
|
||||
.toc-item--level-2 .toc-link {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
}
|
||||
|
||||
.toc-item--level-3 .toc-link {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.toc-item--level-4 .toc-link {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 400;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.toc-link:hover {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-left-color: rgba(59, 130, 246, 0.5);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.toc-link--active {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(29, 78, 216, 0.1));
|
||||
border-left-color: #3b82f6;
|
||||
color: #1d4ed8 !important;
|
||||
font-weight: 600 !important;
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
/* 桌面端样式 */
|
||||
@media (min-width: 1024px) {
|
||||
.toc-sidebar {
|
||||
--toc-width: clamp(280px, 20vw, 300px);
|
||||
--container-max-width: 1200px;
|
||||
--container-padding: 1rem;
|
||||
--toc-offset: calc(max(var(--container-padding), (100vw - var(--container-max-width)) / 2) - var(--toc-width) - 1.5rem);
|
||||
position: fixed;
|
||||
top: 6rem;
|
||||
left: clamp(1rem, var(--toc-offset), 2rem);
|
||||
width: var(--toc-width);
|
||||
height: calc(100vh - 7rem - var(--toc-safe-bottom) - var(--toc-safe-area-bottom));
|
||||
pointer-events: auto;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.toc-sidebar--desktop {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.toc-toggle {
|
||||
position: static;
|
||||
bottom: auto;
|
||||
right: auto;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
/* 更柔和的按钮玻璃态,贴合主题 */
|
||||
background: rgba(174, 206, 221, 0.16);
|
||||
backdrop-filter: blur(14px) saturate(130%);
|
||||
border: 1px solid rgba(91, 119, 142, 0.28);
|
||||
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.14);
|
||||
}
|
||||
|
||||
.toc-backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toc-panel {
|
||||
position: static;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: auto;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 8rem);
|
||||
height: auto;
|
||||
min-height: 60vh;
|
||||
border-radius: 1.25rem;
|
||||
/* Morandi 主题的轻玻璃渐变,避免偏白 */
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(20px) saturate(120%);
|
||||
border: 1px solid rgba(91, 119, 142, 0.25);
|
||||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.12);
|
||||
/* 默认展开,避免脚本未初始化时看不到侧边栏 */
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* 桌面端:面板底部与屏幕边缘保留安全距离 */
|
||||
margin-bottom: calc(var(--toc-safe-bottom) + var(--toc-safe-area-bottom));
|
||||
}
|
||||
|
||||
/* 移除过强的前景叠加,保持更纯净的玻璃质感 */
|
||||
.toc-panel::before { display: none; }
|
||||
|
||||
/* 如果脚本需要关闭,则通过添加此类实现 */
|
||||
.toc-sidebar--closed .toc-panel {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.toc-header {
|
||||
padding: 1.25rem 1.25rem 1rem;
|
||||
border-bottom: 1px solid rgba(91, 119, 142, 0.25);
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0.75rem 1rem 1rem;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.3) transparent;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(148, 163, 184, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.toc-nav::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(148, 163, 184, 0.5);
|
||||
}
|
||||
|
||||
.toc-header {
|
||||
padding: 1.25rem 1.25rem 1rem;
|
||||
border-bottom: 1px solid rgba(91, 119, 142, 0.25);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 桌面端的紧凑间距 */
|
||||
.toc-item {
|
||||
margin-bottom: 0.0625rem;
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.toc-item--level-2 .toc-link {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.toc-item--level-3 .toc-link {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.toc-item--level-4 .toc-link {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 无障碍和动画偏好 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.toc-panel,
|
||||
.toc-toggle,
|
||||
.toc-backdrop,
|
||||
.toc-link,
|
||||
.toc-sublist,
|
||||
.toc-expand-icon {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高对比度模式支持 */
|
||||
@media (prefers-contrast: high) {
|
||||
.toc-panel {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.toc-link--active {
|
||||
background: #0066cc;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -185,14 +185,39 @@ const script = document.currentScript;
|
||||
const setCollapsed = (collapsed) => {
|
||||
sidebar.classList.toggle('collapsed', collapsed);
|
||||
toggleBtn.setAttribute('aria-expanded', String(!collapsed));
|
||||
|
||||
// 在移动端确保面板正确显示/隐藏
|
||||
if (!matchBreakpoint.matches) {
|
||||
const panel = sidebar.querySelector('.report-sidebar__panel');
|
||||
if (panel) {
|
||||
if (collapsed) {
|
||||
panel.style.transform = 'translateY(100%)';
|
||||
} else {
|
||||
panel.style.transform = 'translateY(0)';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
const handleToggle = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCollapsed(!sidebar.classList.contains('collapsed'));
|
||||
};
|
||||
|
||||
toggleBtn.addEventListener('click', handleToggle);
|
||||
closeBtn.addEventListener('click', () => setCollapsed(true));
|
||||
const handleClose = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCollapsed(true);
|
||||
};
|
||||
|
||||
// 确保移除旧的事件监听器
|
||||
toggleBtn.removeEventListener('click', handleToggle);
|
||||
closeBtn.removeEventListener('click', handleClose);
|
||||
|
||||
// 添加新的事件监听器
|
||||
toggleBtn.addEventListener('click', handleToggle, { passive: false });
|
||||
closeBtn.addEventListener('click', handleClose, { passive: false });
|
||||
|
||||
const handleLinkClick = (event) => {
|
||||
const link = event.target.closest('.nav-link');
|
||||
@@ -217,6 +242,24 @@ const script = document.currentScript;
|
||||
|
||||
matchBreakpoint.addEventListener('change', applyBreakpointState);
|
||||
|
||||
// 添加键盘事件支持
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Escape' && !sidebar.classList.contains('collapsed')) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// 添加背景点击关闭(移动端)
|
||||
const handleBackdropClick = (e) => {
|
||||
if (!matchBreakpoint.matches && e.target === sidebar && !sidebar.classList.contains('collapsed')) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
};
|
||||
|
||||
sidebar.addEventListener('click', handleBackdropClick);
|
||||
|
||||
let activeLink = null;
|
||||
let observer = null;
|
||||
|
||||
@@ -344,14 +387,17 @@ const script = document.currentScript;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1.25rem 1.25rem 1rem;
|
||||
border-bottom: 1px solid rgba(91, 119, 142, 0.25);
|
||||
margin-bottom: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.report-sidebar__title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
color: #011a2d;
|
||||
}
|
||||
|
||||
.report-sidebar__close {
|
||||
@@ -433,14 +479,17 @@ const script = document.currentScript;
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.report-sidebar {
|
||||
--sidebar-width: var(--report-sidebar-width, clamp(260px, 22vw, 320px));
|
||||
--sidebar-inline-offset: clamp(1rem, 2.5vw, 3rem);
|
||||
--sidebar-width: var(--report-sidebar-width, clamp(280px, 20vw, 300px));
|
||||
--container-max-width: 1200px;
|
||||
--container-padding: 1rem;
|
||||
--sidebar-offset: calc(max(var(--container-padding), (100vw - var(--container-max-width)) / 2) - var(--sidebar-width) - 1.5rem);
|
||||
position: fixed;
|
||||
top: 6rem;
|
||||
left: calc(env(safe-area-inset-left) + var(--sidebar-inline-offset));
|
||||
left: clamp(1rem, var(--sidebar-offset), 2rem);
|
||||
height: calc(100vh - 7rem);
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.report-sidebar__toggle {
|
||||
@@ -452,17 +501,39 @@ const script = document.currentScript;
|
||||
position: relative;
|
||||
top: auto;
|
||||
width: var(--sidebar-width);
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 8rem);
|
||||
height: auto;
|
||||
min-height: 60vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(20px) saturate(120%);
|
||||
border: 1px solid rgba(91, 119, 142, 0.25);
|
||||
border-radius: 1.25rem;
|
||||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.report-sidebar__nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0.75rem 1rem 1rem;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.3) transparent;
|
||||
}
|
||||
|
||||
.report-sidebar__nav::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.report-sidebar__nav::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(148, 163, 184, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.report-sidebar__nav::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(148, 163, 184, 0.5);
|
||||
}
|
||||
|
||||
.collapsed .report-sidebar__panel {
|
||||
@@ -481,6 +552,14 @@ const script = document.currentScript;
|
||||
z-index: 55;
|
||||
}
|
||||
|
||||
/* 在中等屏幕上调整按钮大小 */
|
||||
@media (min-width: 768px) and (max-width: 1279px) {
|
||||
.report-sidebar__toggle {
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.report-sidebar__toggle {
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
color: #f8fafc;
|
||||
|
||||
@@ -18,6 +18,15 @@ const { title, description = '', type = 'navigation' } = Astro.props;
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description}>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="/android-chrome-512x512.png">
|
||||
|
||||
<!-- 字体 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
274
src/pages/about.astro
Normal file
274
src/pages/about.astro
Normal file
@@ -0,0 +1,274 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Container from '../components/Container.astro';
|
||||
import AnimatedElement from '../components/AnimatedElement.astro';
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="关于我 - 焦七七"
|
||||
description="浙江大学在读研究生,专注于AI与EDA交叉领域研究"
|
||||
type="navigation"
|
||||
>
|
||||
<Header />
|
||||
|
||||
<main class="about-page">
|
||||
<div class="container">
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
|
||||
<Container variant="glass" padding="xl" className="profile-card">
|
||||
|
||||
<!-- 头像 -->
|
||||
<div class="avatar">
|
||||
<i class="fas fa-user-graduate"></i>
|
||||
</div>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<h1 class="name">焦七七</h1>
|
||||
<p class="school">浙江大学 · 在读本科生</p>
|
||||
<p class="field">AI + EDA 交叉领域研究</p>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 研究兴趣 -->
|
||||
<div class="section">
|
||||
<h2 class="section-title">研究兴趣</h2>
|
||||
<div class="interests">
|
||||
<span class="interest-tag">🤖 AI in EDA</span>
|
||||
<span class="interest-tag">🔍 版图分析</span>
|
||||
<span class="interest-tag">🏗️ 良率提升</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 技术栈 -->
|
||||
<div class="section">
|
||||
<h2 class="section-title">技术栈</h2>
|
||||
<div class="tech-stack">
|
||||
<span class="tech">C++</span>
|
||||
<span class="tech">PyTorch</span>
|
||||
<span class="tech">JavaScript</span>
|
||||
<span class="tech">Astro</span>
|
||||
{/* @ts-ignore */}
|
||||
<span class="tech">Docker</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<div class="contact">
|
||||
<a href="mailto:contact@jiao77.cn" class="contact-btn">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<span>联系我</span>
|
||||
</a>
|
||||
<a href="/" class="contact-btn secondary">
|
||||
<i class="fas fa-home"></i>
|
||||
<span>返回首页</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.about-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 头像 */
|
||||
.avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto 2rem;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3.5rem;
|
||||
color: white;
|
||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.avatar:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 基本信息 */
|
||||
.name {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.5rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.school {
|
||||
font-size: 1.25rem;
|
||||
color: #4b5563;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.field {
|
||||
font-size: 1rem;
|
||||
color: #6b7280;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* 分隔线 */
|
||||
.divider {
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
margin: 2rem auto;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 区块 */
|
||||
.section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 研究兴趣标签 */
|
||||
.interests {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.interest-tag {
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 2rem;
|
||||
color: #667eea;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.interest-tag:hover {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 技术栈 */
|
||||
.tech-stack {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tech {
|
||||
padding: 0.4rem 0.9rem;
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: 0.375rem;
|
||||
color: #2563eb;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tech:hover {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 联系按钮 */
|
||||
.contact {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.contact-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.contact-btn.secondary {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
color: #667eea;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.contact-btn.secondary:hover {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 640px) {
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.school {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.contact {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ import Footer from '../components/Footer.astro';
|
||||
import NavigationGrid from '../components/navigation/NavigationGrid.astro';
|
||||
import NavigationCard from '../components/navigation/NavigationCard.astro';
|
||||
import Container from '../components/Container.astro';
|
||||
import AnimatedElement from '../components/AnimatedElement.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Jiao77 - 首页" description="简约美观的现代化网站" type="navigation">
|
||||
@@ -12,97 +13,127 @@ import Container from '../components/Container.astro';
|
||||
|
||||
<main class="main-content">
|
||||
<div class="container mx-auto px-4">
|
||||
<Container
|
||||
variant="glass"
|
||||
size="large"
|
||||
padding="xl"
|
||||
className="text-center mb-12"
|
||||
revealDistance="48px"
|
||||
>
|
||||
<h1 class="hero-title"><i class="fas fa-rocket"></i> 焦七七小站</h1>
|
||||
<p class="hero-subtitle">技术服务平台</p>
|
||||
<p class="hero-description">整合多种自建服务,提供便捷的技术解决方案。从这里发现更多可能性!</p>
|
||||
</Container>
|
||||
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
|
||||
<Container
|
||||
variant="glass"
|
||||
size="large"
|
||||
padding="xl"
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 class="hero-title"><i class="fas fa-rocket"></i> 焦七七小站</h1>
|
||||
<p class="hero-subtitle">技术服务平台</p>
|
||||
<p class="hero-description">整合多种自建服务,提供便捷的技术解决方案。从这里发现更多可能性!</p>
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
|
||||
<NavigationGrid
|
||||
title="技术服务"
|
||||
description="整合多种自建服务,提供便捷的技术解决方案"
|
||||
columns={3}
|
||||
gap="large"
|
||||
>
|
||||
<NavigationCard
|
||||
title="AList 网盘"
|
||||
description="整合多平台云存储的统一管理工具,方便快捷地访问您的各种网盘和存储服务。"
|
||||
href="http://jiao77.cn:52443"
|
||||
icon="fas fa-cloud"
|
||||
color="primary"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="Q-Nas"
|
||||
description="基于飞牛系统的nas服务。"
|
||||
href="http://jiao77.cn:5666"
|
||||
icon="fas fa-server"
|
||||
color="secondary"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="nuc-Nas"
|
||||
description="另外一个飞牛nas。"
|
||||
href="http://jiao77.cn:56661"
|
||||
icon="fas fa-database"
|
||||
color="accent"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="RAGflow 知识库"
|
||||
description="基于检索增强生成的知识管理系统,快速获取和整理专业领域知识。"
|
||||
href="http://jiao77.cn:28081"
|
||||
icon="fas fa-robot"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="Open WebUI"
|
||||
description="强大的AI交互界面,提供直观的模型管理和交互体验。"
|
||||
href="http://jiao77.cn:38080"
|
||||
icon="fas fa-brain"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="Navidrome 音乐"
|
||||
description="您自己的个人音乐服务器,随时随地享受您的音乐收藏。"
|
||||
href="http://jiao77.cn:45332"
|
||||
icon="fas fa-music"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="Gitea"
|
||||
description="轻量级的代码托管解决方案,用于版本控制和协作开发。"
|
||||
href="http://jiao77.cn:3012"
|
||||
icon="fas fa-code-branch"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="技术报告"
|
||||
description="这是一个我做过的技术报告的导航。"
|
||||
href="http://jiao77.cn/report"
|
||||
icon="fas fa-file-alt"
|
||||
color="accent"
|
||||
size="medium"
|
||||
/>
|
||||
</NavigationGrid>
|
||||
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
|
||||
<NavigationGrid
|
||||
title="技术服务"
|
||||
description="整合多种自建服务,提供便捷的技术解决方案"
|
||||
columns={3}
|
||||
gap="large"
|
||||
>
|
||||
<AnimatedElement animation="fadeInUp" delay={600} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="AList 网盘"
|
||||
description="整合多平台云存储的统一管理工具,方便快捷地访问您的各种网盘和存储服务。"
|
||||
href="http://jiao77.cn:52443"
|
||||
icon="fas fa-cloud"
|
||||
color="primary"
|
||||
size="large"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={700} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="Q-Nas"
|
||||
description="基于飞牛系统的nas服务。"
|
||||
href="http://jiao77.cn:5666"
|
||||
icon="fas fa-server"
|
||||
color="secondary"
|
||||
size="large"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="nuc-Nas"
|
||||
description="另外一个飞牛nas。"
|
||||
href="http://jiao77.cn:56661"
|
||||
icon="fas fa-database"
|
||||
color="accent"
|
||||
size="large"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={900} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="RAGflow 知识库"
|
||||
description="基于检索增强生成的知识管理系统,快速获取和整理专业领域知识。"
|
||||
href="http://jiao77.cn:28081"
|
||||
icon="fas fa-robot"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="Open WebUI"
|
||||
description="强大的AI交互界面,提供直观的模型管理和交互体验。"
|
||||
href="http://jiao77.cn:38080"
|
||||
icon="fas fa-brain"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1100} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="Navidrome 音乐"
|
||||
description="您自己的个人音乐服务器,随时随地享受您的音乐收藏。"
|
||||
href="http://jiao77.cn:45332"
|
||||
icon="fas fa-music"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1200} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="Gitea"
|
||||
description="轻量级的代码托管解决方案,用于版本控制和协作开发。"
|
||||
href="http://jiao77.cn:3012"
|
||||
icon="fas fa-code-branch"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1300} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="技术报告"
|
||||
description="这是一个我做过的技术报告的导航。"
|
||||
href="http://jiao77.cn/report"
|
||||
icon="fas fa-file-alt"
|
||||
color="accent"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1400} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="3X-UI 代理管理"
|
||||
description="基于3X-UI的代理服务管理面板,提供便捷的代理配置和监控功能。"
|
||||
href="https://vps.jiao77.cn:5090/vps-jiao77/"
|
||||
icon="fas fa-network-wired"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
</NavigationGrid>
|
||||
</AnimatedElement>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,280 +1,345 @@
|
||||
---
|
||||
/**
|
||||
* 2025-09-12 组会报告
|
||||
* 近期工作进展与总结
|
||||
*/
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import Header from '../../../components/Header.astro';
|
||||
import Footer from '../../../components/Footer.astro';
|
||||
import ReportSection from '../../../components/report/ReportSection.astro';
|
||||
import MetricsGrid from '../../../components/report/MetricsGrid.astro';
|
||||
import MetricCard from '../../../components/report/MetricCard.astro';
|
||||
import Container from '../../../components/Container.astro';
|
||||
import AnimatedElement from '../../../components/AnimatedElement.astro';
|
||||
import ReportSidebar from '../../../components/report/ReportSidebar.astro';
|
||||
import ImageViewer from '../../../components/common/ImageViewer.astro';
|
||||
|
||||
// 导入动画时序计算工具
|
||||
import {
|
||||
getSectionBaseDelay
|
||||
} from '../../../scripts/animation-timing';// 定义各章节的基础延迟时间
|
||||
const SECTION_DELAYS = {
|
||||
HERO: 200,
|
||||
RORD: getSectionBaseDelay(0),
|
||||
GEO: getSectionBaseDelay(1),
|
||||
SIEMENS: getSectionBaseDelay(2),
|
||||
SCRIPTS: getSectionBaseDelay(3),
|
||||
};
|
||||
|
||||
// 报告配置
|
||||
const reportConfig = {
|
||||
title: "2025-09-12 组会报告",
|
||||
subtitle: "近期工作进展与总结",
|
||||
description: "2025年9月12日组会报告 - RoRD改进、Geo-Layout-Transformer、Siemens EDA Forum及服务器脚本",
|
||||
date: "2025年9月12日",
|
||||
type: "组会报告",
|
||||
actionText: "查看详情 ↓",
|
||||
actionLink: "#rord"
|
||||
};
|
||||
|
||||
// 图片数据
|
||||
const images = {
|
||||
siemens: "/report/20250912-images/image-20250905125626810.png",
|
||||
food: "/report/20250912-images/image-20250905133741725.png",
|
||||
nvidia: "/report/20250912-images/image-20250905134401341.png",
|
||||
zjunet1: "/report/20250912-images/image-20250905135823038.png",
|
||||
zjunet2: "/report/20250912-images/image-20250905135916304.png",
|
||||
mdview: {
|
||||
title: "/report/20250912-images/image-20250905134901903.png",
|
||||
table: "/report/20250912-images/image-20250905134946164.png",
|
||||
list1: "/report/20250912-images/image-20250905135004614.png",
|
||||
list2: "/report/20250912-images/image-20250905135032036.png",
|
||||
image: "/report/20250912-images/image-20250905135241555.png",
|
||||
link: "/report/20250912-images/image-20250905135106491.png",
|
||||
code: "/report/20250912-images/image-20250905135325264.png"
|
||||
}
|
||||
};
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="组会报告 20250912 - Jiao77"
|
||||
description="关于RoRD改进、Geo-Layout-Transformer框架、西门子论坛及服务器脚本的进展总结"
|
||||
title={`${reportConfig.title} - Jiao77`}
|
||||
description={reportConfig.description}
|
||||
type="report"
|
||||
>
|
||||
<Header />
|
||||
<Header
|
||||
pageTitle="2025-09-12 组会报告"
|
||||
showPageTitle={true}
|
||||
description={reportConfig.subtitle}
|
||||
/>
|
||||
|
||||
<main class="report-main">
|
||||
<div class="container mx-auto px-4">
|
||||
<!-- 报告标题区域 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={200}>
|
||||
<Container variant="glass" size="full" padding="xl" className="text-center mb-12 mt-24">
|
||||
<div class="report-header">
|
||||
<h1 class="report-title">组会报告 - 2025年9月12日</h1>
|
||||
<p class="report-subtitle">RoRD改进、Geo-Layout-Transformer框架、西门子论坛及服务器脚本的综合进展总结</p>
|
||||
<div class="report-meta">
|
||||
<span class="report-date">报告日期:2025年9月12日</span>
|
||||
<span class="report-type">组会报告</span>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 项目进展概览 -->
|
||||
<ReportSection
|
||||
title="📊 项目进展概览"
|
||||
subtitle="本周期内各个研究方向的关键进展与技术突破汇总"
|
||||
level={2}
|
||||
>
|
||||
<MetricsGrid columns={2} gap="large">
|
||||
<MetricCard
|
||||
title="RoRD模型优化"
|
||||
value="85%"
|
||||
change="+12%"
|
||||
changeType="positive"
|
||||
icon="fas fa-sync-alt"
|
||||
color="primary"
|
||||
/>
|
||||
|
||||
<MetricCard
|
||||
title="Geo-Layout框架"
|
||||
value="70%"
|
||||
change="+25%"
|
||||
changeType="positive"
|
||||
icon="fas fa-project-diagram"
|
||||
color="success"
|
||||
/>
|
||||
|
||||
<MetricCard
|
||||
title="论坛成果整理"
|
||||
value="完成"
|
||||
icon="fas fa-users"
|
||||
color="info"
|
||||
/>
|
||||
|
||||
<MetricCard
|
||||
title="服务器脚本"
|
||||
value="部署中"
|
||||
change="新增"
|
||||
changeType="positive"
|
||||
icon="fas fa-server"
|
||||
color="warning"
|
||||
/>
|
||||
</MetricsGrid>
|
||||
</ReportSection>
|
||||
|
||||
<!-- RoRD改进工作 -->
|
||||
<ReportSection
|
||||
title="🔄 RoRD模型改进与优化"
|
||||
subtitle="针对版图识别精度和效率的关键技术改进"
|
||||
level={2}
|
||||
>
|
||||
<div class="improvement-sections">
|
||||
<div class="improvement-card">
|
||||
<h3 class="improvement-title">算法优化</h3>
|
||||
<ul class="improvement-list">
|
||||
<li>改进旋转鲁棒描述子的特征提取机制</li>
|
||||
<li>优化RANSAC几何验证算法的参数配置</li>
|
||||
<li>增强对小尺寸目标的检测精度</li>
|
||||
<li>减少计算复杂度,提升实时处理能力</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="improvement-card">
|
||||
<h3 class="improvement-title">性能提升</h3>
|
||||
<ul class="improvement-list">
|
||||
<li>匹配精度提升15%,达到业界先进水平</li>
|
||||
<li>处理速度优化30%,支持更大规模版图分析</li>
|
||||
<li>内存占用降低20%,适应资源受限环境</li>
|
||||
<li>支持批处理模式,提升工作流效率</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
|
||||
<!-- Geo-Layout-Transformer框架 -->
|
||||
<ReportSection
|
||||
title="🏗️ Geo-Layout-Transformer统一框架"
|
||||
subtitle="构建面向几何布局理解的Transformer统一基础模型"
|
||||
level={2}
|
||||
>
|
||||
<div class="framework-overview">
|
||||
<div class="framework-component">
|
||||
<div class="component-header">
|
||||
<span class="component-icon">🎯</span>
|
||||
<h3 class="component-title">架构设计</h3>
|
||||
</div>
|
||||
<p class="component-description">
|
||||
基于Transformer架构设计的统一几何布局理解框架,
|
||||
支持多种版图分析任务的端到端学习。
|
||||
</p>
|
||||
<div class="feature-tags">
|
||||
<span class="feature-tag">多任务学习</span>
|
||||
<span class="feature-tag">端到端训练</span>
|
||||
<span class="feature-tag">可扩展架构</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="framework-component">
|
||||
<div class="component-header">
|
||||
<span class="component-icon">🔧</span>
|
||||
<h3 class="component-title">技术特性</h3>
|
||||
</div>
|
||||
<p class="component-description">
|
||||
集成几何感知注意力机制、位置编码优化、
|
||||
多尺度特征融合等关键技术组件。
|
||||
</p>
|
||||
<div class="feature-tags">
|
||||
<span class="feature-tag">几何感知</span>
|
||||
<span class="feature-tag">位置编码</span>
|
||||
<span class="feature-tag">多尺度融合</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="framework-component">
|
||||
<div class="component-header">
|
||||
<span class="component-icon">📈</span>
|
||||
<h3 class="component-title">应用前景</h3>
|
||||
</div>
|
||||
<p class="component-description">
|
||||
面向版图识别、布局优化、缺陷检测等多个EDA应用场景,
|
||||
构建统一的AI基础设施。
|
||||
</p>
|
||||
<div class="feature-tags">
|
||||
<span class="feature-tag">版图识别</span>
|
||||
<span class="feature-tag">布局优化</span>
|
||||
<span class="feature-tag">缺陷检测</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
|
||||
<!-- 西门子EDA论坛成果 -->
|
||||
<ReportSection
|
||||
title="🏢 西门子EDA论坛参会成果"
|
||||
subtitle="SONR与SDPAL的AI良率提升实践经验与技术洞察"
|
||||
level={2}
|
||||
>
|
||||
<div class="forum-outcomes">
|
||||
<div class="outcome-section">
|
||||
<h3 class="outcome-title">核心收获</h3>
|
||||
<div class="outcome-grid">
|
||||
<div class="outcome-item">
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
<div>
|
||||
<h4>技术趋势</h4>
|
||||
<p>AI在EDA工具中的集成趋势和最佳实践</p>
|
||||
</div>
|
||||
<div class="report-layout container mx-auto px-4">
|
||||
<ReportSidebar title="报告目录" toggleLabel="目录" />
|
||||
<div class="report-content" data-report-content>
|
||||
|
||||
<!-- 报告标题区域 -->
|
||||
<section id="intro" class="report-header scroll-mt-16">
|
||||
<AnimatedElement animation="fadeInUp" delay={SECTION_DELAYS.HERO} trigger="load">
|
||||
<Container
|
||||
variant="glass"
|
||||
size="full"
|
||||
padding="xl"
|
||||
className="text-center mb-12 mt-24"
|
||||
>
|
||||
<h1 class="text-4xl md:text-5xl font-extrabold mb-4 bg-gradient-to-r from-blue-600 to-teal-600 bg-clip-text text-transparent">
|
||||
{reportConfig.title}
|
||||
</h1>
|
||||
<p class="text-xl text-gray-700 mb-8 max-w-3xl mx-auto">
|
||||
{reportConfig.subtitle}
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-6 text-sm text-gray-600 mb-8">
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
{reportConfig.date}
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
{reportConfig.type}
|
||||
</span>
|
||||
</div>
|
||||
<div class="outcome-item">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<div>
|
||||
<h4>良率提升</h4>
|
||||
<p>SONR和SDPAL在实际生产中的良率改善效果</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="outcome-item">
|
||||
<i class="fas fa-users"></i>
|
||||
<div>
|
||||
<h4>行业网络</h4>
|
||||
<p>与业界专家建立联系,拓展合作机会</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="outcome-item">
|
||||
<i class="fas fa-road"></i>
|
||||
<div>
|
||||
<h4>发展路径</h4>
|
||||
<p>明确技术发展方向和产业化路径</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
<a
|
||||
href={reportConfig.actionLink}
|
||||
class="inline-flex items-center gap-2 px-8 py-3 bg-gradient-to-r from-blue-600 to-teal-600 text-white font-semibold rounded-full hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-300"
|
||||
>
|
||||
{reportConfig.actionText}
|
||||
</a>
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
</section>
|
||||
|
||||
<!-- 服务器脚本与工具 -->
|
||||
<ReportSection
|
||||
title="⚙️ 服务器脚本与自动化工具"
|
||||
subtitle="提升研发效率的基础设施建设与自动化工具开发"
|
||||
level={2}
|
||||
>
|
||||
<div class="tools-overview">
|
||||
<div class="tool-category">
|
||||
<h3 class="category-title">数据处理脚本</h3>
|
||||
<ul class="tool-list">
|
||||
<li>版图数据预处理自动化脚本</li>
|
||||
<li>训练数据增强与标注工具</li>
|
||||
<li>实验结果统计分析脚本</li>
|
||||
<li>数据可视化生成工具</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tool-category">
|
||||
<h3 class="category-title">模型训练工具</h3>
|
||||
<ul class="tool-list">
|
||||
<li>分布式训练任务调度系统</li>
|
||||
<li>超参数自动优化框架</li>
|
||||
<li>模型性能监控面板</li>
|
||||
<li>实验版本管理工具</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tool-category">
|
||||
<h3 class="category-title">部署运维脚本</h3>
|
||||
<ul class="tool-list">
|
||||
<li>模型服务自动部署脚本</li>
|
||||
<li>系统健康监控告警</li>
|
||||
<li>日志收集分析工具</li>
|
||||
<li>性能基准测试套件</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
|
||||
<!-- 下一步计划 -->
|
||||
<ReportSection
|
||||
title="🚀 下一步工作计划"
|
||||
subtitle="未来两周的重点任务与里程碑目标"
|
||||
level={2}
|
||||
>
|
||||
<div class="plan-timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker"></div>
|
||||
<div class="timeline-content">
|
||||
<h3 class="timeline-title">第一周目标</h3>
|
||||
<ul class="timeline-tasks">
|
||||
<li>完成RoRD模型的最终优化与性能验证</li>
|
||||
<li>提交Geo-Layout-Transformer框架的初版论文</li>
|
||||
<li>整理西门子论坛的技术报告文档</li>
|
||||
<!-- RoRD 改进 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={SECTION_DELAYS.RORD} trigger="scroll">
|
||||
<section id="rord">
|
||||
<ReportSection
|
||||
title="🔧 RoRD-Layout-Recognation 的改进"
|
||||
subtitle="代码健壮性和易用性提升"
|
||||
level={2}
|
||||
>
|
||||
<p class="text-gray-700 mb-4">主要改进体现在代码健壮性和易用性上:</p>
|
||||
<ul class="space-y-3 text-gray-600">
|
||||
<li class="flex items-start gap-3">
|
||||
<i class="fas fa-check-circle text-blue-600 mt-1"></i>
|
||||
<div>
|
||||
<strong class="text-gray-900">完善了训练策略:</strong>
|
||||
现在训练不再需要对数据集进行预标注。Trainer 会在训练过程中自动生成正样本和负样本,并动态保持正负样本的平衡,可直接使用原始数据 (raw data) 进行训练。
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex items-start gap-3">
|
||||
<i class="fas fa-check-circle text-blue-600 mt-1"></i>
|
||||
<div>
|
||||
<strong class="text-gray-900">完善了说明文档:</strong>
|
||||
更新了项目的 Git 仓库中的 README 文件,提供了更详细的设置和使用说明。详情请访问:
|
||||
<a href="http://jiao77.cn:3012/Jiao77/RoRD-Layout-Recognation" target="_blank" class="text-blue-600 hover:underline ml-1">
|
||||
RoRD-Layout-Recognation
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-marker"></div>
|
||||
<div class="timeline-content">
|
||||
<h3 class="timeline-title">第二周目标</h3>
|
||||
<ul class="timeline-tasks">
|
||||
<li>开展大规模版图数据集的验证实验</li>
|
||||
<li>完善自动化工具的文档与用户指南</li>
|
||||
<li>准备下一阶段的项目展示材料</li>
|
||||
<!-- Geo-Layout-Transformer -->
|
||||
<AnimatedElement animation="fadeInUp" delay={SECTION_DELAYS.GEO} trigger="scroll">
|
||||
<section id="geo-transformer">
|
||||
<ReportSection
|
||||
title="🏗️ Geo-Layout-Transformer"
|
||||
subtitle="基于 GNN-Transformer 的版图分析大模型"
|
||||
level={2}
|
||||
>
|
||||
<p class="text-gray-700 mb-4">
|
||||
这是一个旨在利用 GNN-Transformer 架构模仿 ViT (Vision Transformer) 进行集成电路版图分析的大模型构想。
|
||||
</p>
|
||||
<ul class="space-y-3 text-gray-600">
|
||||
<li class="flex items-start gap-3">
|
||||
<i class="fas fa-file-alt text-teal-600 mt-1"></i>
|
||||
<div>
|
||||
<strong class="text-gray-900">技术报告:</strong>
|
||||
详细的技术构想和设计已撰写成报告,可在此处查阅:
|
||||
<a href="http://jiao77.cn/report/Geo-Layout-Transformer/" target="_blank" class="text-blue-600 hover:underline ml-1">
|
||||
技术报告
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex items-start gap-3">
|
||||
<i class="fas fa-code text-teal-600 mt-1"></i>
|
||||
<div>
|
||||
<strong class="text-gray-900">代码框架:</strong>
|
||||
已完成 Geo-Layout-Transformer 的代码骨架搭建,明确了待办事项 (to-dos),但具体实现暂用空函数占位。代码仓库:
|
||||
<a href="http://jiao77.cn:3012/Jiao77/Geo-Layout-Transformer" target="_blank" class="text-blue-600 hover:underline ml-1">
|
||||
Geo-Layout-Transformer
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- Siemens EDA Forum -->
|
||||
<AnimatedElement animation="fadeInUp" delay={SECTION_DELAYS.SIEMENS} trigger="scroll">
|
||||
<section id="siemens">
|
||||
<ReportSection
|
||||
title="📊 Siemens EDA Forum"
|
||||
subtitle="上海 Siemens EDA Forum 参会总结"
|
||||
level={2}
|
||||
>
|
||||
<p class="text-gray-700 mb-6">
|
||||
于8月28日参加了在上海举办的 Siemens EDA Forum。这是一个商业性大于技术性的论坛,但从中也了解到了产业界关于 AI-EDA 的一些最新动向,特别是关于良率提升的部分。
|
||||
</p>
|
||||
|
||||
<div class="my-8">
|
||||
<ImageViewer
|
||||
src={images.siemens}
|
||||
alt="Siemens EDA Forum Presentation"
|
||||
caption="Siemens 关于良率提升部分的演讲内容"
|
||||
width="800px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-700 mb-4">
|
||||
详细的技术报告整理如下:
|
||||
<a href="http://jiao77.cn/report/Siemens-EDA-Forum/" target="_blank" class="text-blue-600 hover:underline ml-1">
|
||||
技术报告
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p class="text-gray-700 mt-6 italic mb-4">另外,他们的茶点非常好吃。</p>
|
||||
|
||||
<div class="my-6 max-w-sm mx-auto">
|
||||
<ImageViewer
|
||||
src={images.food}
|
||||
alt="Forum Refreshments"
|
||||
caption=""
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 服务器脚本 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={SECTION_DELAYS.SCRIPTS} trigger="scroll">
|
||||
<section id="scripts">
|
||||
<ReportSection
|
||||
title="⚙️ 服务器脚本"
|
||||
subtitle="提升服务器稳定性和易用性的自动化脚本"
|
||||
level={2}
|
||||
>
|
||||
<p class="text-gray-700 mb-6">为提升服务器的稳定性和易用性,编写了以下几个自动化脚本:</p>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- NVIDIA 脚本 -->
|
||||
<Container variant="glass" padding="lg" className="border-l-4 border-blue-500">
|
||||
<h3 class="font-bold text-lg text-gray-800 mb-3">NVIDIA 库定时检查和重连接脚本</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
解决了因 <code class="px-2 py-1 bg-gray-100 rounded text-sm">nvidia-smi</code> 版本与内核驱动版本不一致(575 vs 570)而频繁失效的问题。脚本以10分钟为周期检查 <code class="px-2 py-1 bg-gray-100 rounded text-sm">nvidia-smi</code> 状态,若发现问题则自动重连接到正确的 570 库版本。
|
||||
</p>
|
||||
<div class="my-4">
|
||||
<ImageViewer
|
||||
src={images.nvidia}
|
||||
alt="NVIDIA script output"
|
||||
caption=""
|
||||
width="600px"
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<!-- ZJUnet 脚本 -->
|
||||
<Container variant="glass" padding="lg" className="border-l-4 border-teal-500">
|
||||
<h3 class="font-bold text-lg text-gray-800 mb-3">ZJUnet 自动化检测重连脚本</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
解决了服务器 ZJUnet 拨号上网间歇性断连的问题。脚本会定时测试网络连接,一旦失败则自动执行重连命令以恢复网络。
|
||||
</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-4">
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.zjunet1}
|
||||
alt="ZJUnet reconnecting"
|
||||
caption="网络测试失败后重连"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.zjunet2}
|
||||
alt="ZJUnet connection success"
|
||||
caption="网络测试成功"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<!-- MDView 脚本 -->
|
||||
<Container variant="glass" padding="lg" className="border-l-4 border-purple-500">
|
||||
<h3 class="font-bold text-lg text-gray-800 mb-3">MDView 脚本</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
一个有趣的小工具,利用 <strong class="text-purple-600">glow</strong> 和 <strong class="text-purple-600">chafa</strong> 两个成熟的终端工具,实现在终端内浏览 Markdown 文件,并能正确渲染标题、表格、列表、代码块,甚至包括图片。
|
||||
</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-4">
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.mdview.title}
|
||||
alt="MDView Title"
|
||||
caption="标题渲染"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.mdview.table}
|
||||
alt="MDView Table"
|
||||
caption="表格渲染"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.mdview.list1}
|
||||
alt="MDView List 1"
|
||||
caption="列表渲染 1"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.mdview.list2}
|
||||
alt="MDView List 2"
|
||||
caption="列表渲染 2"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.mdview.image}
|
||||
alt="MDView Image"
|
||||
caption="图片渲染"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ImageViewer
|
||||
src={images.mdview.link}
|
||||
alt="MDView Hyperlink"
|
||||
caption="超链接渲染"
|
||||
width="400px"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<ImageViewer
|
||||
src={images.mdview.code}
|
||||
alt="MDView Code"
|
||||
caption="代码块渲染"
|
||||
width="800px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -282,15 +347,21 @@ import AnimatedElement from '../../../components/AnimatedElement.astro';
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.report-main {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
/* 报告布局样式 */
|
||||
.report-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
@@ -301,307 +372,23 @@ import AnimatedElement from '../../../components/AnimatedElement.astro';
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: #011a2d;
|
||||
margin: 0 0 1rem 0;
|
||||
background: linear-gradient(135deg, #011a2d, #2c4a6b, #5b778e);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: #2c4a6b;
|
||||
margin: 0 0 2rem 0;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.report-date,
|
||||
.report-type {
|
||||
background: rgba(91, 119, 142, 0.1);
|
||||
color: #011a2d;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.improvement-sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.improvement-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(91, 119, 142, 0.2);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.improvement-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.improvement-list {
|
||||
|
||||
/* 列表样式 */
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.improvement-list li {
|
||||
background: rgba(91, 119, 142, 0.05);
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid #5b778e;
|
||||
|
||||
/* 代码样式 */
|
||||
code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.framework-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.framework-component {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(91, 119, 142, 0.2);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.component-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.component-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.component-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.component-description {
|
||||
color: #2c4a6b;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.feature-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
background: rgba(91, 119, 142, 0.1);
|
||||
color: #011a2d;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.forum-outcomes {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.outcome-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
margin: 0 0 1.5rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.outcome-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.outcome-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(91, 119, 142, 0.2);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.outcome-item i {
|
||||
font-size: 1.5rem;
|
||||
color: #5b778e;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.outcome-item h4 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.outcome-item p {
|
||||
color: #2c4a6b;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tools-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.tool-category {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(91, 119, 142, 0.2);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
margin: 0 0 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tool-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tool-list li {
|
||||
background: rgba(91, 119, 142, 0.05);
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid #5b778e;
|
||||
}
|
||||
|
||||
.plan-timeline {
|
||||
margin: 2rem 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.plan-timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(to bottom, #5b778e, #b2c5d5);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 2rem;
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.timeline-marker {
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
top: 0.5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background: #5b778e;
|
||||
border-radius: 50%;
|
||||
border: 3px solid white;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #011a2d;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.timeline-tasks {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.timeline-tasks li {
|
||||
background: rgba(91, 119, 142, 0.05);
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid #5b778e;
|
||||
}
|
||||
|
||||
|
||||
/* 响应式网格 */
|
||||
@media (max-width: 768px) {
|
||||
.report-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.improvement-sections,
|
||||
.framework-overview,
|
||||
.tools-overview {
|
||||
.grid-cols-1.md\\:grid-cols-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.outcome-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.plan-timeline::before {
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.timeline-marker {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
167
src/pages/report/20250928/RoRD-featurework.md
Normal file
167
src/pages/report/20250928/RoRD-featurework.md
Normal file
@@ -0,0 +1,167 @@
|
||||
<!-- report/20250928/RoRD-featurework.md -->
|
||||
# 后续工作
|
||||
|
||||
本文档整合了 RoRD 项目的优化待办清单和训练需求,用于规划未来的开发和实验工作。
|
||||
|
||||
---
|
||||
|
||||
## RoRD 项目优化待办清单
|
||||
|
||||
本文档旨在为 RoRD (Rotation-Robust Descriptors) 项目提供一系列可行的优化任务。各项任务按优先级和模块划分,您可以根据项目进度和资源情况选择执行。
|
||||
|
||||
### 一、 数据策略与增强 (Data Strategy & Augmentation)
|
||||
|
||||
> *目标:提升模型的鲁棒性和泛化能力,减少对大量真实数据的依赖。*
|
||||
|
||||
- [ ] **引入弹性变形 (Elastic Transformations)**
|
||||
- **✔️ 价值**: 模拟芯片制造中可能出现的微小物理形变,使模型对非刚性变化更鲁棒。
|
||||
- **📝 执行方案**:
|
||||
1. 添加 `albumentations` 库作为项目依赖。
|
||||
2. 在 `train.py` 的 `ICLayoutTrainingDataset` 类中,集成 `A.ElasticTransform` 到数据增强管道中。
|
||||
- [ ] **创建合成版图数据生成器**
|
||||
- **✔️ 价值**: 解决真实版图数据获取难、数量少的问题,通过程序化生成大量多样化的训练样本。
|
||||
- **📝 执行方案**:
|
||||
1. 创建一个新脚本,例如 `tools/generate_synthetic_layouts.py`。
|
||||
2. 利用 `gdstk` 库 编写函数,程序化地生成包含不同尺寸、密度和类型标准单元的 GDSII 文件。
|
||||
3. 结合 `tools/layout2png.py` 的逻辑,将生成的版图批量转换为 PNG 图像,用于扩充训练集。
|
||||
|
||||
### 二、 模型架构 (Model Architecture)
|
||||
|
||||
> *目标:提升模型的特征提取效率和精度,降低计算资源消耗。*
|
||||
|
||||
- [ ] **实验更现代的骨干网络 (Backbone)**
|
||||
- **✔️ 价值**: VGG-16 经典但效率偏低。新架构(如 ResNet, EfficientNet)能以更少的参数量和计算量达到更好的性能。
|
||||
- **📝 执行方案**:
|
||||
1. 在 `models/rord.py` 中,修改 `RoRD` 类的 `__init__` 方法。
|
||||
2. 使用 `torchvision.models` 替换 `vgg16`。可尝试 `models.resnet34(pretrained=True)` 或 `models.efficientnet_b0(pretrained=True)` 作为替代方案。
|
||||
3. 相应地调整检测头和描述子头的输入通道数。
|
||||
- [ ] **集成注意力机制 (Attention Mechanism)**
|
||||
- **✔️ 价值**: 引导模型自动关注版图中的关键几何结构(如边角、交点),忽略大面积的空白或重复区域,提升特征质量。
|
||||
- **📝 执行方案**:
|
||||
1. 寻找一个可靠的注意力模块实现,如 CBAM 或 SE-Net。
|
||||
2. 在 `models/rord.py` 中,将该模块插入到 `self.backbone` 和两个 `head` 之间。
|
||||
|
||||
### 三、 训练与损失函数 (Training & Loss Function)
|
||||
|
||||
> *目标:优化训练过程的稳定性,提升模型收敛效果。*
|
||||
|
||||
- [ ] **实现损失函数的自动加权**
|
||||
- **✔️ 价值**: 当前检测损失和描述子损失是等权重相加,手动调参困难。自动加权可以使模型自主地平衡不同任务的优化难度。
|
||||
- **📝 执行方案**:
|
||||
1. 参考学术界关于“多任务学习中的不确定性加权” (Uncertainty Weighting) 的论文。
|
||||
2. 在 `train.py` 中,将损失权重定义为两个可学习的参数 `log_var_a` 和 `log_var_b`。
|
||||
3. 将总损失函数修改为 `loss = torch.exp(-log_var_a) * det_loss + log_var_a + torch.exp(-log_var_b) * desc_loss + log_var_b`。
|
||||
4. 将这两个新参数加入到优化器中进行训练。
|
||||
- [ ] **实现基于关键点响应的困难样本采样**
|
||||
- **✔️ 价值**: 提升描述子学习的效率。只在模型认为是“关键点”的区域进行采样,能让模型更专注于学习有区分度的特征。
|
||||
- **📝 执行方案**:
|
||||
1. 在 `train.py` 的 `compute_description_loss` 函数中。
|
||||
2. 获取 `det_original` 的输出图,进行阈值处理或 Top-K 选择,得到关键点的位置坐标。
|
||||
3. 使用这些坐标,而不是 `torch.linspace` 生成的网格坐标,作为采样点来提取 `anchor`、`positive` 和 `negative` 描述子。
|
||||
|
||||
### 四、 推理与匹配 (Inference & Matching)
|
||||
|
||||
> *目标:大幅提升大尺寸版图的匹配速度和多尺度检测能力。*
|
||||
|
||||
- [x] **将模型改造为特征金字塔网络 (FPN) 架构**
|
||||
- **✔️ 价值**: 当前的多尺度匹配需要多次缩放图像并推理,速度慢。FPN 只需一次推理即可获得所有尺度的特征,极大加速匹配过程。
|
||||
- **📝 执行方案**:
|
||||
1. 修改 `models/rord.py`,从骨干网络的不同层级(如 VGG 的 `relu2_2`, `relu3_3`, `relu4_3`)提取特征图。
|
||||
2. 添加上采样和横向连接层来融合这些特征图,构建出特征金字塔。
|
||||
3. 修改 `match.py`,使其能够直接从 FPN 的不同层级获取特征,替代原有的图像金字塔循环。
|
||||
- [x] **在滑动窗口匹配后增加关键点去重**
|
||||
- **✔️ 价值**: `match.py` 中的滑动窗口在重叠区域会产生大量重复的关键点,增加后续匹配的计算量并可能影响精度。
|
||||
- **📝 执行方案**:
|
||||
1. 在 `match.py` 的 `extract_features_sliding_window` 函数返回前。
|
||||
2. 实现一个非极大值抑制 (NMS) 算法。
|
||||
3. 根据关键点的位置和检测分数(需要模型输出强度图),对 `all_kps` 和 `all_descs` 进行过滤,去除冗余点。
|
||||
|
||||
### 五、 代码与项目结构 (Code & Project Structure)
|
||||
|
||||
> *目标:提升项目的可维护性、可扩展性和易用性。*
|
||||
|
||||
- [x] **迁移配置到 YAML 文件**
|
||||
- **✔️ 价值**: `config.py` 不利于管理多组实验配置。YAML 文件能让每组实验的参数独立、清晰,便于复现。
|
||||
- **📝 执行方案**:
|
||||
1. 创建一个 `configs` 目录,并编写一个 `base_config.yaml` 文件。
|
||||
2. 引入 `OmegaConf` 或 `Hydra` 库。
|
||||
3. 修改 `train.py` 和 `match.py` 等脚本,使其从 YAML 文件加载配置,而不是从 `config.py` 导入。
|
||||
- [x] **代码模块解耦**
|
||||
- **✔️ 价值**: `train.py` 文件过长,职责过多。解耦能使代码结构更清晰,符合单一职责原则。
|
||||
- **📝 执行方案**:
|
||||
1. 将 `ICLayoutTrainingDataset` 类从 `train.py` 移动到 `data/ic_dataset.py`。
|
||||
2. 创建一个新文件 `losses.py`,将 `compute_detection_loss` 和 `compute_description_loss` 函数移入其中。
|
||||
|
||||
### 六、 实验跟踪与评估 (Experiment Tracking & Evaluation)
|
||||
|
||||
> *目标:建立科学的实验流程,提供更全面的模型性能度量。*
|
||||
|
||||
- [x] **集成实验跟踪工具 (TensorBoard / W&B)**
|
||||
- **✔️ 价值**: 日志文件不利于直观对比实验结果。可视化工具可以实时监控、比较多组实验的损失和评估指标。
|
||||
- **📝 执行方案**:
|
||||
1. 在 `train.py` 中,导入 `torch.utils.tensorboard.SummaryWriter`。
|
||||
2. 在训练循环中,使用 `writer.add_scalar()` 记录各项损失值。
|
||||
3. 在验证结束后,记录评估指标和学习率等信息。
|
||||
- [x] **增加更全面的评估指标**
|
||||
- **✔️ 价值**: 当前的评估指标 主要关注检测框的重合度。增加 mAP 和几何误差评估能更全面地衡量模型性能。
|
||||
- **📝 执行方案**:
|
||||
1. 在 `evaluate.py` 中,实现 mAP (mean Average Precision) 的计算逻辑。
|
||||
2. 在计算 IoU 匹配成功后,从 `match_template_multiscale` 返回的单应性矩阵 `H` 中,分解出旋转/平移等几何参数,并与真实变换进行比较,计算误差。
|
||||
|
||||
---
|
||||
|
||||
## 训练需求
|
||||
|
||||
### 1. 数据集类型
|
||||
|
||||
* **格式**: 训练数据为PNG格式的集成电路 (IC) 版图图像。这些图像可以是二值化的黑白图,也可以是灰度图。
|
||||
* **来源**: 可以从 GDSII (.gds) 或 OASIS (.oas) 版图文件通过光栅化生成。
|
||||
* **内容**: 数据集应包含多种不同区域、不同风格的版图,以确保模型的泛化能力。
|
||||
* **标注**: **训练阶段无需任何人工标注**。模型采用自监督学习,通过对原图进行旋转、镜像等几何变换来自动生成训练对。
|
||||
|
||||
### 2. 数据集大小
|
||||
|
||||
* **启动阶段 (功能验证)**: **100 - 200 张** 高分辨率 (例如:2048x2048) 的版图图像。这个规模足以验证训练流程是否能跑通,损失函数是否收敛。
|
||||
* **初步可用模型**: **1,000 - 2,000 张** 版图图像。在这个数量级上,模型能学习到比较鲁棒的几何特征,在与训练数据相似的版图上取得不错的效果。
|
||||
* **生产级模型**: **5,000 - 10,000+ 张** 版图图像。要让模型在各种不同工艺、设计风格的版图上都具有良好的泛化能力,需要大规模、多样化的数据集。
|
||||
|
||||
训练脚本 `train.py` 会将提供的数据集自动按 80/20 的比例划分为训练集和验证集。
|
||||
|
||||
### 3. 计算资源
|
||||
|
||||
* **硬件**: **一块支持 CUDA 的 NVIDIA GPU 是必需的**。考虑到模型的 VGG-16 骨干网络和复杂的几何感知损失函数,使用中高端 GPU 会显著提升训练效率。
|
||||
* **推荐型号**:
|
||||
* **入门级**: NVIDIA RTX 3060 / 4060
|
||||
* **主流级**: NVIDIA RTX 3080 / 4070 / A4000
|
||||
* **专业级**: NVIDIA RTX 3090 / 4090 / A6000
|
||||
* **CPU 与内存**: 建议至少 8 核 CPU 和 32 GB 内存,以确保数据预处理和加载不会成为瓶颈。
|
||||
|
||||
### 4. 显存大小 (VRAM)
|
||||
|
||||
根据配置文件 `config.py` 和 `train.py` 中的参数,可以估算所需显存:
|
||||
|
||||
* **模型架构**: 基于 VGG-16。
|
||||
* **批次大小 (Batch Size)**: 默认为 8。
|
||||
* **图像块大小 (Patch Size)**: 256x256。
|
||||
|
||||
综合以上参数,并考虑到梯度和优化器状态的存储开销,**建议至少需要 12 GB 显存**。如果显存不足,需要将 `BATCH_SIZE` 减小 (例如 4 或 2),但这会牺牲训练速度和稳定性。
|
||||
|
||||
### 5. 训练时间估算
|
||||
|
||||
假设使用一块 **NVIDIA RTX 3080 (10GB)** 显卡和 **2,000 张** 版图图像的数据集:
|
||||
|
||||
* **单个 Epoch 时间**: 约 15 - 25 分钟。
|
||||
* **总训练时间**: 配置文件中设置的总轮数 (Epochs) 为 50。
|
||||
* `50 epochs * 20 分钟/epoch ≈ 16.7 小时`
|
||||
* **收敛时间**: 项目引入了早停机制 (patience=10),如果验证集损失在 10 个 epoch 内没有改善,训练会提前停止。因此,实际训练时间可能在 **10 到 20 小时** 之间。
|
||||
|
||||
### 6. 逐步调优时间
|
||||
|
||||
调优是一个迭代过程,非常耗时。根据 `TRAINING_STRATEGY_ANALYSIS.md` 文件中提到的优化点 和进一步优化建议,调优阶段可能包括:
|
||||
|
||||
* **数据增强策略探索 (1-2周)**: 调整尺度抖动范围、亮度和对比度参数,尝试不同的噪声类型等。
|
||||
* **损失函数权重平衡 (1-2周)**: `loss_function.md` 中提到了多种损失分量(BCE, SmoothL1, Triplet, Manhattan, Sparsity, Binary),调整它们之间的权重对模型性能至关重要。
|
||||
* **超参数搜索 (2-4周)**: 对学习率、批次大小、优化器类型 (Adam, SGD等)、学习率调度策略等进行网格搜索或贝叶斯优化。
|
||||
* **模型架构微调 (可选,2-4周)**: 尝试不同的骨干网络 (如 ResNet)、修改检测头和描述子头的层数或通道数。
|
||||
|
||||
**总计,要达到一个稳定、可靠、泛化能力强的生产级模型,从数据准备到最终调优完成,预计需要 1 个半到 3 个月的时间。**
|
||||
46
src/pages/report/20250928/RoRD-paper-plan.md
Normal file
46
src/pages/report/20250928/RoRD-paper-plan.md
Normal file
@@ -0,0 +1,46 @@
|
||||
<!-- report/20250928/RoRD-paper-plan.md -->
|
||||
好的,这是从您提供的 PDF 文件中识别出的 Markdown 格式内容:
|
||||
|
||||
### 会议信息
|
||||
|
||||
* **ICCAD (International Conference on Computer-Aided Design)**
|
||||
* [cite_start]投稿截止时间: 每年5月中下旬 [cite: 1]
|
||||
* [cite_start]审核结束时间(结果通知): 同年8月上旬 [cite: 1]
|
||||
* [cite_start]会议召开时间: 同年10月底或11月初 [cite: 1]
|
||||
* **DAC (Design Automation Conference)**
|
||||
* [cite_start]投稿截止时间: 每年11月中下旬 [cite: 1]
|
||||
* [cite_start]审核结束时间(结果通知): 第二年2月底或3月初 [cite: 1]
|
||||
* [cite_start]会议召开时间: 第二年6月或7月 [cite: 1]
|
||||
* **ASP-DAC (Asia and South Pacific Design Automation Conference)**
|
||||
* [cite_start]投稿截止时间: 每年7月中旬 [cite: 1]
|
||||
* [cite_start]审核结束时间(结果通知): 同年10月中下旬 [cite: 1]
|
||||
* [cite_start]会议召开时间: 第二年1月下旬 [cite: 1]
|
||||
* **DATE (Design, Automation & Test in Europe)**
|
||||
* [cite_start]投稿截止时间: 每年9月中旬 [cite: 1]
|
||||
* [cite_start]审核结束时间(结果通知): 同年12月中旬 [cite: 1]
|
||||
* [cite_start]会议召开时间: 第二年3月或4月 [cite: 1]
|
||||
|
||||
---
|
||||
|
||||
### 投稿计划
|
||||
|
||||
[cite_start]基于以上的时间信息, 我认为明年四月前完成论文比较合适, 这样对这些会议投稿的安排可以按照下面这个计划: [cite: 1]
|
||||
|
||||
**第一次尝试 (2026年春季)**
|
||||
|
||||
[cite_start]我们的论文四月完稿, 正好可以赶上ICCAD 2026的投稿(5月中下旬截止)。 [cite: 1] [cite_start]我们可以把这次作为首要目标, 去争取一下。 [cite: 1] [cite_start]根据第一部分的时间点, 我们将在8月上旬拿到评审结果, 这为后续的修改和决策留出了充足的时间。 [cite: 1]
|
||||
|
||||
**第二次尝试 (2026年秋季)**
|
||||
|
||||
[cite_start]如果ICCAD的结果不理想, 我们会在8月份拿到评审意见。 [cite: 1] [cite_start]我的想法是, 利用8月到11月这三个月的时间, 根据意见对论文进行一次深入的修改和完善。 [cite: 1] [cite_start]然后, 我们可以分为两个计划: [cite: 1]
|
||||
|
||||
* [cite_start]**Plan A**: 尝试一下DATE 2027 (9月截止)。 [cite: 1] [cite_start]相对来说时间比较赶, 只有一个星期, 如果5-8月有明显改进的话可以尝试这一方案。 [cite: 1]
|
||||
* [cite_start]**Plan B**: 投稿 DAC 2027 (11月中下旬截止)。 [cite: 1] [cite_start]三个月的修改时间相对比较从容, 足够我们补上一些关键实验, 提升论文的竞争力。 [cite: 1]
|
||||
|
||||
**第三次尝试 (2027年春季-夏季)**
|
||||
|
||||
[cite_start]假如DAC之后还需要继续打磨, 我们大概会在2027年3月初知道结果。 [cite: 1] [cite_start]届时, 论文已经有了两轮顶级会议的反馈, 质量会更高。 [cite: 1] [cite_start]我们可以利用3月到7月这段时间, 进行再次修改, 然后投稿 ASP-DAC 2028 (7月中旬截止)。 [cite: 1]
|
||||
|
||||
**后续计划**
|
||||
|
||||
[cite_start]如果经过上面几轮, 论文还未能发表, 我觉得也不用担心。 [cite: 2] [cite_start]到那个时候, 这篇论文经过了这么多轮的修改, 内容应该已经非常扎实和全面了。 [cite: 2] [cite_start]我们可以考虑将其整理成一篇更完整的文章, 直接投稿到期刊, 比如 IEEE TCAD。 [cite: 2]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
247
src/pages/report/ai-eda-paper-report/README.md
Normal file
247
src/pages/report/ai-eda-paper-report/README.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# 交互式EDA/CAD学术发表指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
这是一个基于《学术发表的投稿难度与周期分析》报告构建的交互式指南页面,帮助研究人员制定最佳投稿策略。
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
### 1. 智能筛选系统
|
||||
- **类型筛选**:会议 / 期刊
|
||||
- **难度等级筛选**:顶级 / 高 / 应用/其他
|
||||
- **评审速度筛选**:快速(<10周) / 中等(10-15周) / 慢速(>15周)
|
||||
- **一键重置**:快速清除所有筛选条件
|
||||
|
||||
### 2. 场所卡片展示
|
||||
包含7个主要发表场所:
|
||||
- **DAC** - Design Automation Conference (顶级会议)
|
||||
- **ICCAD** - International Conference on Computer-Aided Design (顶级会议)
|
||||
- **DATE** - Design, Automation and Test in Europe (高级会议)
|
||||
- **ASP-DAC** - Asia and South Pacific Design Automation Conference (高级会议)
|
||||
- **IEEE TCAD** - Transactions on Computer-Aided Design (顶级期刊)
|
||||
- **ACM TODAES** - Transactions on Design Automation of Electronic Systems (高级期刊)
|
||||
- **IEEE D&T** - Design & Test (应用期刊)
|
||||
|
||||
每个卡片显示:
|
||||
- 🏆 难度等级
|
||||
- 📊 接收率
|
||||
- ⏱️ 评审速度
|
||||
- ⭐ 关键指标
|
||||
|
||||
### 3. 交互式模态框
|
||||
- **GSAP动画**:卡片到模态框的流畅展开动画
|
||||
- **详细信息展示**:
|
||||
- 关键信息(难度、主办方、页数限制、投稿指南)
|
||||
- 评审过程特点
|
||||
- 可视化图表(Chart.js)
|
||||
- 典型发表时间线
|
||||
- **降级方案**:GSAP未加载时使用简化模态框
|
||||
|
||||
### 4. 投稿策略指南
|
||||
手风琴式折叠面板,包含:
|
||||
- 如何理解投稿难度?
|
||||
- 如何规划发表时间线?
|
||||
- 如何制定投稿策略?
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
### 框架与组件
|
||||
- **Astro** - 主框架
|
||||
- **莫兰蒂蓝色系** - 主题色 `#2c4a6b`
|
||||
- **Container组件** - 玻璃态容器
|
||||
- **AnimatedElement组件** - 滚动动画
|
||||
|
||||
### 外部依赖
|
||||
- **Chart.js 4.4.1** - 数据可视化图表
|
||||
- **GSAP 3.13** - 模态框动画效果
|
||||
- **Tailwind CSS** - 样式框架
|
||||
|
||||
### 自定义脚本
|
||||
`./eda-venues-interactive.ts`(本地脚本)
|
||||
- TypeScript编写
|
||||
- 完整类型定义
|
||||
- 模块化设计
|
||||
- 降级兼容处理
|
||||
|
||||
## 📂 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── pages/report/ai-eda-paper-report/
|
||||
│ ├── index.astro # 主页面文件
|
||||
│ ├── eda-venues-interactive.ts # 交互逻辑脚本(页面专用)
|
||||
│ └── README.md # 文档说明
|
||||
└── components/
|
||||
├── Container.astro # 容器组件
|
||||
├── AnimatedElement.astro # 动画组件
|
||||
├── Header.astro # 页头组件
|
||||
└── Footer.astro # 页脚组件
|
||||
```
|
||||
|
||||
## 🎨 设计特点
|
||||
|
||||
### 1. 莫兰蒂蓝色系
|
||||
- 主色:`#2c4a6b`
|
||||
- 强调色:`#011a2d`(深蓝)
|
||||
- 辅助色:`#5b778e`(浅蓝)
|
||||
|
||||
### 2. 玻璃态效果
|
||||
```css
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(44, 74, 107, 0.1);
|
||||
```
|
||||
|
||||
### 3. 响应式布局
|
||||
- 桌面:3列网格
|
||||
- 平板:2列网格
|
||||
- 移动:单列网格
|
||||
|
||||
### 4. 动画时序
|
||||
- Hero区域:200ms
|
||||
- 筛选器:400ms
|
||||
- 场所卡片:600ms
|
||||
- 策略指南:800ms
|
||||
|
||||
## 🚀 使用方式
|
||||
|
||||
### 访问路径
|
||||
```
|
||||
/report/ai-eda-paper-report
|
||||
```
|
||||
|
||||
### 本地开发
|
||||
```bash
|
||||
npm run dev
|
||||
# 访问 http://localhost:4321/report/ai-eda-paper-report
|
||||
```
|
||||
|
||||
### 生产构建
|
||||
```bash
|
||||
npm run build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## 📊 数据结构
|
||||
|
||||
### VenueData接口
|
||||
```typescript
|
||||
interface VenueData {
|
||||
id: string;
|
||||
name: string;
|
||||
fullName: string;
|
||||
type: '会议' | '期刊';
|
||||
tier: '顶级' | '高' | '应用/其他';
|
||||
speed: 'fast' | 'medium' | 'slow';
|
||||
speedValue: number;
|
||||
acceptanceRate: string;
|
||||
acceptanceValue: number | null;
|
||||
keyMetric: string;
|
||||
pageLimit: string;
|
||||
sponsors: string;
|
||||
overview: string;
|
||||
reviewProcess: string[];
|
||||
timeline: TimelineStage[];
|
||||
guidelines: string;
|
||||
}
|
||||
```
|
||||
|
||||
### StrategyData接口
|
||||
```typescript
|
||||
interface StrategyData {
|
||||
title: string;
|
||||
content: string; // HTML内容
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 核心函数
|
||||
|
||||
### 主要功能函数
|
||||
- `renderVenues()` - 渲染场所卡片
|
||||
- `openModal(venueId, cardElement)` - 打开模态框(GSAP动画)
|
||||
- `openSimpleModal(venueId)` - 简化模态框(降级方案)
|
||||
- `closeModal()` - 关闭模态框
|
||||
- `renderStrategyAccordion()` - 渲染策略手风琴
|
||||
- `initEDAVenuesInteractive()` - 初始化所有交互
|
||||
|
||||
## ⚙️ 配置选项
|
||||
|
||||
### 模态框尺寸(响应式)
|
||||
```typescript
|
||||
// 大屏幕 (≥1024px)
|
||||
targetW = '70vw';
|
||||
targetH = '70vh';
|
||||
|
||||
// 中等屏幕
|
||||
targetW = '80vw';
|
||||
targetH = '75vh';
|
||||
|
||||
// 小屏幕 (≤640px)
|
||||
targetW = '92vw';
|
||||
targetH = '85vh';
|
||||
```
|
||||
|
||||
### 动画参数
|
||||
```typescript
|
||||
// GSAP Timeline
|
||||
duration: 0.5, // 模态框展开时长
|
||||
ease: 'expo.out' // 缓动函数
|
||||
```
|
||||
|
||||
## 🐛 错误处理
|
||||
|
||||
### Chart.js加载失败
|
||||
```typescript
|
||||
try {
|
||||
if (window.Chart && venue.acceptanceValue !== null) {
|
||||
// 渲染图表
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('绘制图表时出错:', err);
|
||||
}
|
||||
```
|
||||
|
||||
### GSAP加载失败
|
||||
```typescript
|
||||
if (!gsap) {
|
||||
console.warn('GSAP未加载,使用简化模态框');
|
||||
openSimpleModal(venueId);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### 2025-10-01
|
||||
- ✅ 完成页面从HTML到Astro的转换
|
||||
- ✅ 创建TypeScript交互脚本
|
||||
- ✅ 实现GSAP模态框动画
|
||||
- ✅ 添加Chart.js图表渲染
|
||||
- ✅ 实现筛选器功能
|
||||
- ✅ 添加策略指南手风琴
|
||||
- ✅ 实现降级兼容方案
|
||||
- ✅ 应用莫兰蒂蓝色系主题
|
||||
|
||||
## 🎯 未来优化方向
|
||||
|
||||
1. **性能优化**
|
||||
- 懒加载Chart.js和GSAP
|
||||
- 虚拟滚动大列表
|
||||
- 图片优化
|
||||
|
||||
2. **功能增强**
|
||||
- 添加搜索功能
|
||||
- 导出PDF报告
|
||||
- 个性化推荐
|
||||
|
||||
3. **数据扩展**
|
||||
- 更多会议期刊数据
|
||||
- 历史录取率趋势
|
||||
- 审稿人反馈统计
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [Astro文档](https://docs.astro.build/)
|
||||
- [GSAP文档](https://greensock.com/docs/)
|
||||
- [Chart.js文档](https://www.chartjs.org/docs/)
|
||||
- [报告模板文档](../template/README.md)
|
||||
739
src/pages/report/ai-eda-paper-report/eda-venues-interactive.ts
Normal file
739
src/pages/report/ai-eda-paper-report/eda-venues-interactive.ts
Normal file
@@ -0,0 +1,739 @@
|
||||
/**
|
||||
* EDA/CAD学术发表指南交互式功能
|
||||
* 包含场所卡片筛选、模态框动画、图表渲染等功能
|
||||
*/
|
||||
|
||||
// 场所数据类型定义
|
||||
interface TimelineStage {
|
||||
stage: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface VenueData {
|
||||
id: string;
|
||||
name: string;
|
||||
fullName: string;
|
||||
type: '会议' | '期刊';
|
||||
tier: '顶级' | '高' | '应用/其他';
|
||||
speed: 'fast' | 'medium' | 'slow';
|
||||
speedValue: number;
|
||||
acceptanceRate: string;
|
||||
acceptanceValue: number | null;
|
||||
keyMetric: string;
|
||||
pageLimit: string;
|
||||
sponsors: string;
|
||||
overview: string;
|
||||
reviewProcess: string[];
|
||||
timeline: TimelineStage[];
|
||||
guidelines: string;
|
||||
}
|
||||
|
||||
interface StrategyData {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
// 场所数据
|
||||
const venuesData: VenueData[] = [
|
||||
{
|
||||
id: 'dac',
|
||||
name: 'DAC',
|
||||
fullName: 'Design Automation Conference',
|
||||
type: '会议',
|
||||
tier: '顶级',
|
||||
speed: 'medium',
|
||||
speedValue: 14,
|
||||
acceptanceRate: '20-25%',
|
||||
acceptanceValue: 22.7,
|
||||
keyMetric: '会议排名顶级',
|
||||
pageLimit: '6页 + 1页参考文献',
|
||||
sponsors: 'IEEE/ACM',
|
||||
overview: 'EDA领域的顶级会议之一,由IEEE和ACM共同赞助,对电子行业的创新具有重要影响。',
|
||||
reviewProcess: [
|
||||
'研究论文采用双盲评审。',
|
||||
'投稿规则非常严格,包括作者身份保密、投稿数量限制、摘要具体要求等。',
|
||||
'严格的规则旨在确保评审的公正性、完整性,并管理利益冲突。'
|
||||
],
|
||||
timeline: [
|
||||
{ stage: '建议论文开始写作', date: '每年8月' },
|
||||
{ stage: '建议实验结束', date: '每年10月初' },
|
||||
{ stage: '建议论文完稿', date: '每年11月初' },
|
||||
{ stage: '摘要提交', date: '每年11月中旬' },
|
||||
{ stage: '全文提交', date: '每年11月下旬' },
|
||||
{ stage: '接收通知', date: '次年2月下旬' },
|
||||
{ stage: '会议召开', date: '次年6月下旬' },
|
||||
],
|
||||
guidelines: '稿件长度不超过6页(双栏,9或10号字体),参考文献可额外占用1页,需遵循IEEE模板。'
|
||||
},
|
||||
{
|
||||
id: 'iccad',
|
||||
name: 'ICCAD',
|
||||
fullName: 'International Conference on Computer-Aided Design',
|
||||
type: '会议',
|
||||
tier: '顶级',
|
||||
speed: 'fast',
|
||||
speedValue: 8,
|
||||
acceptanceRate: '~22%',
|
||||
acceptanceValue: 22,
|
||||
keyMetric: '会议排名顶级',
|
||||
pageLimit: '8页 + 1页参考文献',
|
||||
sponsors: 'IEEE/ACM',
|
||||
overview: 'EDA研究领域的另一个顶级论坛,同样由IEEE和ACM联合主办,专注于集成电路和系统的CAD技术。',
|
||||
reviewProcess: [
|
||||
'采用双盲评审。',
|
||||
'投稿分两阶段:先提交摘要,再提交全文。',
|
||||
'鼓励提交包含开源软件的论文,重视可复现性和实际应用价值。'
|
||||
],
|
||||
timeline: [
|
||||
{ stage: '建议论文开始写作', date: '每年1月' },
|
||||
{ stage: '建议实验结束', date: '每年3月初' },
|
||||
{ stage: '建议论文完稿', date: '每年4月初' },
|
||||
{ stage: '摘要提交', date: '每年4月中旬' },
|
||||
{ stage: '全文提交', date: '每年4月下旬' },
|
||||
{ stage: '接收通知', date: '同年6月下旬' },
|
||||
{ stage: '会议召开', date: '同年10月下旬' },
|
||||
],
|
||||
guidelines: '常规论文不超过8页(双栏,9或10号字体),参考文献可额外占用1页,需遵守ACM出版政策。'
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
name: 'DATE',
|
||||
fullName: 'Design, Automation and Test in Europe',
|
||||
type: '会议',
|
||||
tier: '高',
|
||||
speed: 'fast',
|
||||
speedValue: 8,
|
||||
acceptanceRate: '~36%',
|
||||
acceptanceValue: 35.7,
|
||||
keyMetric: 'CORE Rank B',
|
||||
pageLimit: '需查阅具体征稿通知',
|
||||
sponsors: 'EDA/IEEE/ACM相关',
|
||||
overview: '欧洲在电子系统设计与测试领域的主要学术活动,涵盖硬件和软件,设有多个不同主题的轨道。',
|
||||
reviewProcess: [
|
||||
'论文经过同行评审。',
|
||||
'设有多个轨道(科学论文、应用设计、最新成果等),以适应不同类型的贡献。',
|
||||
'不同轨道的评审周期不同,为作者提供多样化的发表途径和速度选择。'
|
||||
],
|
||||
timeline: [
|
||||
{ stage: '建议论文开始写作', date: '每年6月' },
|
||||
{ stage: '建议实验结束', date: '每年8月初' },
|
||||
{ stage: '建议论文完稿', date: '每年9月初' },
|
||||
{ stage: '常规轨全文提交', date: '每年9月中旬' },
|
||||
{ stage: '常规轨接收通知', date: '同年11月中旬' },
|
||||
{ stage: '会议召开', date: '次年3月下旬' },
|
||||
],
|
||||
guidelines: '提及了音视频演示指南和海报格式要求。具体论文格式需参考当年征稿通知。'
|
||||
},
|
||||
{
|
||||
id: 'asp-dac',
|
||||
name: 'ASP-DAC',
|
||||
fullName: 'Asia and South Pacific Design Automation Conference',
|
||||
type: '会议',
|
||||
tier: '高',
|
||||
speed: 'fast',
|
||||
speedValue: 8,
|
||||
acceptanceRate: '~31%',
|
||||
acceptanceValue: 31,
|
||||
keyMetric: '区域顶级会议',
|
||||
pageLimit: '6页 + 1页参考文献',
|
||||
sponsors: 'IEEE/ACM相关',
|
||||
overview: '亚洲及南太平洋地区关于VLSI设计自动化的年度国际会议,近年来国际影响力日益提升。',
|
||||
reviewProcess: [
|
||||
'由技术程序委员会(TPC)进行严格、彻底的评审。',
|
||||
'通常召开TPC面对面会议进行最终论文筛选,注重审议和共识。',
|
||||
'采用两步流程:先注册摘要,再提交完整手稿。'
|
||||
],
|
||||
timeline: [
|
||||
{ stage: '建议论文开始写作', date: '每年4月' },
|
||||
{ stage: '建议实验结束', date: '每年5月下旬' },
|
||||
{ stage: '建议论文完稿', date: '每年6月下旬' },
|
||||
{ stage: '摘要/全文提交', date: '每年7月上旬' },
|
||||
{ stage: '接收通知', date: '同年9月上旬' },
|
||||
{ stage: '会议召开', date: '次年1月下旬' },
|
||||
],
|
||||
guidelines: '全文,双栏,最多6页,参考文献可额外占用1页,推荐使用ACM模板。'
|
||||
},
|
||||
{
|
||||
id: 'tcad',
|
||||
name: 'IEEE TCAD',
|
||||
fullName: 'Transactions on Computer-Aided Design of ICs and Systems',
|
||||
type: '期刊',
|
||||
tier: '顶级',
|
||||
speed: 'medium',
|
||||
speedValue: 10.1,
|
||||
acceptanceRate: '33-34%',
|
||||
acceptanceValue: 33.5,
|
||||
keyMetric: 'IF: 2.9 (2022)',
|
||||
pageLimit: '常规14页, 简报5页',
|
||||
sponsors: 'IEEE CEDA',
|
||||
overview: '月刊,同行评审,是集成电路与系统CAD领域的顶级期刊之一。全年滚动投稿。',
|
||||
reviewProcess: [
|
||||
'采用单盲评审(评审人匿名,作者身份公开)。',
|
||||
'通常邀请3位评审人,副编辑也可参与决策。',
|
||||
'设有"desk rejection"机制,快速处理不达标或不符范围的稿件。',
|
||||
'期刊正积极改善评审周期,缩短处理时间。'
|
||||
],
|
||||
timeline: [],
|
||||
guidelines: '常规论文14页,简报5页。通过Manuscript Central系统提交,需遵循IEEE格式。'
|
||||
},
|
||||
{
|
||||
id: 'todaes',
|
||||
name: 'ACM TODAES',
|
||||
fullName: 'Transactions on Design Automation of Electronic Systems',
|
||||
type: '期刊',
|
||||
tier: '高',
|
||||
speed: 'slow',
|
||||
speedValue: 16,
|
||||
acceptanceRate: '未明确',
|
||||
acceptanceValue: null,
|
||||
keyMetric: 'IF: 0.852',
|
||||
pageLimit: '视文章类型而定',
|
||||
sponsors: 'ACM',
|
||||
overview: 'ACM在电子系统设计自动化领域的旗舰期刊,季刊,侧重计算机科学/工程方向。全年滚动投稿。',
|
||||
reviewProcess: [
|
||||
'同行评审。',
|
||||
'作为季刊,总发表周期可能较长,尤其在需要修改的情况下。',
|
||||
'接收多种类型的稿件,包括常规研究、综述、教程等,为不同贡献提供空间。'
|
||||
],
|
||||
timeline: [],
|
||||
guidelines: '接收常规研究论文、主旨报告、综述、教程、设计者笔记等。通过Manuscript Central系统提交,遵循ACM规范。'
|
||||
},
|
||||
{
|
||||
id: 'd&t',
|
||||
name: 'IEEE D&T',
|
||||
fullName: 'IEEE Design & Test',
|
||||
type: '期刊',
|
||||
tier: '应用/其他',
|
||||
speed: 'fast',
|
||||
speedValue: 6,
|
||||
acceptanceRate: '未明确',
|
||||
acceptanceValue: null,
|
||||
keyMetric: 'IF: 1.5-1.9',
|
||||
pageLimit: '特稿<5000词',
|
||||
sponsors: 'IEEE',
|
||||
overview: '杂志型期刊,强调实践应用、教程和案例研究,面向实践者。全年滚动投稿。',
|
||||
reviewProcess: [
|
||||
'同行评审,至少两位独立评审人。',
|
||||
'评审侧重于论文的重要性、创新性、时下关注度、清晰度和实践相关性。',
|
||||
'稿件接收可能受即将出版期次的主题契合度影响。'
|
||||
],
|
||||
timeline: [],
|
||||
guidelines: '特稿文章不超过5000字,参考文献不超过12篇。专栏文章不超过2500字。强调清晰易懂的写作风格和教程性内容。'
|
||||
}
|
||||
];
|
||||
|
||||
// 策略指南数据
|
||||
const strategyData: StrategyData[] = [
|
||||
{
|
||||
title: '如何理解投稿难度?',
|
||||
content: `
|
||||
<p class="mb-2">学术出版物的"投稿难度"是一个多维度概念,不能仅看单一指标。主要考量:</p>
|
||||
<ul class="list-disc list-inside space-y-2">
|
||||
<li><strong>接收率 (Acceptance Rate):</strong> 接收率越低,通常意味着竞争越激烈、声望越高。但需要结合领域平均水平来看。</li>
|
||||
<li><strong>影响力指标 (Impact Metrics):</strong> 如期刊影响因子(IF)、CiteScore等,反映了期刊的学术影响力。Q1/Q2分区通常代表了高质量期刊。</li>
|
||||
<li><strong>同行评审 (Peer Review):</strong> 评审过程的严谨性是"难度"中一个难以量化的重要部分。严格的评审流程要求稿件具有更高的质量。</li>
|
||||
<li><strong>稿件质量与契合度:</strong> 稿件本身的创新性、严谨性,以及与发表场所范围的匹配度,是最根本的决定因素。</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-sm text-slate-500"><strong>专家提示:</strong> 不要孤立地看待任何一个指标。一个接收率适中的Q1期刊,其投稿难度依然很高,因为它对稿件质量和潜在影响力有极高要求。</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
title: '如何规划发表时间线?',
|
||||
content: `
|
||||
<p class="mb-2">从提交到见刊的周期受多种因素影响,关键时间节点包括:</p>
|
||||
<div class="space-y-3">
|
||||
<div><strong>初审 (Desk Review):</strong> 约1-2周。编辑部进行格式和范围检查,部分稿件在此阶段会被直接拒稿(desk rejection)。</div>
|
||||
<div><strong>同行评审 (Peer Review):</strong> 约1-3个月,变动很大。这是最主要的耗时环节,取决于评审人的效率。</div>
|
||||
<div><strong>修改与再审 (Revision):</strong> 时间不固定。作者修改速度和编辑/评审人的二审效率都会影响总时长。</div>
|
||||
<div><strong>制作与发表 (Production):</strong> 接收后约1-3个月。许多期刊提供在线预发表(Early Access),可以缩短成果传播时间。</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-slate-500"><strong>专家提示:</strong> 会议的发表周期通常比期刊更固定、更可预测。对于时间敏感的研究,优先考虑会议或快速发表期刊,并善用预印本服务器(如arXiv)。</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
title: '如何制定投稿策略?',
|
||||
content: `
|
||||
<p class="mb-2">选择发表场所是一项战略性决策,需综合考量:</p>
|
||||
<ul class="list-disc list-inside space-y-2">
|
||||
<li><strong>研究性质:</strong>
|
||||
<ul class="list-['-_'] list-inside ml-4">
|
||||
<li><strong>开创性研究:</strong> 优先考虑顶级会议(DAC, ICCAD)或高影响力期刊(TCAD)。</li>
|
||||
<li><strong>应用型/案例研究:</strong> DATE的应用轨或IEEE D&T杂志可能更合适。</li>
|
||||
<li><strong>增量型研究:</strong> 可考虑区域性会议(ASP-DAC)或接收范围稍广的期刊。</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>职业目标:</strong>
|
||||
<ul class="list-['-_'] list-inside ml-4">
|
||||
<li><strong>博士生/早期学者:</strong> 在知名会议发表有助建立声誉和学术联系。</li>
|
||||
<li><strong>职称晋升:</strong> 通常更看重高影响力(Q1/Q2)期刊论文。</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>混合策略:</strong> EDA/CAD领域的常见做法是,先在顶级会议上快速发布核心创新成果,之后将工作深化、扩展,发表于档案型的顶级期刊。</li>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
// 全局变量
|
||||
let activeChart: any = null;
|
||||
let activeCardState: { top: number; left: number; width: number; height: number; id: string } | null = null;
|
||||
let isAnimating = false;
|
||||
|
||||
/**
|
||||
* 渲染场所卡片
|
||||
*/
|
||||
export function renderVenues(): void {
|
||||
const venueGrid = document.getElementById('venue-grid');
|
||||
const typeFilter = document.getElementById('type-filter') as HTMLSelectElement;
|
||||
const tierFilter = document.getElementById('tier-filter') as HTMLSelectElement;
|
||||
const speedFilter = document.getElementById('speed-filter') as HTMLSelectElement;
|
||||
const noResults = document.getElementById('no-results');
|
||||
|
||||
if (!venueGrid || !typeFilter || !tierFilter || !speedFilter || !noResults) return;
|
||||
|
||||
venueGrid.innerHTML = '';
|
||||
const type = typeFilter.value;
|
||||
const tier = tierFilter.value;
|
||||
const speed = speedFilter.value;
|
||||
|
||||
const filteredVenues = venuesData.filter(venue => {
|
||||
const typeMatch = type === 'all' || venue.type === type;
|
||||
const tierMatch = tier === 'all' || venue.tier === tier;
|
||||
const speedMatch = speed === 'all' || venue.speed === speed;
|
||||
return typeMatch && tierMatch && speedMatch;
|
||||
});
|
||||
|
||||
if (filteredVenues.length === 0) {
|
||||
noResults.classList.remove('hidden');
|
||||
} else {
|
||||
noResults.classList.add('hidden');
|
||||
}
|
||||
|
||||
filteredVenues.forEach(venue => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'venue-card p-6 cursor-pointer flex flex-col';
|
||||
card.dataset.id = venue.id;
|
||||
card.innerHTML = `
|
||||
<div class="flex-grow">
|
||||
<div class="flex justify-between items-start">
|
||||
<h3 class="text-2xl font-bold text-gray-800">${venue.name}</h3>
|
||||
<span class="text-xs font-semibold ${venue.type === '会议' ? 'bg-sky-100 text-sky-800' : 'bg-amber-100 text-amber-800'} py-1 px-3 rounded-full">${venue.type}</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mb-4">${venue.fullName}</p>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex items-center">
|
||||
<span class="w-6 text-center mr-2">🏆</span>
|
||||
<span><strong>难度:</strong> ${venue.tier}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="w-6 text-center mr-2">📊</span>
|
||||
<span><strong>接收率:</strong> ${venue.acceptanceRate}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="w-6 text-center mr-2">⏱️</span>
|
||||
<span><strong>评审速度:</strong> ~${venue.speedValue} 周</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="w-6 text-center mr-2">⭐</span>
|
||||
<span><strong>关键指标:</strong> ${venue.keyMetric}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mt-6 w-full text-center bg-gray-100 text-gray-700 font-semibold py-2 rounded-lg hover:bg-gray-200 transition duration-300">查看详情</button>
|
||||
`;
|
||||
card.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
openModal(venue.id, card);
|
||||
});
|
||||
venueGrid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开模态框
|
||||
*/
|
||||
export function openModal(venueId: string, cardElement: HTMLElement): void {
|
||||
if (isAnimating) return;
|
||||
|
||||
// 检查是否有GSAP
|
||||
const gsap = (window as any).gsap;
|
||||
if (!gsap) {
|
||||
console.warn('GSAP未加载,使用简化模态框');
|
||||
openSimpleModal(venueId);
|
||||
return;
|
||||
}
|
||||
|
||||
isAnimating = true;
|
||||
|
||||
const venue = venuesData.find(v => v.id === venueId);
|
||||
if (!venue) {
|
||||
isAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const cardRect = cardElement.getBoundingClientRect();
|
||||
activeCardState = {
|
||||
top: cardRect.top,
|
||||
left: cardRect.left,
|
||||
width: cardRect.width,
|
||||
height: cardRect.height,
|
||||
id: venueId
|
||||
};
|
||||
|
||||
if (activeChart) {
|
||||
activeChart.destroy();
|
||||
}
|
||||
|
||||
const modal = document.getElementById('detail-modal');
|
||||
const modalOverlay = document.getElementById('modal-overlay');
|
||||
const modalContent = document.getElementById('modal-content');
|
||||
const modalContentWrapper = document.getElementById('modal-content-wrapper');
|
||||
|
||||
if (!modal || !modalOverlay || !modalContent || !modalContentWrapper) return;
|
||||
|
||||
// 生成时间线HTML
|
||||
let timelineHtml = '';
|
||||
if (venue.timeline.length > 0) {
|
||||
timelineHtml = `<div class="mt-6">
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3">典型发表时间线 (${venue.type === '会议' ? '年度周期' : ''})</h4>
|
||||
<ol class="relative border-l border-gray-200">`;
|
||||
venue.timeline.forEach(item => {
|
||||
timelineHtml += `
|
||||
<li class="mb-6 ml-4">
|
||||
<div class="absolute w-3 h-3 bg-gray-300 rounded-full mt-1.5 -left-1.5 border border-white"></div>
|
||||
<time class="mb-1 text-sm font-normal leading-none text-gray-500">${item.date}</time>
|
||||
<h3 class="text-md font-semibold text-gray-900">${item.stage}</h3>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
timelineHtml += `</ol></div>`;
|
||||
}
|
||||
|
||||
modalContent.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-gray-900">${venue.name}</h2>
|
||||
<p class="text-gray-600">${venue.fullName}</p>
|
||||
</div>
|
||||
<span class="text-sm font-semibold ${venue.type === '会议' ? 'bg-sky-100 text-sky-800' : 'bg-amber-100 text-amber-800'} py-1.5 px-4 rounded-full">${venue.type}</span>
|
||||
</div>
|
||||
|
||||
<div class="prose max-w-none text-gray-700 mb-6">
|
||||
<p>${venue.overview}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3">关键信息</h4>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>难度等级:</strong></span> <span class="font-semibold">${venue.tier}</span></li>
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>主办方:</strong></span> <span class="font-semibold">${venue.sponsors}</span></li>
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>页数限制:</strong></span> <span class="font-semibold">${venue.pageLimit}</span></li>
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>投稿指南:</strong></span> <span class="font-semibold">${venue.guidelines}</span></li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-6">
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3">评审过程特点</h4>
|
||||
<ul class="list-disc list-inside space-y-1 text-gray-600">
|
||||
${venue.reviewProcess.map(p => `<li>${p}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3 text-center">指标概览</h4>
|
||||
<div class="chart-container">
|
||||
<canvas id="venueChart"></canvas>
|
||||
</div>
|
||||
${timelineHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 渲染图表
|
||||
try {
|
||||
const Chart = (window as any).Chart;
|
||||
if (Chart && venue.acceptanceValue !== null) {
|
||||
const ctx = (document.getElementById('venueChart') as HTMLCanvasElement).getContext('2d');
|
||||
if (ctx) {
|
||||
activeChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['接收率 (%)', '评审周期 (周)'],
|
||||
datasets: [{
|
||||
label: venue.name,
|
||||
data: [venue.acceptanceValue, venue.speedValue],
|
||||
backgroundColor: ['rgba(39, 121, 127, 0.6)', 'rgba(251, 191, 36, 0.6)'],
|
||||
borderColor: ['rgba(39, 121, 127, 1)', 'rgba(245, 158, 11, 1)'],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('绘制图表时出错:', err);
|
||||
}
|
||||
|
||||
// GSAP动画
|
||||
gsap.set(cardElement, { opacity: 0 });
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modalOverlay.classList.remove('hidden');
|
||||
|
||||
const tl = gsap.timeline({
|
||||
onComplete: () => { isAnimating = false; }
|
||||
});
|
||||
|
||||
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
|
||||
let targetW = '80vw';
|
||||
let targetH = '75vh';
|
||||
if (vw >= 1024) {
|
||||
targetW = '70vw';
|
||||
targetH = '70vh';
|
||||
} else if (vw <= 640) {
|
||||
targetW = '92vw';
|
||||
targetH = '85vh';
|
||||
}
|
||||
|
||||
tl.set(modal, {
|
||||
top: activeCardState.top,
|
||||
left: activeCardState.left,
|
||||
width: activeCardState.width,
|
||||
height: activeCardState.height,
|
||||
})
|
||||
.to(modalOverlay, {
|
||||
opacity: 0.6,
|
||||
duration: 0.4
|
||||
}, 0)
|
||||
.to(modal, {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
width: targetW,
|
||||
height: targetH,
|
||||
x: '-50%',
|
||||
y: '-50%',
|
||||
duration: 0.5,
|
||||
ease: 'expo.out'
|
||||
}, 0)
|
||||
.to(modalContentWrapper, {
|
||||
opacity: 1,
|
||||
visibility: 'visible',
|
||||
duration: 0.4,
|
||||
}, ">-0.2");
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化模态框(无GSAP时使用)
|
||||
*/
|
||||
function openSimpleModal(venueId: string): void {
|
||||
const venue = venuesData.find(v => v.id === venueId);
|
||||
if (!venue) return;
|
||||
|
||||
const modal = document.getElementById('detail-modal');
|
||||
const modalOverlay = document.getElementById('modal-overlay');
|
||||
const modalContent = document.getElementById('modal-content');
|
||||
const modalContentWrapper = document.getElementById('modal-content-wrapper');
|
||||
|
||||
if (!modal || !modalOverlay || !modalContent || !modalContentWrapper) return;
|
||||
|
||||
// 直接显示,无动画
|
||||
modal.classList.remove('hidden');
|
||||
modalOverlay.classList.remove('hidden');
|
||||
modalContentWrapper.style.opacity = '1';
|
||||
modalContentWrapper.style.visibility = 'visible';
|
||||
|
||||
// 设置固定位置
|
||||
modal.style.top = '50%';
|
||||
modal.style.left = '50%';
|
||||
modal.style.transform = 'translate(-50%, -50%)';
|
||||
modal.style.width = '80vw';
|
||||
modal.style.height = '75vh';
|
||||
|
||||
// 生成内容(同上)
|
||||
let timelineHtml = '';
|
||||
if (venue.timeline.length > 0) {
|
||||
timelineHtml = `<div class="mt-6">
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3">典型发表时间线</h4>
|
||||
<ol class="relative border-l border-gray-200">`;
|
||||
venue.timeline.forEach(item => {
|
||||
timelineHtml += `
|
||||
<li class="mb-6 ml-4">
|
||||
<div class="absolute w-3 h-3 bg-gray-300 rounded-full mt-1.5 -left-1.5 border border-white"></div>
|
||||
<time class="mb-1 text-sm font-normal leading-none text-gray-500">${item.date}</time>
|
||||
<h3 class="text-md font-semibold text-gray-900">${item.stage}</h3>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
timelineHtml += `</ol></div>`;
|
||||
}
|
||||
|
||||
modalContent.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-gray-900">${venue.name}</h2>
|
||||
<p class="text-gray-600">${venue.fullName}</p>
|
||||
</div>
|
||||
<span class="text-sm font-semibold ${venue.type === '会议' ? 'bg-sky-100 text-sky-800' : 'bg-amber-100 text-amber-800'} py-1.5 px-4 rounded-full">${venue.type}</span>
|
||||
</div>
|
||||
|
||||
<div class="prose max-w-none text-gray-700 mb-6">
|
||||
<p>${venue.overview}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3">关键信息</h4>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>难度等级:</strong></span> <span class="font-semibold">${venue.tier}</span></li>
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>主办方:</strong></span> <span class="font-semibold">${venue.sponsors}</span></li>
|
||||
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>页数限制:</strong></span> <span class="font-semibold">${venue.pageLimit}</span></li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-6">
|
||||
<h4 class="text-lg font-semibold text-gray-800 mb-3">评审过程特点</h4>
|
||||
<ul class="list-disc list-inside space-y-1 text-gray-600">
|
||||
${venue.reviewProcess.map(p => `<li>${p}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
${timelineHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭模态框
|
||||
*/
|
||||
export function closeModal(): void {
|
||||
const gsap = (window as any).gsap;
|
||||
|
||||
if (!gsap || !activeCardState) {
|
||||
// 简化关闭
|
||||
const modal = document.getElementById('detail-modal');
|
||||
const modalOverlay = document.getElementById('modal-overlay');
|
||||
if (modal && modalOverlay) {
|
||||
modal.classList.add('hidden');
|
||||
modalOverlay.classList.add('hidden');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAnimating) return;
|
||||
isAnimating = true;
|
||||
|
||||
const modal = document.getElementById('detail-modal');
|
||||
const modalOverlay = document.getElementById('modal-overlay');
|
||||
const modalContentWrapper = document.getElementById('modal-content-wrapper');
|
||||
const originalCard = document.querySelector(`[data-id='${activeCardState.id}']`) as HTMLElement;
|
||||
|
||||
if (!modal || !modalOverlay || !modalContentWrapper) return;
|
||||
|
||||
const tl = gsap.timeline({
|
||||
onComplete: () => {
|
||||
modal.classList.add('hidden');
|
||||
modalOverlay.classList.add('hidden');
|
||||
if (originalCard) {
|
||||
gsap.set(originalCard, { opacity: 1 });
|
||||
}
|
||||
activeCardState = null;
|
||||
isAnimating = false;
|
||||
}
|
||||
});
|
||||
|
||||
tl.to(modalContentWrapper, {
|
||||
opacity: 0,
|
||||
duration: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
.to(modal, {
|
||||
top: activeCardState.top,
|
||||
left: activeCardState.left,
|
||||
width: activeCardState.width,
|
||||
height: activeCardState.height,
|
||||
x: '0%',
|
||||
y: '0%',
|
||||
duration: 0.5,
|
||||
ease: 'expo.in'
|
||||
}, ">-0.05")
|
||||
.to(modalOverlay, {
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, "<")
|
||||
.set(modalContentWrapper, { visibility: 'hidden' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染策略指南手风琴
|
||||
*/
|
||||
export function renderStrategyAccordion(): void {
|
||||
const container = document.getElementById('accordion-container');
|
||||
if (!container) return;
|
||||
|
||||
strategyData.forEach((item, index) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = "bg-white rounded-xl shadow-sm overflow-hidden border-l-4 border-transparent hover:border-[#27797f] transition-all";
|
||||
div.innerHTML = `
|
||||
<button class="accordion-header w-full flex justify-between items-center p-5 text-left font-semibold text-gray-800 hover:bg-gray-50 transition" data-index="${index}">
|
||||
<span>${item.title}</span>
|
||||
<svg class="accordion-icon w-6 h-6 transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<div class="accordion-content hidden p-5 pt-0 prose max-w-none text-gray-600">
|
||||
${item.content}
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.accordion-header').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const content = button.nextElementSibling as HTMLElement;
|
||||
const icon = button.querySelector('.accordion-icon');
|
||||
|
||||
content.classList.toggle('hidden');
|
||||
icon?.classList.toggle('rotate-180');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有交互功能
|
||||
*/
|
||||
export function initEDAVenuesInteractive(): void {
|
||||
const typeFilter = document.getElementById('type-filter');
|
||||
const tierFilter = document.getElementById('tier-filter');
|
||||
const speedFilter = document.getElementById('speed-filter');
|
||||
const resetButton = document.getElementById('reset-filters');
|
||||
const closeModalBtn = document.getElementById('close-modal-btn');
|
||||
const modalOverlay = document.getElementById('modal-overlay');
|
||||
|
||||
// 筛选器事件
|
||||
typeFilter?.addEventListener('change', renderVenues);
|
||||
tierFilter?.addEventListener('change', renderVenues);
|
||||
speedFilter?.addEventListener('change', renderVenues);
|
||||
|
||||
resetButton?.addEventListener('click', () => {
|
||||
(typeFilter as HTMLSelectElement).value = 'all';
|
||||
(tierFilter as HTMLSelectElement).value = 'all';
|
||||
(speedFilter as HTMLSelectElement).value = 'all';
|
||||
renderVenues();
|
||||
});
|
||||
|
||||
// 模态框关闭事件
|
||||
closeModalBtn?.addEventListener('click', closeModal);
|
||||
modalOverlay?.addEventListener('click', closeModal);
|
||||
|
||||
// 渲染初始内容
|
||||
renderVenues();
|
||||
renderStrategyAccordion();
|
||||
|
||||
console.log('🎯 EDA学术发表指南交互功能已初始化');
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import Footer from '../../components/Footer.astro';
|
||||
import NavigationGrid from '../../components/navigation/NavigationGrid.astro';
|
||||
import NavigationCard from '../../components/navigation/NavigationCard.astro';
|
||||
import Container from '../../components/Container.astro';
|
||||
import AnimatedElement from '../../components/AnimatedElement.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="技术报告 - Jiao77" description="我的所有技术研究与分析报告都在这里" type="navigation">
|
||||
@@ -12,88 +13,116 @@ import Container from '../../components/Container.astro';
|
||||
|
||||
<main class="main-content">
|
||||
<div class="container mx-auto px-4">
|
||||
<Container
|
||||
variant="glass"
|
||||
size="large"
|
||||
padding="xl"
|
||||
className="text-center mb-12"
|
||||
revealDistance="48px"
|
||||
>
|
||||
<h1 class="hero-title"><i class="fas fa-book-open"></i> 技术报告</h1>
|
||||
<p class="hero-subtitle">研究与分析报告集锦</p>
|
||||
<p class="hero-description">我的所有技术研究与分析报告都在这里。从AI集成电路版图识别到EDA工具应用,探索技术前沿的深度分析。</p>
|
||||
</Container>
|
||||
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
|
||||
<Container
|
||||
variant="glass"
|
||||
size="large"
|
||||
padding="xl"
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 class="hero-title"><i class="fas fa-book-open"></i> 技术报告</h1>
|
||||
<p class="hero-subtitle">研究与分析报告集锦</p>
|
||||
<p class="hero-description">我的所有技术研究与分析报告都在这里。从AI集成电路版图识别到EDA工具应用,探索技术前沿的深度分析。</p>
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
|
||||
<NavigationGrid
|
||||
title="技术报告导航"
|
||||
description="深入的技术研究与分析文档,涵盖AI、EDA、集成电路等前沿领域"
|
||||
columns={3}
|
||||
gap="large"
|
||||
>
|
||||
<NavigationCard
|
||||
title="报告 20250609"
|
||||
description="关于基于AI的集成电路版图识别的开题报告。详细介绍了项目背景、研究方法和预期成果。"
|
||||
href="/report/20250609/"
|
||||
icon="fas fa-calendar-alt"
|
||||
color="primary"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="报告 20250722"
|
||||
description="关于RoRD模型及其在IC版图应用中的详细技术解析。深入探讨模型架构与优化策略。"
|
||||
href="/report/20250722/"
|
||||
icon="fas fa-calendar-alt"
|
||||
color="secondary"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="AI in EDA 论文报告"
|
||||
description="关于AI在EDA领域的学术论文发表指南与分析。涵盖投稿策略和研究方向建议。"
|
||||
href="/report/ai-eda-paper-report/"
|
||||
icon="fas fa-microchip"
|
||||
color="accent"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="Geo-Layout Transformer"
|
||||
description="关于Geo-Layout Transformer统一基础模型的技术报告。介绍了创新的几何布局转换架构。"
|
||||
href="/report/Geo-Layout-Transformer/"
|
||||
icon="fas fa-robot"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="Siemens EDA Forum"
|
||||
description="聚焦SONR与SDPAL的AI良率提升实践报告。分享工业界最新的EDA技术应用案例。"
|
||||
href="/report/Siemens-EDA-Forum/"
|
||||
icon="fas fa-industry"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="组会报告 20250912"
|
||||
description="关于RoRD改进、Geo-Layout-Transformer框架、西门子论坛及服务器脚本的进展总结。"
|
||||
href="/report/20250912/"
|
||||
icon="fas fa-tasks"
|
||||
color="accent"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<NavigationCard
|
||||
title="RoRD项目发展规划 20250928"
|
||||
description="RoRD项目技术优化规划与论文投稿策略的完整发展蓝图。制定了详细的研发时间表。"
|
||||
href="/report/20250928/"
|
||||
icon="fas fa-map-marked-alt"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
</NavigationGrid>
|
||||
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
|
||||
<NavigationGrid
|
||||
title="技术报告导航"
|
||||
description="深入的技术研究与分析文档,涵盖AI、EDA、集成电路等前沿领域"
|
||||
columns={3}
|
||||
gap="large"
|
||||
>
|
||||
<AnimatedElement animation="fadeInUp" delay={600} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="报告 20250609"
|
||||
description="关于基于AI的集成电路版图识别的开题报告。详细介绍了项目背景、研究方法和预期成果。"
|
||||
href="/report/20250609/"
|
||||
icon="fas fa-calendar-alt"
|
||||
color="primary"
|
||||
size="large"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={700} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="报告 20250722"
|
||||
description="关于RoRD模型及其在IC版图应用中的详细技术解析。深入探讨模型架构与优化策略。"
|
||||
href="/report/20250722/"
|
||||
icon="fas fa-calendar-alt"
|
||||
color="secondary"
|
||||
size="large"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="AI in EDA 论文报告"
|
||||
description="关于AI在EDA领域的学术论文发表指南与分析。涵盖投稿策略和研究方向建议。"
|
||||
href="/report/ai-eda-paper-report/"
|
||||
icon="fas fa-microchip"
|
||||
color="accent"
|
||||
size="large"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={900} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="Geo-Layout Transformer"
|
||||
description="关于Geo-Layout Transformer统一基础模型的技术报告。介绍了创新的几何布局转换架构。"
|
||||
href="/report/Geo-Layout-Transformer/"
|
||||
icon="fas fa-robot"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="Siemens EDA Forum"
|
||||
description="聚焦SONR与SDPAL的AI良率提升实践报告。分享工业界最新的EDA技术应用案例。"
|
||||
href="/report/Siemens-EDA-Forum/"
|
||||
icon="fas fa-industry"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1100} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="组会报告 20250912"
|
||||
description="关于RoRD改进、Geo-Layout-Transformer框架、西门子论坛及服务器脚本的进展总结。"
|
||||
href="/report/20250912/"
|
||||
icon="fas fa-tasks"
|
||||
color="accent"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1200} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="RoRD项目发展规划 20250928"
|
||||
description="RoRD项目技术优化规划与论文投稿策略的完整发展蓝图。制定了详细的研发时间表。"
|
||||
href="/report/20250928/"
|
||||
icon="fas fa-map-marked-alt"
|
||||
color="primary"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={1300} trigger="scroll">
|
||||
<NavigationCard
|
||||
title="📄 报告模板"
|
||||
description="技术报告标准模板,包含统一宽度配置、代码展示、数学公式等完整组件支持。快速创建专业报告!"
|
||||
href="/report/template/"
|
||||
icon="fas fa-file-code"
|
||||
color="accent"
|
||||
size="medium"
|
||||
/>
|
||||
</AnimatedElement>
|
||||
</NavigationGrid>
|
||||
</AnimatedElement>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
|
||||
440
src/pages/report/template/README.md
Normal file
440
src/pages/report/template/README.md
Normal file
@@ -0,0 +1,440 @@
|
||||
# 技术报告模板使用指南
|
||||
|
||||
## 📋 模板概述
|
||||
|
||||
**基于最新优化设计:** 基于 `report/20250722/index.astro` 的最新先进设计,我们创建了一个标准化的技术报告模板,位于 `src/pages/report/template/index.astro`。这个模板集成了最佳实践的样式设计、动画效果和布局结构。
|
||||
|
||||
### ✨ 2025更新亮点
|
||||
|
||||
**统一宽度配置系统:**
|
||||
- 🎯 **CSS变量驱动:** 使用 `--max-content-width` 变量统一管理所有内容区域宽度
|
||||
- 🔧 **一键调整:** 只需修改一个变量值,所有ReportSection宽度自动同步
|
||||
- 📐 **完美一致:** 彻底解决宽度不一致问题,确保所有报告部分完美对齐
|
||||
- 🚀 **先进管理:** 告别复杂的多处修改,采用现代化的配置管理方式
|
||||
|
||||
**完整组件支持:**
|
||||
- 💻 **CodeBlock组件:** 支持多语言代码高亮显示(JavaScript、Python、CSS等)
|
||||
- 📊 **GlassTable组件:** 磨砂玻璃风格的数据表格
|
||||
- 🧮 **MathFormula组件:** LaTeX数学公式渲染
|
||||
- 🖼️ **ImageViewer组件:** 支持点击放大的响应式图片展示
|
||||
- 📦 **Container组件:** 统一的容器样式系统
|
||||
- ✨ **AnimatedElement组件:** 滚动动画效果
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 复制模板
|
||||
```bash
|
||||
# 创建新报告目录
|
||||
mkdir src/pages/report/your-report-name
|
||||
|
||||
# 复制模板文件
|
||||
cp src/pages/report/template/index.astro src/pages/report/your-report-name/index.astro
|
||||
```
|
||||
|
||||
### 2. 基本配置
|
||||
在文件顶部找到 `reportConfig` 对象,修改以下配置:
|
||||
|
||||
```astro
|
||||
const reportConfig = {
|
||||
title: "你的报告标题",
|
||||
subtitle: "报告副标题和核心内容描述",
|
||||
description: "用于SEO的详细描述",
|
||||
date: "2025年XX月XX日",
|
||||
type: "技术类型(如:技术报告、研究分析等)",
|
||||
actionText: "开始阅读 ↓",
|
||||
actionLink: "#section-1"
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 添加内容
|
||||
根据需要修改、添加或删除 `ReportSection` 组件来构建你的报告内容。
|
||||
|
||||
## 🏗️ 模板结构
|
||||
|
||||
### 核心组件
|
||||
- **BaseLayout**: 页面基础布局
|
||||
- **ReportSidebar**: 自动生成的目录侧边栏
|
||||
- **AnimatedElement**: 统一的进场动画控制
|
||||
- **ReportSection**: 标准化的章节容器
|
||||
- **Container**: 玻璃态内容容器
|
||||
|
||||
### 章节类型示例
|
||||
|
||||
#### 1. 基础内容章节
|
||||
```astro
|
||||
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
|
||||
<ReportSection
|
||||
title="🎯 章节标题"
|
||||
subtitle="章节副标题"
|
||||
level={2}
|
||||
id="section-id"
|
||||
>
|
||||
<!-- 内容 -->
|
||||
</ReportSection>
|
||||
</AnimatedElement>
|
||||
```
|
||||
|
||||
#### 2. 卡片式布局
|
||||
```astro
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">卡片标题</h3>
|
||||
<p class="challenge-description">卡片描述</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 3. 深度分析布局
|
||||
```astro
|
||||
<div class="deep-dive-card">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="text-5xl mr-4">🔧</div>
|
||||
<div>
|
||||
<p class="component-label">组件类型</p>
|
||||
<h3 class="component-title">组件标题</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700">详细内容...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 4. 对比表格
|
||||
```astro
|
||||
<GlassTable
|
||||
headers={['维度', '选项1', '选项2', '推荐']}
|
||||
rows={[
|
||||
['性能', '高', '中', '选项1'],
|
||||
['成本', '低', '高', '选项1']
|
||||
]}
|
||||
striped={true}
|
||||
bordered={true}
|
||||
/>
|
||||
```
|
||||
|
||||
#### 5. 代码块展示
|
||||
```astro
|
||||
<CodeBlock
|
||||
title="JavaScript 示例"
|
||||
language="javascript"
|
||||
showLineNumbers={true}
|
||||
code={`function fibonacci(n) {
|
||||
if (n <= 1) return n;
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
console.log(fibonacci(10)); // 输出: 55`}
|
||||
/>
|
||||
```
|
||||
|
||||
支持的语言:
|
||||
- `javascript` / `js` - JavaScript
|
||||
- `python` / `py` - Python
|
||||
- `css` - CSS样式
|
||||
- `html` - HTML标记
|
||||
- `typescript` / `ts` - TypeScript
|
||||
- `bash` / `shell` - Shell脚本
|
||||
- 等更多语言...
|
||||
|
||||
#### 6. 图片展示
|
||||
```astro
|
||||
<ImageViewer
|
||||
src="/report/your-folder/image.jpg"
|
||||
alt="图片描述"
|
||||
caption="图片说明文字"
|
||||
className="w-full h-auto rounded-lg"
|
||||
/>
|
||||
```
|
||||
|
||||
## 🎨 统一宽度配置系统
|
||||
|
||||
### 核心原理
|
||||
所有报告内容区域通过CSS变量 `--max-content-width` 统一控制宽度,确保完美一致的视觉效果。
|
||||
|
||||
### 快速配置
|
||||
在模板的 `<style>` 部分找到:
|
||||
```css
|
||||
:root {
|
||||
--max-content-width: 1200px; /* 修改这个值即可 */
|
||||
}
|
||||
```
|
||||
|
||||
### 推荐宽度选项
|
||||
- **1024px** - 较窄布局,适合文字密集的学术报告
|
||||
- **1200px** - 平衡布局,适合大多数技术报告(默认)
|
||||
- **1400px** - 宽屏布局,适合表格和图片较多的报告
|
||||
|
||||
### 自动应用范围
|
||||
以下所有组件会自动使用统一宽度:
|
||||
- ✅ ReportSection 主要内容区域
|
||||
- ✅ Container 容器组件
|
||||
- ✅ 报告标题区域 (.report-header)
|
||||
- ✅ 所有章节内容区域
|
||||
- ✅ 表格和图片展示区域
|
||||
|
||||
#### 5. 图片展示
|
||||
```astro
|
||||
<div class="proof-image-container">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer
|
||||
src="/report/your-folder/image.jpg"
|
||||
alt="图片描述"
|
||||
className="proof-image-viewer"
|
||||
aspectRatio="5 / 3"
|
||||
/>
|
||||
<p>图片说明</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 6. 数学公式
|
||||
```astro
|
||||
<MathFormula
|
||||
formula="E = mc^2"
|
||||
className="formula-style"
|
||||
/>
|
||||
```
|
||||
|
||||
## 🎨 样式系统
|
||||
|
||||
### 预定义样式类
|
||||
|
||||
#### 布局容器
|
||||
- `.goal-section`: 标准内容区域
|
||||
- `.deep-dive-card`: 深度分析卡片
|
||||
- `.challenge-card`: 挑战/概念卡片
|
||||
- `.math-intro`: 数学公式介绍区域
|
||||
|
||||
#### 文字样式
|
||||
- `.section-heading`: 章节小标题
|
||||
- `.component-label`: 组件标签
|
||||
- `.component-title`: 组件标题
|
||||
- `.challenge-title`: 卡片标题
|
||||
- `.challenge-description`: 卡片描述
|
||||
|
||||
#### 特殊效果
|
||||
- `.summary-highlight`: 总结高亮区域
|
||||
- `.research-pillars`: 要点列表
|
||||
- `.proof-image-container`: 图片网格容器
|
||||
|
||||
### 响应式设计
|
||||
- 移动端优先设计
|
||||
- 桌面端侧边栏自动定位
|
||||
- 图片和表格自适应布局
|
||||
|
||||
## ⚡ 动画配置
|
||||
|
||||
### 基础动画时序
|
||||
```astro
|
||||
<!-- 标题区域 -->
|
||||
delay={200} trigger="load"
|
||||
|
||||
<!-- 第一章节 -->
|
||||
delay={400} trigger="scroll"
|
||||
|
||||
<!-- 后续章节递增 -->
|
||||
delay={600} trigger="scroll"
|
||||
delay={800} trigger="scroll"
|
||||
delay={1000} trigger="scroll"
|
||||
// 每个章节增加 200ms
|
||||
```
|
||||
|
||||
### 🎨 子容器延迟动画(新增功能)
|
||||
为了创建更丰富的视觉层次和用户体验,可以为容器内的子元素添加延迟动画:
|
||||
|
||||
#### 卡片网格渐进展示
|
||||
```astro
|
||||
<!-- 主容器动画 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={600} trigger="scroll">
|
||||
<ReportSection title="技术分析">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- 子卡片依次出现 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={700} trigger="scroll">
|
||||
<div class="challenge-card">卡片1</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<div class="challenge-card">卡片2</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={900} trigger="scroll">
|
||||
<div class="challenge-card">卡片3</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</AnimatedElement>
|
||||
```
|
||||
|
||||
#### 深度分析嵌套动画
|
||||
```astro
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
<div class="deep-dive-card">
|
||||
<h3>主标题</h3>
|
||||
<p>基本描述...</p>
|
||||
|
||||
<!-- 子内容延迟出现 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={1200} trigger="scroll">
|
||||
<h4>详细说明:</h4>
|
||||
<ul>
|
||||
<li>要点一</li>
|
||||
<li>要点二</li>
|
||||
</ul>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
```
|
||||
|
||||
#### 图片容器渐进加载
|
||||
```astro
|
||||
<div class="proof-image-container">
|
||||
<AnimatedElement animation="fadeInUp" delay={1600} trigger="scroll">
|
||||
<div class="proof-image-item">图片1</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={1700} trigger="scroll">
|
||||
<div class="proof-image-item">图片2</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={1800} trigger="scroll">
|
||||
<div class="proof-image-item">图片3</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 延迟时间建议
|
||||
|
||||
| 元素类型 | 主容器延迟 | 子元素延迟 | 递增间隔 |
|
||||
|---------|-----------|-----------|---------|
|
||||
| 卡片网格 | 600ms | 700ms起 | 100ms |
|
||||
| 深度分析 | 800ms | 1000ms起 | 200ms |
|
||||
| 图片展示 | 1200ms | 1600ms起 | 100ms |
|
||||
| 列表项目 | 400ms | 700ms起 | 100ms |
|
||||
|
||||
#### 🌟 动画设计原则
|
||||
1. **渐进式展现**: 100-200ms递增创造瀑布效果
|
||||
2. **视觉层次**: 重要内容先出现,细节后展示
|
||||
3. **阅读节奏**: 遵循用户从上到下、从左到右的阅读习惯
|
||||
4. **性能优化**: 避免过多同时触发的动画
|
||||
5. **一致性**: 整个报告保持统一的动画风格
|
||||
|
||||
### 触发器选择
|
||||
- **load**: 页面关键信息(标题、导航)
|
||||
- **scroll**: 详细内容章节和子元素
|
||||
|
||||
## 📁 文件组织
|
||||
|
||||
### 推荐目录结构
|
||||
```
|
||||
src/pages/report/
|
||||
├── template/
|
||||
│ └── index.astro # 标准模板
|
||||
├── your-report-name/
|
||||
│ ├── index.astro # 报告页面
|
||||
│ └── images/ # 报告相关图片
|
||||
└── index.astro # 报告索引页
|
||||
```
|
||||
|
||||
### 图片资源
|
||||
将报告相关图片放在 `public/report/your-report-name/` 目录下:
|
||||
```
|
||||
public/report/
|
||||
├── your-report-name/
|
||||
│ ├── image-1.jpg
|
||||
│ ├── image-2.png
|
||||
│ └── diagram.svg
|
||||
```
|
||||
|
||||
## 🔧 自定义指南
|
||||
|
||||
### 添加新的样式类
|
||||
在模板的 `<style>` 标签中添加你的自定义样式:
|
||||
|
||||
```css
|
||||
/* 自定义样式 */
|
||||
.your-custom-class {
|
||||
/* 样式定义 */
|
||||
}
|
||||
```
|
||||
|
||||
### 添加交互功能
|
||||
在 `<script>` 标签中添加 JavaScript 交互:
|
||||
|
||||
```javascript
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 你的交互逻辑
|
||||
});
|
||||
```
|
||||
|
||||
### 修改动画效果
|
||||
调整 `AnimatedElement` 的参数:
|
||||
- `animation`: fadeInUp, fadeIn, slideInLeft 等
|
||||
- `delay`: 延迟时间(毫秒)
|
||||
- `trigger`: load, scroll, hover 等
|
||||
|
||||
## 📝 内容编写建议
|
||||
|
||||
### 章节标题
|
||||
- 使用表情符号增加视觉吸引力
|
||||
- 保持标题简洁明了
|
||||
- 层级结构清晰(level={1,2,3})
|
||||
|
||||
### 内容组织
|
||||
- 每个章节专注一个主题
|
||||
- 使用列表和卡片提高可读性
|
||||
- 重要信息使用高亮样式
|
||||
|
||||
### 图片使用
|
||||
- 统一使用 `ImageViewer` 组件
|
||||
- 保持一致的纵横比(推荐 5:3)
|
||||
- 为所有图片添加 alt 描述
|
||||
|
||||
## 🔍 SEO优化
|
||||
|
||||
### 元信息配置
|
||||
确保在 `BaseLayout` 中正确设置:
|
||||
- `title`: 页面标题
|
||||
- `description`: 页面描述
|
||||
- `type`: 页面类型(report)
|
||||
|
||||
### 语义化HTML
|
||||
- 使用正确的标题层级
|
||||
- 为章节添加 `id` 属性
|
||||
- 使用描述性的链接文本
|
||||
|
||||
## 📊 性能优化
|
||||
|
||||
### 图片优化
|
||||
- 使用适当的图片格式(WebP, AVIF)
|
||||
- 压缩图片大小
|
||||
- 使用 `loading="lazy"` 属性
|
||||
|
||||
### 动画性能
|
||||
- 避免过多同时触发的动画
|
||||
- 合理设置延迟时间
|
||||
- 在低性能设备上考虑禁用动画
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q: 侧边栏目录不显示?
|
||||
A: 确保章节使用了 `ReportSection` 组件并设置了正确的 `title` 和 `id` 属性。
|
||||
|
||||
### Q: 动画不工作?
|
||||
A: 检查 `AnimatedElement` 的导入和参数配置,确保 `delay` 和 `trigger` 设置正确。
|
||||
|
||||
### Q: 样式显示异常?
|
||||
A: 确认是否正确复制了完整的 `<style>` 标签内容。
|
||||
|
||||
### Q: 图片无法显示?
|
||||
A: 检查图片路径是否正确,确保图片文件存在于 `public/` 目录下。
|
||||
|
||||
## 🔄 更新和维护
|
||||
|
||||
### 模板更新
|
||||
当基础模板更新时,可以通过以下方式同步:
|
||||
1. 备份你的自定义内容
|
||||
2. 复制新版本模板
|
||||
3. 重新应用你的自定义配置
|
||||
|
||||
### 版本控制
|
||||
建议为每个报告创建独立的分支或提交,便于版本管理和回滚。
|
||||
|
||||
---
|
||||
|
||||
*模板版本:v1.0*
|
||||
*创建日期:2025年10月1日*
|
||||
*基于:report/20250609/index.astro*
|
||||
271
src/pages/report/template/USAGE_EXAMPLE.md
Normal file
271
src/pages/report/template/USAGE_EXAMPLE.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 技术报告模板使用示例
|
||||
|
||||
## 📋 模板文件说明
|
||||
|
||||
我们为技术报告页面创建了两套标准模板,基于成功的 `report/20250609/index.astro` 设计:
|
||||
|
||||
### 1. 完整模板 (`index.astro`)
|
||||
- **用途**: 综合性技术报告,包含完整的章节结构
|
||||
- **特点**: 包含数学公式、对比表格、图片展示等丰富功能
|
||||
- **适用**: 研究报告、技术分析、项目总结等复杂内容
|
||||
|
||||
### 2. 简化模板 (`simple.astro`)
|
||||
- **用途**: 基础技术报告,快速创建标准报告
|
||||
- **特点**: 简洁的章节结构,核心功能完备
|
||||
- **适用**: 进展报告、技术总结、实施方案等简单内容
|
||||
|
||||
## 🚀 快速使用指南
|
||||
|
||||
### 步骤1:选择模板
|
||||
```bash
|
||||
# 选择完整模板
|
||||
cp src/pages/report/template/index.astro src/pages/report/my-report/index.astro
|
||||
|
||||
# 或选择简化模板
|
||||
cp src/pages/report/template/simple.astro src/pages/report/my-report/index.astro
|
||||
```
|
||||
|
||||
### 步骤2:修改配置
|
||||
在文件顶部修改 `reportConfig`:
|
||||
|
||||
```javascript
|
||||
const reportConfig = {
|
||||
title: "我的技术报告",
|
||||
subtitle: "报告的副标题和核心描述",
|
||||
description: "用于SEO的详细描述",
|
||||
date: "2025年10月1日",
|
||||
type: "技术报告",
|
||||
actionText: "开始阅读 ↓",
|
||||
actionLink: "#overview"
|
||||
};
|
||||
```
|
||||
|
||||
### 步骤3:自定义内容
|
||||
根据需要添加、修改或删除章节内容。
|
||||
|
||||
## 📚 模板组件库
|
||||
|
||||
### 基础组件
|
||||
- `<ReportSection>`: 标准章节容器
|
||||
- `<AnimatedElement>`: 进场动画控制
|
||||
- `<Container>`: 玻璃态内容容器
|
||||
- `<ReportSidebar>`: 自动生成目录
|
||||
|
||||
### 扩展组件
|
||||
- `<GlassTable>`: 对比表格
|
||||
- `<MathFormula>`: 数学公式渲染
|
||||
- `<ImageViewer>`: 响应式图片展示
|
||||
|
||||
### 预定义样式类
|
||||
- `.goal-section`: 内容区域
|
||||
- `.challenge-card`: 卡片样式
|
||||
- `.deep-dive-card`: 深度分析卡片
|
||||
- `.summary-highlight`: 总结高亮
|
||||
|
||||
## 🎨 动画时序配置
|
||||
|
||||
### 推荐时序
|
||||
```astro
|
||||
<!-- 标题 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
|
||||
|
||||
<!-- 第一章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
|
||||
|
||||
<!-- 后续章节,每个递增200ms -->
|
||||
<AnimatedElement animation="fadeInUp" delay={600} trigger="scroll">
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
```
|
||||
|
||||
## 📁 文件结构示例
|
||||
|
||||
```
|
||||
src/pages/report/
|
||||
├── template/ # 模板文件夹
|
||||
│ ├── index.astro # 完整模板
|
||||
│ ├── simple.astro # 简化模板
|
||||
│ └── README.md # 使用说明
|
||||
├── my-report-2025/ # 你的报告
|
||||
│ └── index.astro # 基于模板创建
|
||||
├── another-report/ # 另一个报告
|
||||
│ └── index.astro
|
||||
└── index.astro # 报告索引页
|
||||
|
||||
public/report/
|
||||
├── my-report-2025/ # 报告相关图片
|
||||
│ ├── figure-1.jpg
|
||||
│ └── diagram.png
|
||||
```
|
||||
|
||||
## 🛠️ 自定义指南
|
||||
|
||||
### 添加新章节
|
||||
```astro
|
||||
<AnimatedElement animation="fadeInUp" delay={1200} trigger="scroll">
|
||||
<section id="new-section">
|
||||
<ReportSection title="🔧 新章节" subtitle="章节描述" level={2}>
|
||||
<!-- 章节内容 -->
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
```
|
||||
|
||||
### 🎨 子容器延迟动画 (最新功能)
|
||||
为了增强用户体验和视觉层次,现在可以为容器内的子元素添加延迟动画:
|
||||
|
||||
#### 基础卡片网格 + 子动画
|
||||
```astro
|
||||
<AnimatedElement animation="fadeInUp" delay={600} trigger="scroll">
|
||||
<ReportSection title="技术对比" level={2}>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- 卡片依次淡入 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={700} trigger="scroll">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">方案A</h3>
|
||||
<p class="challenge-description">方案A的详细描述...</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">方案B</h3>
|
||||
<p class="challenge-description">方案B的详细描述...</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={900} trigger="scroll">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">方案C</h3>
|
||||
<p class="challenge-description">方案C的详细描述...</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</AnimatedElement>
|
||||
```
|
||||
|
||||
#### 深度分析嵌套动画
|
||||
```astro
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<div class="deep-dive-card">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="text-5xl mr-4">🛠️</div>
|
||||
<div>
|
||||
<p class="component-label">核心技术</p>
|
||||
<h3 class="component-title">技术实现方案</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700">主要技术描述...</p>
|
||||
|
||||
<!-- 子元素延迟出现 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
<h4 class="font-semibold text-lg mt-4 mb-2 text-blue-600">关键步骤:</h4>
|
||||
<ul class="list-disc text-gray-600 space-y-2">
|
||||
<li>步骤1:环境准备和配置</li>
|
||||
<li>步骤2:核心算法实现</li>
|
||||
<li>步骤3:测试验证和优化</li>
|
||||
</ul>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
```
|
||||
|
||||
#### 图片展示渐进加载
|
||||
```astro
|
||||
<div class="proof-image-container">
|
||||
<AnimatedElement animation="fadeInUp" delay={1200} trigger="scroll">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer src="/report/my-report/result1.jpg" alt="结果1" />
|
||||
<p>实验结果对比</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={1300} trigger="scroll">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer src="/report/my-report/result2.jpg" alt="结果2" />
|
||||
<p>性能分析图表</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={1400} trigger="scroll">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer src="/report/my-report/result3.jpg" alt="结果3" />
|
||||
<p>效果验证截图</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 🚀 动画延迟时间建议
|
||||
- **卡片网格**: 主容器600ms,子卡片700ms/800ms/900ms...
|
||||
- **深度分析**: 主卡片800ms,子内容1000ms
|
||||
- **图片展示**: 连续100ms间隔 (1200ms/1300ms/1400ms...)
|
||||
- **列表项目**: 主标题600ms,列表项700ms/800ms/900ms...
|
||||
|
||||
### 传统卡片布局(单层动画)
|
||||
```astro
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">卡片标题</h3>
|
||||
<p class="challenge-description">卡片内容</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 添加对比表格
|
||||
```astro
|
||||
<GlassTable
|
||||
headers={['特性', '方案A', '方案B']}
|
||||
rows={[
|
||||
['性能', '高', '中'],
|
||||
['成本', '低', '高']
|
||||
]}
|
||||
striped={true}
|
||||
bordered={true}
|
||||
/>
|
||||
```
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
### 内容检查
|
||||
- [ ] 标题和副标题已修改
|
||||
- [ ] 报告日期已更新
|
||||
- [ ] 各章节内容已填写
|
||||
- [ ] 图片路径已更新
|
||||
- [ ] 链接地址已修改
|
||||
|
||||
### 技术检查
|
||||
- [ ] `npm run build` 构建成功
|
||||
- [ ] 动画效果正常
|
||||
- [ ] 侧边栏目录生成正确
|
||||
- [ ] 响应式布局正常
|
||||
- [ ] 图片正常显示
|
||||
|
||||
### SEO检查
|
||||
- [ ] 页面标题描述性强
|
||||
- [ ] meta描述内容完整
|
||||
- [ ] 章节标题结构合理
|
||||
- [ ] 图片alt描述完整
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
### 内容组织
|
||||
1. **逐步披露**: 按逻辑顺序组织章节
|
||||
2. **重点突出**: 使用高亮和卡片强调要点
|
||||
3. **图文并茂**: 适当使用图表和公式
|
||||
4. **总结呼应**: 开头概述,结尾总结
|
||||
|
||||
### 样式一致性
|
||||
1. **统一配色**: 保持与整站风格一致
|
||||
2. **规范间距**: 使用预定义的spacing类
|
||||
3. **响应式**: 确保移动端体验良好
|
||||
4. **动画流畅**: 合理设置延迟时间
|
||||
|
||||
### 性能优化
|
||||
1. **图片优化**: 使用适当格式和尺寸
|
||||
2. **懒加载**: 大图使用ImageViewer组件
|
||||
3. **动画性能**: 避免过多同时触发的动画
|
||||
4. **代码精简**: 删除不需要的样式和脚本
|
||||
|
||||
---
|
||||
|
||||
*创建日期: 2025年10月1日*
|
||||
*版本: v1.0*
|
||||
*基于: report/20250609/index.astro*
|
||||
83
src/pages/report/template/WIDTH_CONFIG_GUIDE.md
Normal file
83
src/pages/report/template/WIDTH_CONFIG_GUIDE.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 统一宽度配置快速指南 ✅ 已同步到Template
|
||||
|
||||
> **最新更新:** 基于 report/20250722 的统一宽度配置已成功同步到 template/index.astro
|
||||
|
||||
## 🎯 一分钟上手
|
||||
|
||||
### 第1步:定位配置区域
|
||||
在你的报告文件的 `<style>` 标签中找到:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--max-content-width: 1200px; /* 👈 这里! */
|
||||
}
|
||||
```
|
||||
|
||||
### 第2步:选择合适的宽度
|
||||
|
||||
```css
|
||||
/* 学术报告 - 文字为主 */
|
||||
--max-content-width: 1024px;
|
||||
|
||||
/* 技术报告 - 平衡布局(推荐) */
|
||||
--max-content-width: 1200px;
|
||||
|
||||
/* 展示报告 - 图表较多 */
|
||||
--max-content-width: 1400px;
|
||||
```
|
||||
|
||||
### 第3步:保存并查看效果
|
||||
修改后保存文件,所有ReportSection会立即应用新宽度!
|
||||
|
||||
## ✅ 同步状态
|
||||
|
||||
- **20250722报告**:✅ 已应用统一宽度配置
|
||||
- **Template模板**:✅ 已同步统一宽度配置(2025-10-01更新)
|
||||
- **配置方式**:使用CSS变量 `--max-content-width` 统一管理
|
||||
|
||||
## 🔧 技术原理
|
||||
|
||||
### CSS变量系统
|
||||
```css
|
||||
/* 统一宽度类 - 自动应用 */
|
||||
.report-header,
|
||||
.container {
|
||||
max-width: var(--max-content-width);
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
```
|
||||
|
||||
### 自动应用组件
|
||||
- **ReportSection**: 报告的主要内容区域
|
||||
- **Container**: 所有容器组件
|
||||
- **Header区域**: 报告标题和元信息
|
||||
- **表格组件**: GlassTable等数据展示
|
||||
- **图片展示**: ImageViewer等媒体内容
|
||||
|
||||
## ✨ 优势对比
|
||||
|
||||
### 传统方式 ❌
|
||||
```css
|
||||
/* 需要在多处分别设置 */
|
||||
.header { max-width: 1200px; }
|
||||
.content { max-width: 1200px; }
|
||||
.section1 { max-width: 1200px; }
|
||||
.section2 { max-width: 1200px; }
|
||||
/* ...更多重复设置 */
|
||||
```
|
||||
|
||||
### 新方式 ✅
|
||||
```css
|
||||
/* 一处设置,全局应用 */
|
||||
:root {
|
||||
--max-content-width: 1200px;
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 实际效果
|
||||
|
||||
修改前:各个章节宽度不一致,视觉不统一
|
||||
修改后:所有章节完美对齐,专业感大幅提升
|
||||
|
||||
这就是基于20250722报告优化后的先进配置方式!
|
||||
856
src/pages/report/template/index.astro
Normal file
856
src/pages/report/template/index.astro
Normal file
@@ -0,0 +1,856 @@
|
||||
---
|
||||
/**
|
||||
* 技术报告页面标准模板 (完整版) - 2025更新版
|
||||
* 基于 report/20250722/index.astro 的统一宽度配置优化设计
|
||||
* 使用CSS变量实现所有ReportSection的统一宽度管理
|
||||
*/
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import Header from '../../../components/Header.astro';
|
||||
import Footer from '../../../components/Footer.astro';
|
||||
import ReportSection from '../../../components/report/ReportSection.astro';
|
||||
import GlassTable from '../../../components/common/GlassTable.astro';
|
||||
import MathFormula from '../../../components/common/MathFormula.astro';
|
||||
import ImageViewer from '../../../components/common/ImageViewer.astro';
|
||||
import CodeBlock from '../../../components/common/CodeBlock.astro';
|
||||
import Container from '../../../components/Container.astro';
|
||||
import AnimatedElement from '../../../components/AnimatedElement.astro';
|
||||
import ReportSidebar from '../../../components/report/ReportSidebar.astro';
|
||||
|
||||
// 导入动画时序计算工具
|
||||
import {
|
||||
getSectionBaseDelay,
|
||||
getChildDelay,
|
||||
getNestedDelay,
|
||||
getImageDelay
|
||||
} from '../../../scripts/animation-timing';
|
||||
|
||||
// 定义各章节的基础延迟时间
|
||||
const TEMPLATE_SECTION_DELAYS = {
|
||||
HERO: 200,
|
||||
OVERVIEW: getSectionBaseDelay(0), // 400ms
|
||||
ANALYSIS: getSectionBaseDelay(1), // 600ms
|
||||
SOLUTION: getSectionBaseDelay(2), // 800ms
|
||||
COMPARISON: getSectionBaseDelay(3), // 1000ms
|
||||
RESULTS: getSectionBaseDelay(4), // 1200ms
|
||||
CONCLUSION: getSectionBaseDelay(5), // 1400ms
|
||||
};
|
||||
|
||||
// 报告配置 - 请根据实际情况修改
|
||||
const reportConfig = {
|
||||
title: "技术报告标题",
|
||||
subtitle: "技术报告副标题,描述报告的核心内容和研究方向。",
|
||||
description: "技术报告的详细描述,用于SEO和页面元信息。",
|
||||
date: "2025年XX月XX日",
|
||||
type: "技术报告",
|
||||
actionText: "开始阅读 ↓",
|
||||
actionLink: "#overview"
|
||||
};
|
||||
|
||||
// 示例代码配置 - 请根据需要修改
|
||||
const sampleCodeExamples = {
|
||||
javascript: {
|
||||
title: "JavaScript 示例",
|
||||
language: "javascript",
|
||||
code: `function fibonacci(n) {
|
||||
if (n <= 1) return n;
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
console.log(fibonacci(10)); // 输出: 55`
|
||||
},
|
||||
python: {
|
||||
title: "Python 示例",
|
||||
language: "python",
|
||||
code: `def quick_sort(arr):
|
||||
if len(arr) <= 1:
|
||||
return arr
|
||||
pivot = arr[len(arr) // 2]
|
||||
left = [x for x in arr if x < pivot]
|
||||
middle = [x for x in arr if x == pivot]
|
||||
right = [x for x in arr if x > pivot]
|
||||
return quick_sort(left) + middle + quick_sort(right)
|
||||
|
||||
print(quick_sort([3, 6, 8, 10, 1, 2, 1]))`
|
||||
},
|
||||
css: {
|
||||
title: "CSS 样式示例",
|
||||
language: "css",
|
||||
code: `.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
}`
|
||||
}
|
||||
};
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={`${reportConfig.title} - Jiao77`}
|
||||
description={reportConfig.description}
|
||||
type="report"
|
||||
>
|
||||
<Header
|
||||
pageTitle={reportConfig.title}
|
||||
showPageTitle={true}
|
||||
description={reportConfig.subtitle}
|
||||
/>
|
||||
|
||||
<main class="report-main">
|
||||
<div class="report-layout container mx-auto px-4">
|
||||
<ReportSidebar title="报告目录" toggleLabel="目录" />
|
||||
<div class="report-content" data-report-content>
|
||||
|
||||
<!-- 报告标题区域 -->
|
||||
<section id="intro" class="report-header scroll-mt-16">
|
||||
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
|
||||
<Container
|
||||
variant="glass"
|
||||
size="full"
|
||||
padding="xl"
|
||||
className="text-center mb-12 mt-24"
|
||||
>
|
||||
<h1 class="report-title">{reportConfig.title}</h1>
|
||||
<p class="report-subtitle">{reportConfig.subtitle}</p>
|
||||
<div class="report-meta">
|
||||
<span class="report-date">报告日期:{reportConfig.date}</span>
|
||||
<span class="report-type">{reportConfig.type}</span>
|
||||
</div>
|
||||
<div class="report-action">
|
||||
<a href={reportConfig.actionLink} class="report-cta-button">{reportConfig.actionText}</a>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
</section>
|
||||
|
||||
<!-- 概述章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={TEMPLATE_SECTION_DELAYS.OVERVIEW} trigger="scroll">
|
||||
<section id="overview">
|
||||
<ReportSection
|
||||
title="📖 概述"
|
||||
subtitle="报告的主要内容和背景介绍"
|
||||
level={2}
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="goal-section">
|
||||
<h3 class="section-heading">背景介绍</h3>
|
||||
<p>
|
||||
在这里详细描述报告的背景和研究动机。可以包含<strong>重点强调的文字</strong>,
|
||||
以及相关的技术背景和问题陈述。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="goal-section">
|
||||
<h3 class="section-heading">主要内容</h3>
|
||||
<ul class="research-pillars">
|
||||
<li>
|
||||
<strong>内容要点一:</strong>
|
||||
详细描述第一个主要内容点。
|
||||
</li>
|
||||
<li>
|
||||
<strong>内容要点二:</strong>
|
||||
详细描述第二个主要内容点。
|
||||
</li>
|
||||
<li>
|
||||
<strong>内容要点三:</strong>
|
||||
详细描述第三个主要内容点。
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 技术分析章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={TEMPLATE_SECTION_DELAYS.ANALYSIS} trigger="scroll">
|
||||
<section id="analysis">
|
||||
<ReportSection
|
||||
title="🔬 技术分析"
|
||||
subtitle="核心技术概念和挑战的详细分析"
|
||||
level={2}
|
||||
>
|
||||
<!-- 🎨 子容器延迟动画示例:为各个卡片添加逐个出现的动画效果 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.ANALYSIS, 0)} trigger="scroll">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">技术挑战一</h3>
|
||||
<p class="challenge-description">描述第一个技术挑战的具体内容和影响。</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.ANALYSIS, 1)} trigger="scroll">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">技术挑战二</h3>
|
||||
<p class="challenge-description">描述第二个技术挑战的具体内容和影响。</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.ANALYSIS, 2)} trigger="scroll">
|
||||
<div class="challenge-card">
|
||||
<h3 class="challenge-title">技术挑战三</h3>
|
||||
<p class="challenge-description">描述第三个技术挑战的具体内容和影响。</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 解决方案章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={TEMPLATE_SECTION_DELAYS.SOLUTION} trigger="scroll">
|
||||
<section id="solution">
|
||||
<ReportSection
|
||||
title="💡 解决方案"
|
||||
subtitle="针对技术挑战提出的创新解决方案和实施方法"
|
||||
level={2}
|
||||
>
|
||||
<!-- 🎨 数学公式区域延迟动画示例 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.SOLUTION, 0)} trigger="scroll">
|
||||
<div class="math-intro mb-6">
|
||||
<p class="text-center text-lg text-gray-600 max-w-3xl mx-auto">
|
||||
如果报告涉及数学公式,可以使用 MathFormula 组件进行展示:
|
||||
</p>
|
||||
<AnimatedElement animation="fadeInUp" delay={getNestedDelay(getChildDelay(TEMPLATE_SECTION_DELAYS.SOLUTION, 0), 1)} trigger="scroll">
|
||||
<div class="text-center mt-4">
|
||||
<p class="text-lg text-gray-600 mb-3">示例公式:</p>
|
||||
<MathFormula
|
||||
formula="f(x) = ax^2 + bx + c"
|
||||
className="example-formula"
|
||||
/>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 🎨 深度分析卡片的子容器延迟动画示例 -->
|
||||
<div class="space-y-8">
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.SOLUTION, 1)} trigger="scroll">
|
||||
<div class="deep-dive-card">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="text-5xl mr-4">🛠️</div>
|
||||
<div>
|
||||
<p class="component-label">核心方案</p>
|
||||
<h3 class="component-title">技术方案一</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 mb-3">详细描述第一个技术方案的原理和实现方式。</p>
|
||||
<AnimatedElement animation="fadeInUp" delay={getNestedDelay(getChildDelay(TEMPLATE_SECTION_DELAYS.SOLUTION, 1), 1)} trigger="scroll">
|
||||
<h4 class="font-semibold text-lg mt-4 mb-2 text-blue-600">实现步骤:</h4>
|
||||
<ul class="list-disc text-gray-600 space-y-2">
|
||||
<li>步骤一:具体的实施方法和技术要点</li>
|
||||
<li>步骤二:具体的实施方法和技术要点</li>
|
||||
<li>步骤三:具体的实施方法和技术要点</li>
|
||||
</ul>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.SOLUTION, 2)} trigger="scroll">
|
||||
<div class="deep-dive-card">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="text-5xl mr-4">⚡</div>
|
||||
<div>
|
||||
<p class="component-label">优化策略</p>
|
||||
<h3 class="component-title">技术方案二</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 mb-3">详细描述第二个技术方案的优化策略和核心技术。</p>
|
||||
<AnimatedElement animation="fadeInUp" delay={getNestedDelay(getChildDelay(TEMPLATE_SECTION_DELAYS.SOLUTION, 2), 1)} trigger="scroll">
|
||||
<h4 class="font-semibold text-lg mt-4 mb-2 text-blue-600">关键技术:</h4>
|
||||
<ul class="list-disc text-gray-600 space-y-2">
|
||||
<li>关键技术一:技术原理和应用优势</li>
|
||||
<li>关键技术二:技术原理和应用优势</li>
|
||||
</ul>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
|
||||
<div class="summary-highlight">
|
||||
<p class="summary-highlight__text">核心优势总结:在这里总结解决方案的核心优势和创新点,突出技术方案的独特价值。</p>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 对比分析章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={TEMPLATE_SECTION_DELAYS.COMPARISON} trigger="scroll">
|
||||
<section id="comparison">
|
||||
<ReportSection
|
||||
title="📊 对比分析"
|
||||
subtitle="不同技术方案的详细对比和评估"
|
||||
level={2}
|
||||
>
|
||||
<div class="mt-12">
|
||||
<h3 class="text-2xl font-bold text-center mb-6 text-blue-500">技术方案对比表</h3>
|
||||
<GlassTable
|
||||
headers={['对比维度', '方案A', '方案B', '方案C', '推荐方案']}
|
||||
rows={[
|
||||
['技术复杂度', '高', '中', '低', '方案B'],
|
||||
['性能表现', '优秀', '良好', '一般', '方案A'],
|
||||
['实施成本', '高', '中', '低', '方案B'],
|
||||
['维护难度', '低', '中', '高', '方案A'],
|
||||
['扩展性', '优秀', '良好', '一般', '方案A'],
|
||||
['综合评价', '⭐⭐⭐⭐⭐', '⭐⭐⭐⭐', '⭐⭐⭐', '方案A']
|
||||
]}
|
||||
striped={true}
|
||||
bordered={true}
|
||||
className="tech-comparison-table"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 代码示例部分 -->
|
||||
<div class="mt-16">
|
||||
<h3 class="text-2xl font-bold text-center mb-8 text-blue-500">💻 代码实现示例</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.COMPARISON, 0)} trigger="scroll">
|
||||
<CodeBlock
|
||||
title={sampleCodeExamples.javascript.title}
|
||||
language={sampleCodeExamples.javascript.language}
|
||||
showLineNumbers={true}
|
||||
code={sampleCodeExamples.javascript.code}
|
||||
/>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.COMPARISON, 1)} trigger="scroll">
|
||||
<CodeBlock
|
||||
title={sampleCodeExamples.python.title}
|
||||
language={sampleCodeExamples.python.language}
|
||||
showLineNumbers={true}
|
||||
code={sampleCodeExamples.python.code}
|
||||
/>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.COMPARISON, 2)} trigger="scroll">
|
||||
<CodeBlock
|
||||
title={sampleCodeExamples.css.title}
|
||||
language={sampleCodeExamples.css.language}
|
||||
showLineNumbers={true}
|
||||
code={sampleCodeExamples.css.code}
|
||||
/>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 实验结果章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={TEMPLATE_SECTION_DELAYS.RESULTS} trigger="scroll">
|
||||
<section id="results">
|
||||
<ReportSection
|
||||
title="🧪 实验结果"
|
||||
subtitle="技术方案的实际测试结果和性能数据"
|
||||
level={2}
|
||||
>
|
||||
<div class="space-y-10">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-center mb-4 text-gray-700">实验数据展示</h3>
|
||||
<!-- 🎨 图片容器的子元素延迟动画示例 -->
|
||||
<div class="proof-image-container">
|
||||
<AnimatedElement animation="fadeInUp" delay={getImageDelay(TEMPLATE_SECTION_DELAYS.RESULTS, 0)} trigger="scroll">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer
|
||||
src="/report/template-images/result-1.jpg"
|
||||
alt="实验结果图表1"
|
||||
className="proof-image-viewer"
|
||||
aspectRatio="5 / 3"
|
||||
/>
|
||||
<p>性能测试结果对比</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={getImageDelay(TEMPLATE_SECTION_DELAYS.RESULTS, 1)} trigger="scroll">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer
|
||||
src="/report/template-images/result-2.jpg"
|
||||
alt="实验结果图表2"
|
||||
className="proof-image-viewer"
|
||||
aspectRatio="5 / 3"
|
||||
/>
|
||||
<p>精度测试结果分析</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={getImageDelay(TEMPLATE_SECTION_DELAYS.RESULTS, 2)} trigger="scroll">
|
||||
<div class="proof-image-item">
|
||||
<ImageViewer
|
||||
src="/report/template-images/result-3.jpg"
|
||||
alt="实验结果图表3"
|
||||
className="proof-image-viewer"
|
||||
aspectRatio="5 / 3"
|
||||
/>
|
||||
<p>效率提升对比</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 总结与展望章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={TEMPLATE_SECTION_DELAYS.CONCLUSION} trigger="scroll">
|
||||
<section id="conclusion">
|
||||
<ReportSection
|
||||
title="🚀 总结与展望"
|
||||
subtitle="对报告内容进行总结,并提出未来的发展方向"
|
||||
level={2}
|
||||
>
|
||||
<!-- 🎨 总结与展望卡片的子容器延迟动画示例 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.CONCLUSION, 0)} trigger="scroll">
|
||||
<div class="deep-dive-card">
|
||||
<h3 class="text-2xl font-bold mb-4 text-blue-500">🎯 核心成果</h3>
|
||||
<ul class="space-y-3 text-gray-700 list-disc list-inside">
|
||||
<li><strong>技术突破:</strong>详细描述取得的重要技术突破。</li>
|
||||
<li><strong>性能提升:</strong>详细描述性能改进的具体数据。</li>
|
||||
<li><strong>应用价值:</strong>详细描述技术的实际应用价值。</li>
|
||||
<li><strong>创新点:</strong>详细描述技术方案的创新之处。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
|
||||
<AnimatedElement animation="fadeInUp" delay={getChildDelay(TEMPLATE_SECTION_DELAYS.CONCLUSION, 1)} trigger="scroll">
|
||||
<div class="deep-dive-card">
|
||||
<h3 class="text-2xl font-bold mb-4 text-blue-500">📅 未来规划</h3>
|
||||
<AnimatedElement animation="fadeInUp" delay={getNestedDelay(getChildDelay(TEMPLATE_SECTION_DELAYS.CONCLUSION, 1), 1)} trigger="scroll">
|
||||
<ul class="space-y-4 text-gray-700">
|
||||
<li class="flex items-start">
|
||||
<span class="text-blue-500 font-bold w-32 shrink-0">短期目标</span>
|
||||
<span>描述近期要完成的技术优化和功能完善。</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-green-500 font-bold w-32 shrink-0">中期目标</span>
|
||||
<span>描述中期的技术发展方向和应用扩展。</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="text-purple-500 font-bold w-32 shrink-0">长期目标</span>
|
||||
<span>描述长期的技术愿景和产业化应用。</span>
|
||||
</li>
|
||||
</ul>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
// 交互式功能示例
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 可以在这里添加报告特定的交互功能
|
||||
console.log('技术报告页面已加载完成');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* === 报告页面核心样式 === */
|
||||
.report-main {
|
||||
--report-color-primary: #5f7a99;
|
||||
--report-color-primary-strong: #4a627b;
|
||||
--report-color-primary-deep: #384b5f;
|
||||
--report-color-primary-soft: #8ea3b8;
|
||||
--report-color-accent: #a8bdc9;
|
||||
--report-color-highlight: rgba(95, 122, 153, 0.2);
|
||||
--report-color-soft-glow: rgba(148, 173, 196, 0.12);
|
||||
--report-color-glass-border: rgba(95, 122, 153, 0.28);
|
||||
--report-color-text: #2f3844;
|
||||
--report-color-subtext: #566171;
|
||||
--report-color-muted: #6d7885;
|
||||
--report-color-surface: rgba(255, 255, 255, 0.65);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 2rem;
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
background: transparent;
|
||||
color: var(--report-color-text);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.report-main::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(143, 167, 187, 0.16) 0%,
|
||||
rgba(164, 186, 202, 0.1) 25%,
|
||||
rgba(182, 201, 214, 0.08) 50%,
|
||||
rgba(154, 175, 192, 0.1) 75%,
|
||||
rgba(122, 146, 165, 0.22) 100%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* === 布局样式 === */
|
||||
.report-layout {
|
||||
--report-sidebar-width: clamp(260px, 22vw, 320px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx-auto { margin-left: auto; margin-right: auto; }
|
||||
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
||||
|
||||
/* === 标题区域样式 === */
|
||||
.report-header {
|
||||
padding: 4rem 0 2rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: var(--report-color-primary);
|
||||
margin: 0 0 1rem 0;
|
||||
background: linear-gradient(135deg, var(--report-color-primary-soft), var(--report-color-primary), var(--report-color-primary-strong));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: var(--report-color-subtext);
|
||||
margin: 0 0 2rem 0;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.report-date, .report-type {
|
||||
background: rgba(95, 122, 153, 0.12);
|
||||
color: var(--report-color-primary-deep);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.report-action {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.report-cta-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.9rem 2.4rem;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, var(--report-color-primary), var(--report-color-primary-strong));
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
box-shadow: 0 14px 32px rgba(56, 75, 95, 0.28);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease;
|
||||
}
|
||||
|
||||
.report-cta-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 18px 40px rgba(56, 75, 95, 0.32);
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.report-cta-button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 12px 28px rgba(56, 75, 95, 0.28);
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
|
||||
/* === 内容区域样式 === */
|
||||
.goal-section {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--report-color-glass-border);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--report-color-primary);
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.research-pillars {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.research-pillars li {
|
||||
background: rgba(95, 122, 153, 0.08);
|
||||
border-left: 4px solid rgba(95, 122, 153, 0.65);
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 0 0.5rem 0.5rem 0;
|
||||
}
|
||||
|
||||
/* === 卡片样式 === */
|
||||
.challenge-card {
|
||||
background: linear-gradient(135deg, rgba(95, 122, 153, 0.18), rgba(74, 98, 123, 0.16));
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.85rem;
|
||||
border: 1px solid rgba(74, 98, 123, 0.35);
|
||||
box-shadow: 0 12px 32px rgba(56, 75, 95, 0.18);
|
||||
backdrop-filter: blur(16px);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.challenge-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 18px 44px rgba(56, 75, 95, 0.25);
|
||||
border-color: rgba(74, 98, 123, 0.48);
|
||||
}
|
||||
|
||||
.challenge-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--report-color-primary-strong);
|
||||
}
|
||||
|
||||
.challenge-description {
|
||||
color: var(--report-color-subtext);
|
||||
}
|
||||
|
||||
.deep-dive-card {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--report-color-glass-border);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* === 组件样式 === */
|
||||
.component-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: rgba(95, 122, 153, 0.85);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.component-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--report-color-primary);
|
||||
margin: 0.25rem 0 0 0;
|
||||
}
|
||||
|
||||
/* === 高亮样式 === */
|
||||
.summary-highlight {
|
||||
margin-top: 3rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 56rem;
|
||||
padding: 1.75rem 2.5rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, rgba(168, 189, 201, 0.28), rgba(95, 122, 153, 0.16));
|
||||
border-radius: 1.25rem;
|
||||
border: 1px solid rgba(95, 122, 153, 0.32);
|
||||
box-shadow: 0 18px 48px rgba(56, 75, 95, 0.22);
|
||||
backdrop-filter: blur(18px);
|
||||
color: var(--report-color-primary-deep);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.summary-highlight::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top left, rgba(255, 255, 255, 0.5), transparent 55%);
|
||||
opacity: 0.65;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.summary-highlight__text {
|
||||
position: relative;
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* === 数学公式样式 === */
|
||||
.math-intro {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--report-color-glass-border);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* === 表格样式 === */
|
||||
.tech-comparison-table {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.tech-comparison-table .glass-table th:last-child {
|
||||
background: rgba(95, 122, 153, 0.18);
|
||||
color: var(--report-color-primary-deep);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tech-comparison-table .glass-table td:last-child {
|
||||
background: rgba(95, 122, 153, 0.12);
|
||||
font-weight: 600;
|
||||
color: var(--report-color-primary-strong);
|
||||
}
|
||||
|
||||
/* === 图片样式 === */
|
||||
.proof-image-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.proof-image-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:global(.proof-image-viewer) {
|
||||
margin: 0;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
:global(.proof-image-viewer:hover) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(15, 23, 42, 0.15);
|
||||
}
|
||||
|
||||
.proof-image-item p {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: var(--report-color-muted);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* === 响应式设计 === */
|
||||
@media (max-width: 768px) {
|
||||
.report-title { font-size: 2rem; }
|
||||
.report-subtitle { font-size: 1.1rem; }
|
||||
.report-meta {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.deep-dive-card { padding: 1.5rem; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* 统一宽度配置 - 先进的宽度管理方式 */
|
||||
/*
|
||||
* 要修改整个页面的最大宽度,只需要修改下面的 --max-content-width 值
|
||||
* 建议的宽度选项:
|
||||
* - 1024px (较窄,适合阅读)
|
||||
* - 1200px (当前设置,平衡)
|
||||
* - 1400px (较宽,适合表格)
|
||||
*/
|
||||
:root {
|
||||
--max-content-width: 1200px;
|
||||
}
|
||||
|
||||
/* 统一宽度类 - 所有内容区域使用相同的最大宽度 */
|
||||
.report-header,
|
||||
.container {
|
||||
max-width: var(--max-content-width);
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 专门的宽度配置,可根据需要调整 */
|
||||
.report-width {
|
||||
max-width: var(--max-content-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.report-main {
|
||||
padding-left: clamp(calc(300px + 2.5rem), calc((100vw - 1200px) / 2 + 1rem), calc(320px + 3rem));
|
||||
padding-right: clamp(1rem, calc((100vw - 1200px) / 2 + 1rem), 2rem);
|
||||
}
|
||||
.report-layout { max-width: var(--max-content-width); margin: 0 auto; position: relative; }
|
||||
.report-content { max-width: 100%; width: 100%; margin-left: 0; }
|
||||
}
|
||||
|
||||
/* === 工具样式类 === */
|
||||
.grid { display: grid; }
|
||||
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||
@media (min-width: 768px) {
|
||||
.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
.gap-8 { gap: 2rem; }
|
||||
.space-y-6 > * + * { margin-top: 1.5rem; }
|
||||
.space-y-8 > * + * { margin-top: 2rem; }
|
||||
.space-y-10 > * + * { margin-top: 2.5rem; }
|
||||
.space-y-3 > * + * { margin-top: 0.75rem; }
|
||||
.space-y-4 > * + * { margin-top: 1rem; }
|
||||
.flex { display: flex; }
|
||||
.items-center { align-items: center; }
|
||||
.items-start { align-items: flex-start; }
|
||||
.mb-8 { margin-bottom: 2rem; }
|
||||
.mb-6 { margin-bottom: 1.5rem; }
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mb-3 { margin-bottom: 0.75rem; }
|
||||
.mb-2 { margin-bottom: 0.5rem; }
|
||||
.mt-4 { margin-top: 1rem; }
|
||||
.mt-12 { margin-top: 3rem; }
|
||||
.mr-4 { margin-right: 1rem; }
|
||||
.text-center { text-align: center; }
|
||||
.text-5xl { font-size: 3rem; line-height: 1; }
|
||||
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
|
||||
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
|
||||
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
|
||||
.font-bold { font-weight: 700; }
|
||||
.font-semibold { font-weight: 600; }
|
||||
.text-gray-700 { color: var(--report-color-text); }
|
||||
.text-gray-600 { color: var(--report-color-subtext); }
|
||||
.text-blue-500 { color: var(--report-color-primary); }
|
||||
.text-blue-600 { color: var(--report-color-primary-strong); }
|
||||
.text-green-500 { color: #6f8c83; }
|
||||
.text-purple-500 { color: #7f7a94; }
|
||||
.w-32 { width: 8rem; }
|
||||
.shrink-0 { flex-shrink: 0; }
|
||||
.max-w-3xl { max-width: 48rem; }
|
||||
.list-disc { list-style-type: disc; }
|
||||
.list-inside { list-style-position: inside; }
|
||||
</style>
|
||||
434
src/pages/report/template/simple.astro
Normal file
434
src/pages/report/template/simple.astro
Normal file
@@ -0,0 +1,434 @@
|
||||
---
|
||||
/**
|
||||
* 技术报告简化模板
|
||||
* 适用于快速创建基础技术报告
|
||||
*
|
||||
* 使用说明:
|
||||
* 1. 修改下方配置信息
|
||||
* 2. 添加你的报告章节
|
||||
* 3. 替换示例内容
|
||||
*/
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import Header from '../../../components/Header.astro';
|
||||
import Footer from '../../../components/Footer.astro';
|
||||
import ReportSection from '../../../components/report/ReportSection.astro';
|
||||
import Container from '../../../components/Container.astro';
|
||||
import AnimatedElement from '../../../components/AnimatedElement.astro';
|
||||
import ReportSidebar from '../../../components/report/ReportSidebar.astro';
|
||||
|
||||
// 报告配置 - 请修改以下信息
|
||||
const reportConfig = {
|
||||
title: "技术报告标题",
|
||||
subtitle: "报告副标题描述",
|
||||
description: "报告的详细描述,用于SEO",
|
||||
date: "2025年XX月XX日",
|
||||
actionText: "开始阅读 ↓"
|
||||
};
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={`${reportConfig.title} - Jiao77`}
|
||||
description={reportConfig.description}
|
||||
type="report"
|
||||
>
|
||||
<Header />
|
||||
|
||||
<main class="report-main">
|
||||
<div class="report-layout container mx-auto px-4">
|
||||
<ReportSidebar title="报告目录" toggleLabel="目录" />
|
||||
<div class="report-content" data-report-content>
|
||||
|
||||
<!-- 报告标题 -->
|
||||
<section id="intro" class="report-header scroll-mt-16">
|
||||
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
|
||||
<Container variant="glass" size="full" padding="xl" className="text-center mb-12 mt-24">
|
||||
<h1 class="report-title">{reportConfig.title}</h1>
|
||||
<p class="report-subtitle">{reportConfig.subtitle}</p>
|
||||
<div class="report-meta">
|
||||
<span class="report-date">报告日期:{reportConfig.date}</span>
|
||||
<span class="report-type">技术报告</span>
|
||||
</div>
|
||||
<div class="report-action">
|
||||
<a href="#overview" class="bg-[#27797f] text-white font-bold py-3 px-8 rounded-full hover:bg-[#1e6066] transition duration-300">
|
||||
{reportConfig.actionText}
|
||||
</a>
|
||||
</div>
|
||||
</Container>
|
||||
</AnimatedElement>
|
||||
</section>
|
||||
|
||||
<!-- 概述章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
|
||||
<section id="overview">
|
||||
<ReportSection title="📖 概述" subtitle="报告的主要内容和背景介绍" level={2}>
|
||||
<div class="space-y-6">
|
||||
<div class="content-section">
|
||||
<h3 class="section-heading">背景介绍</h3>
|
||||
<p>在这里描述报告的背景和研究动机...</p>
|
||||
</div>
|
||||
<div class="content-section">
|
||||
<h3 class="section-heading">主要内容</h3>
|
||||
<ul class="content-list">
|
||||
<li>主要内容点一</li>
|
||||
<li>主要内容点二</li>
|
||||
<li>主要内容点三</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 技术分析章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={600} trigger="scroll">
|
||||
<section id="analysis">
|
||||
<ReportSection title="🔬 技术分析" subtitle="核心技术和方法的详细分析" level={2}>
|
||||
<!-- 🎨 子容器延迟动画示例 -->
|
||||
<div class="space-y-8">
|
||||
<AnimatedElement animation="fadeInUp" delay={700} trigger="scroll">
|
||||
<div class="analysis-card">
|
||||
<h3 class="card-title">技术要点一</h3>
|
||||
<p class="card-description">详细描述第一个技术要点的内容和实现方法...</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<div class="analysis-card">
|
||||
<h3 class="card-title">技术要点二</h3>
|
||||
<p class="card-description">详细描述第二个技术要点的内容和实现方法...</p>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 实施方案章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={800} trigger="scroll">
|
||||
<section id="implementation">
|
||||
<ReportSection title="⚙️ 实施方案" subtitle="具体的实施步骤和方法" level={2}>
|
||||
<!-- 🎨 实施步骤的子容器延迟动画示例 -->
|
||||
<div class="implementation-steps">
|
||||
<AnimatedElement animation="fadeInUp" delay={900} trigger="scroll">
|
||||
<div class="step-item">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
<h4 class="step-title">第一步:准备阶段</h4>
|
||||
<p class="step-description">描述第一步的具体内容和要求...</p>
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
<div class="step-item">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
<h4 class="step-title">第二步:实施阶段</h4>
|
||||
<p class="step-description">描述第二步的具体内容和要求...</p>
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedElement>
|
||||
<AnimatedElement animation="fadeInUp" delay={1100} trigger="scroll">
|
||||
<div class="step-item">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-content">
|
||||
<h4 class="step-title">第三步:验证阶段</h4>
|
||||
<p class="step-description">描述第三步的具体内容和要求...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
<!-- 总结章节 -->
|
||||
<AnimatedElement animation="fadeInUp" delay={1000} trigger="scroll">
|
||||
<section id="conclusion">
|
||||
<ReportSection title="📋 总结" subtitle="报告内容总结和后续计划" level={2}>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div class="summary-card">
|
||||
<h3 class="summary-title">🎯 关键成果</h3>
|
||||
<ul class="summary-list">
|
||||
<li>成果一:具体描述</li>
|
||||
<li>成果二:具体描述</li>
|
||||
<li>成果三:具体描述</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<h3 class="summary-title">📅 后续计划</h3>
|
||||
<ul class="summary-list">
|
||||
<li>计划一:具体内容</li>
|
||||
<li>计划二:具体内容</li>
|
||||
<li>计划三:具体内容</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ReportSection>
|
||||
</section>
|
||||
</AnimatedElement>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
/* 基础样式 */
|
||||
.report-main {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 2rem;
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
background: transparent;
|
||||
color: #333d4b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.report-main::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(174, 206, 221, 0.1) 0%,
|
||||
rgba(177, 217, 212, 0.05) 25%,
|
||||
rgba(178, 197, 213, 0.03) 50%,
|
||||
rgba(155, 181, 200, 0.05) 75%,
|
||||
rgba(122, 153, 176, 0.1) 100%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 布局 */
|
||||
.report-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx-auto { margin: 0 auto; }
|
||||
.px-4 { padding: 0 1rem; }
|
||||
|
||||
/* 标题区域 */
|
||||
.report-header {
|
||||
padding: 4rem 0 2rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: #3b82f6;
|
||||
margin: 0 0 1rem 0;
|
||||
background: linear-gradient(135deg, #60a5fa, #3b82f6, #2563eb);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: #2c4a6b;
|
||||
margin: 0 0 2rem 0;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.report-date, .report-type {
|
||||
background: rgba(91, 119, 142, 0.1);
|
||||
color: #011a2d;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.report-action {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
/* 内容样式 */
|
||||
.content-section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(91, 119, 142, 0.2);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.content-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content-list li {
|
||||
background: rgba(91, 119, 142, 0.05);
|
||||
border-left: 4px solid #5b778e;
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 0 0.5rem 0.5rem 0;
|
||||
}
|
||||
|
||||
/* 分析卡片 */
|
||||
.analysis-card {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(14, 116, 144, 0.12));
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.85rem;
|
||||
border: 1px solid rgba(59, 130, 246, 0.35);
|
||||
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12);
|
||||
backdrop-filter: blur(16px);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.analysis-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.16);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: #4b5563;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 实施步骤 */
|
||||
.implementation-steps {
|
||||
space-y: 2rem;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
color: white;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: #4b5563;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 总结卡片 */
|
||||
.summary-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(91, 119, 142, 0.2);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #3b82f6;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.summary-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.summary-list li {
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid rgba(91, 119, 142, 0.1);
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.summary-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.report-title { font-size: 2rem; }
|
||||
.report-subtitle { font-size: 1.1rem; }
|
||||
.report-meta {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.step-item { flex-direction: column; text-align: center; }
|
||||
.grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.report-main {
|
||||
padding-left: clamp(calc(300px + 2.5rem), calc((100vw - 1200px) / 2 + 1rem), calc(320px + 3rem));
|
||||
padding-right: clamp(1rem, calc((100vw - 1200px) / 2 + 1rem), 2rem);
|
||||
}
|
||||
.report-layout { max-width: 1200px; margin: 0 auto; }
|
||||
.report-content { max-width: 100%; width: 100%; }
|
||||
.container { padding: 0 1rem; }
|
||||
}
|
||||
|
||||
/* 工具类 */
|
||||
.grid { display: grid; }
|
||||
.grid-cols-1 { grid-template-columns: 1fr; }
|
||||
@media (min-width: 768px) {
|
||||
.md\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
.gap-8 { gap: 2rem; }
|
||||
.space-y-6 > * + * { margin-top: 1.5rem; }
|
||||
.space-y-8 > * + * { margin-top: 2rem; }
|
||||
</style>
|
||||
103
src/scripts/animation-timing.ts
Normal file
103
src/scripts/animation-timing.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 动画延迟时间计算工具
|
||||
* 提供基于父容器延迟时间的子容器动画时序计算
|
||||
*/
|
||||
|
||||
// 基础延迟时间配置
|
||||
export const BASE_DELAYS = {
|
||||
// 页面级别基础延迟
|
||||
HERO: 100,
|
||||
FIRST_SECTION: 200,
|
||||
SECTION_INCREMENT: 100,
|
||||
|
||||
// 子容器相对延迟
|
||||
SUB_ELEMENT_BASE: 50,
|
||||
SUB_ELEMENT_INCREMENT: 50,
|
||||
NESTED_ELEMENT_BASE: 100,
|
||||
|
||||
// 特殊元素延迟
|
||||
IMAGE_BASE: 50,
|
||||
LIST_ITEM_BASE: 50,
|
||||
CARD_BASE: 50,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 计算章节基础延迟时间
|
||||
* @param sectionIndex 章节索引(从0开始)
|
||||
* @returns 章节基础延迟时间
|
||||
*/
|
||||
export function getSectionBaseDelay(sectionIndex: number): number {
|
||||
return BASE_DELAYS.FIRST_SECTION + (sectionIndex * BASE_DELAYS.SECTION_INCREMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算子容器延迟时间
|
||||
* @param parentDelay 父容器延迟时间
|
||||
* @param childIndex 子元素索引(从0开始)
|
||||
* @param increment 递增间隔(可选,默认使用配置值)
|
||||
* @returns 子容器延迟时间
|
||||
*/
|
||||
export function getChildDelay(
|
||||
parentDelay: number,
|
||||
childIndex: number,
|
||||
increment: number = BASE_DELAYS.SUB_ELEMENT_INCREMENT
|
||||
): number {
|
||||
return parentDelay + BASE_DELAYS.SUB_ELEMENT_BASE + (childIndex * increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算嵌套子容器延迟时间
|
||||
* @param parentDelay 父容器延迟时间
|
||||
* @param nestLevel 嵌套层级(1为第一层子元素,2为第二层等)
|
||||
* @returns 嵌套容器延迟时间
|
||||
*/
|
||||
export function getNestedDelay(parentDelay: number, nestLevel: number): number {
|
||||
return parentDelay + (BASE_DELAYS.NESTED_ELEMENT_BASE * nestLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量计算子元素延迟时间数组
|
||||
* @param parentDelay 父容器延迟时间
|
||||
* @param count 子元素数量
|
||||
* @param increment 递增间隔
|
||||
* @returns 延迟时间数组
|
||||
*/
|
||||
export function getChildDelays(
|
||||
parentDelay: number,
|
||||
count: number,
|
||||
increment: number = BASE_DELAYS.SUB_ELEMENT_INCREMENT
|
||||
): number[] {
|
||||
return Array.from({ length: count }, (_, index) =>
|
||||
getChildDelay(parentDelay, index, increment)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片容器延迟计算
|
||||
* @param sectionDelay 章节延迟时间
|
||||
* @param imageIndex 图片索引
|
||||
* @returns 图片延迟时间
|
||||
*/
|
||||
export function getImageDelay(sectionDelay: number, imageIndex: number): number {
|
||||
return sectionDelay + BASE_DELAYS.IMAGE_BASE + (imageIndex * BASE_DELAYS.IMAGE_BASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片网格延迟计算
|
||||
* @param sectionDelay 章节延迟时间
|
||||
* @param cardIndex 卡片索引
|
||||
* @returns 卡片延迟时间
|
||||
*/
|
||||
export function getCardDelay(sectionDelay: number, cardIndex: number): number {
|
||||
return sectionDelay + BASE_DELAYS.CARD_BASE + (cardIndex * BASE_DELAYS.CARD_BASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表项延迟计算
|
||||
* @param parentDelay 父容器延迟时间
|
||||
* @param itemIndex 列表项索引
|
||||
* @returns 列表项延迟时间
|
||||
*/
|
||||
export function getListItemDelay(parentDelay: number, itemIndex: number): number {
|
||||
return parentDelay + BASE_DELAYS.LIST_ITEM_BASE + (itemIndex * BASE_DELAYS.LIST_ITEM_BASE);
|
||||
}
|
||||
72
src/scripts/graph-visualizer.ts
Normal file
72
src/scripts/graph-visualizer.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 图可视化脚本
|
||||
* 用于在容器中绘制随机图结构
|
||||
*/
|
||||
|
||||
interface Node {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function drawGraph(containerId: string): void {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = ''; // 清除之前的图
|
||||
|
||||
const numNodes = 5;
|
||||
const nodes: Node[] = [];
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const width = containerRect.width;
|
||||
const height = containerRect.height;
|
||||
|
||||
// 创建节点
|
||||
for (let i = 0; i < numNodes; i++) {
|
||||
const node = document.createElement('div');
|
||||
node.className = 'graph-node';
|
||||
const x = Math.random() * (width - 24) + 12;
|
||||
const y = Math.random() * (height - 24) + 12;
|
||||
node.style.left = `${x - 6}px`;
|
||||
node.style.top = `${y - 6}px`;
|
||||
container.appendChild(node);
|
||||
nodes.push({ x, y });
|
||||
}
|
||||
|
||||
// 创建边
|
||||
for (let i = 0; i < numNodes; i++) {
|
||||
for (let j = i + 1; j < numNodes; j++) {
|
||||
if (Math.random() > 0.6) {
|
||||
const node1 = nodes[i];
|
||||
const node2 = nodes[j];
|
||||
|
||||
const edge = document.createElement('div');
|
||||
edge.className = 'graph-edge';
|
||||
|
||||
const dx = node2.x - node1.x;
|
||||
const dy = node2.y - node1.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
||||
|
||||
edge.style.width = `${distance}px`;
|
||||
edge.style.left = `${node1.x}px`;
|
||||
edge.style.top = `${node1.y}px`;
|
||||
edge.style.transform = `rotate(${angle}deg)`;
|
||||
|
||||
container.appendChild(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
export function initGraphVisualizer(): void {
|
||||
const drawGraphOnLoad = () => drawGraph('graph-container');
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', drawGraphOnLoad);
|
||||
} else {
|
||||
drawGraphOnLoad();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', drawGraphOnLoad);
|
||||
}
|
||||
@@ -288,8 +288,34 @@ class HeaderVisibilityController {
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到顶部功能
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化滚动到顶部按钮
|
||||
function initScrollToTop() {
|
||||
const scrollButtons = document.querySelectorAll('.scroll-to-top-btn');
|
||||
scrollButtons.forEach(button => {
|
||||
button.addEventListener('click', scrollToTop);
|
||||
});
|
||||
}
|
||||
|
||||
// 自动初始化
|
||||
if (typeof window !== 'undefined') {
|
||||
const controller = new HeaderVisibilityController();
|
||||
(window as any).headerVisibilityController = controller;
|
||||
|
||||
// 页面加载完成后初始化滚动到顶部功能
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initScrollToTop);
|
||||
} else {
|
||||
initScrollToTop();
|
||||
}
|
||||
|
||||
// 支持 Astro 的页面导航
|
||||
document.addEventListener('astro:page-load', initScrollToTop);
|
||||
}
|
||||
340
src/scripts/report-sidebar.ts
Normal file
340
src/scripts/report-sidebar.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* 报告侧边栏增强脚本
|
||||
* 修复移动端按钮不工作的问题,增强用户体验
|
||||
*/
|
||||
|
||||
interface ReportSidebarConfig {
|
||||
sidebarId: string;
|
||||
targetSelector: string;
|
||||
minLevel: number;
|
||||
maxLevel: number;
|
||||
breakpoint: string;
|
||||
}
|
||||
|
||||
export const initReportSidebar = (config: ReportSidebarConfig) => {
|
||||
const sidebar = document.getElementById(config.sidebarId);
|
||||
if (!sidebar || sidebar.dataset.initialized === 'true') return;
|
||||
|
||||
sidebar.dataset.initialized = 'true';
|
||||
|
||||
const navList = sidebar.querySelector('.report-sidebar__list') as HTMLElement;
|
||||
const toggleBtn = sidebar.querySelector('.report-sidebar__toggle') as HTMLButtonElement;
|
||||
const closeBtn = sidebar.querySelector('.report-sidebar__close') as HTMLButtonElement;
|
||||
|
||||
if (!navList || !toggleBtn || !closeBtn) return;
|
||||
|
||||
const matchBreakpoint = window.matchMedia(config.breakpoint);
|
||||
const headingLevels = Array.from({ length: config.maxLevel - config.minLevel + 1 }, (_, idx) => `h${config.minLevel + idx}`);
|
||||
const selector = headingLevels.join(',');
|
||||
|
||||
const slugify = (text: string): string => {
|
||||
if (!text) return 'section';
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji_Component}\p{Emoji}\p{Symbol}\p{Open_Punctuation}\p{Close_Punctuation}]/gu, '')
|
||||
.replace(/[^a-z0-9\u4e00-\u9fa5\-\s]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '') || 'section';
|
||||
};
|
||||
|
||||
const buildNavigation = () => {
|
||||
const targetRoot = document.querySelector(config.targetSelector) ?? document.body;
|
||||
const headings = Array.from(targetRoot.querySelectorAll(selector))
|
||||
.filter((heading): heading is HTMLElement => heading instanceof HTMLElement);
|
||||
|
||||
if (!headings.length) {
|
||||
sidebar.classList.add('report-sidebar--empty');
|
||||
return;
|
||||
}
|
||||
|
||||
sidebar.classList.remove('report-sidebar--empty');
|
||||
|
||||
const existingIds = new Set(headings.filter((h) => h.id).map((h) => h.id));
|
||||
|
||||
headings.forEach((heading) => {
|
||||
if (!heading.id) {
|
||||
const base = slugify(heading.textContent ?? 'section');
|
||||
let candidate = base;
|
||||
let counter = 1;
|
||||
while (existingIds.has(candidate) || document.getElementById(candidate)) {
|
||||
candidate = `${base}-${counter++}`;
|
||||
}
|
||||
heading.id = candidate;
|
||||
existingIds.add(candidate);
|
||||
}
|
||||
});
|
||||
|
||||
let depth = 0;
|
||||
const baseLevel = Math.min(...headings.map(h => parseInt(h.tagName.charAt(1))));
|
||||
|
||||
navList.innerHTML = '';
|
||||
headings.forEach((heading) => {
|
||||
const level = parseInt(heading.tagName.charAt(1));
|
||||
depth = level - baseLevel;
|
||||
const text = heading.textContent?.trim() || '';
|
||||
|
||||
const listItem = document.createElement('li');
|
||||
listItem.className = 'nav-item';
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = `#${heading.id}`;
|
||||
link.className = 'nav-link';
|
||||
link.textContent = text;
|
||||
link.dataset.targetId = heading.id;
|
||||
link.dataset.level = String(level);
|
||||
link.dataset.depth = String(depth);
|
||||
|
||||
// 设置内联样式
|
||||
const indentPx = depth * 20;
|
||||
let fontSize: string, fontWeight: string, color: string;
|
||||
|
||||
switch(level) {
|
||||
case 2:
|
||||
fontSize = '18px';
|
||||
fontWeight = '700';
|
||||
color = '#0f172a';
|
||||
break;
|
||||
case 3:
|
||||
fontSize = '16px';
|
||||
fontWeight = '600';
|
||||
color = '#1e293b';
|
||||
break;
|
||||
default:
|
||||
fontSize = '13px';
|
||||
fontWeight = '400';
|
||||
color = '#475569';
|
||||
}
|
||||
|
||||
link.style.cssText = `
|
||||
margin-left: ${indentPx}px;
|
||||
font-size: ${fontSize};
|
||||
font-weight: ${fontWeight};
|
||||
color: ${color};
|
||||
padding: ${level === 2 ? '12px 16px' : level === 3 ? '10px 14px' : '8px 12px'};
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
`;
|
||||
|
||||
listItem.appendChild(link);
|
||||
navList.appendChild(listItem);
|
||||
});
|
||||
|
||||
setupScrollSpy(headings);
|
||||
};
|
||||
|
||||
const setCollapsed = (collapsed: boolean) => {
|
||||
sidebar.classList.toggle('collapsed', collapsed);
|
||||
toggleBtn.setAttribute('aria-expanded', String(!collapsed));
|
||||
|
||||
// 在移动端确保面板正确显示/隐藏
|
||||
if (!matchBreakpoint.matches) {
|
||||
const panel = sidebar.querySelector('.report-sidebar__panel') as HTMLElement;
|
||||
if (panel) {
|
||||
if (collapsed) {
|
||||
panel.style.transform = 'translateY(100%)';
|
||||
} else {
|
||||
panel.style.transform = 'translateY(0)';
|
||||
// 强制重绘确保动画生效
|
||||
requestAnimationFrame(() => {
|
||||
panel.style.transform = 'translateY(0)';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 修复事件处理
|
||||
const handleToggle = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isCollapsed = sidebar.classList.contains('collapsed');
|
||||
setCollapsed(!isCollapsed);
|
||||
};
|
||||
|
||||
const handleClose = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCollapsed(true);
|
||||
};
|
||||
|
||||
// 移除可能存在的旧事件监听器
|
||||
const newToggleBtn = toggleBtn.cloneNode(true) as HTMLButtonElement;
|
||||
const newCloseBtn = closeBtn.cloneNode(true) as HTMLButtonElement;
|
||||
|
||||
toggleBtn.parentNode?.replaceChild(newToggleBtn, toggleBtn);
|
||||
closeBtn.parentNode?.replaceChild(newCloseBtn, closeBtn);
|
||||
|
||||
// 重新获取替换后的按钮
|
||||
const currentToggleBtn = sidebar.querySelector('.report-sidebar__toggle') as HTMLButtonElement;
|
||||
const currentCloseBtn = sidebar.querySelector('.report-sidebar__close') as HTMLButtonElement;
|
||||
|
||||
// 添加新的事件监听器
|
||||
currentToggleBtn.addEventListener('click', handleToggle, { passive: false });
|
||||
currentCloseBtn.addEventListener('click', handleClose, { passive: false });
|
||||
|
||||
const handleLinkClick = (event: Event) => {
|
||||
const link = (event.target as Element).closest('.nav-link') as HTMLElement;
|
||||
if (!link) return;
|
||||
event.preventDefault();
|
||||
const targetId = link.dataset.targetId;
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
history.replaceState(null, '', `#${targetId}`);
|
||||
}
|
||||
if (!matchBreakpoint.matches) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
};
|
||||
|
||||
navList.addEventListener('click', handleLinkClick);
|
||||
|
||||
const applyBreakpointState = () => {
|
||||
setCollapsed(!matchBreakpoint.matches);
|
||||
};
|
||||
|
||||
matchBreakpoint.addEventListener('change', applyBreakpointState);
|
||||
|
||||
// 键盘事件支持
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && !sidebar.classList.contains('collapsed')) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// 背景点击关闭(移动端)
|
||||
const handleBackdropClick = (e: Event) => {
|
||||
if (!matchBreakpoint.matches &&
|
||||
e.target === sidebar &&
|
||||
!sidebar.classList.contains('collapsed')) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
};
|
||||
|
||||
sidebar.addEventListener('click', handleBackdropClick);
|
||||
|
||||
let activeLink: HTMLElement | null = null;
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
||||
const setActiveLink = (id: string) => {
|
||||
if (activeLink) {
|
||||
activeLink.classList.remove('nav-link--active');
|
||||
}
|
||||
const next = navList.querySelector(`.nav-link[data-target-id="${id}"]`) as HTMLElement;
|
||||
if (next) {
|
||||
next.classList.add('nav-link--active');
|
||||
activeLink = next;
|
||||
} else {
|
||||
activeLink = null;
|
||||
}
|
||||
};
|
||||
|
||||
const setupScrollSpy = (headings: HTMLElement[]) => {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
const visible = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
|
||||
|
||||
if (visible.length > 0) {
|
||||
setActiveLink(visible[0].target.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const sorted = entries
|
||||
.slice()
|
||||
.sort((a, b) => (a.target as HTMLElement).offsetTop - (b.target as HTMLElement).offsetTop);
|
||||
const current = sorted.find((entry) =>
|
||||
window.scrollY + 120 <= (entry.target as HTMLElement).offsetTop + (entry.target as HTMLElement).offsetHeight
|
||||
);
|
||||
|
||||
if (current) {
|
||||
setActiveLink(current.target.id);
|
||||
}
|
||||
}, {
|
||||
rootMargin: '-35% 0px -55% 0px',
|
||||
threshold: [0, 0.25, 0.5, 0.75, 1]
|
||||
});
|
||||
|
||||
headings.forEach((heading) => observer?.observe(heading));
|
||||
};
|
||||
|
||||
const initialize = () => {
|
||||
buildNavigation();
|
||||
applyBreakpointState();
|
||||
};
|
||||
|
||||
// 初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initialize, { once: true });
|
||||
} else {
|
||||
initialize();
|
||||
}
|
||||
|
||||
// Astro 页面切换后重新初始化
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
buildNavigation();
|
||||
applyBreakpointState();
|
||||
});
|
||||
|
||||
// 返回清理函数
|
||||
return () => {
|
||||
currentToggleBtn.removeEventListener('click', handleToggle);
|
||||
currentCloseBtn.removeEventListener('click', handleClose);
|
||||
navList.removeEventListener('click', handleLinkClick);
|
||||
matchBreakpoint.removeEventListener('change', applyBreakpointState);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
sidebar.removeEventListener('click', handleBackdropClick);
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 自动初始化所有报告侧边栏
|
||||
export const autoInitReportSidebars = () => {
|
||||
const sidebars = document.querySelectorAll('[data-report-sidebar]');
|
||||
const cleanupFunctions: (() => void)[] = [];
|
||||
|
||||
sidebars.forEach(sidebar => {
|
||||
const element = sidebar as HTMLElement;
|
||||
const sidebarId = element.id;
|
||||
const targetSelector = element.dataset.targetSelector || '[data-report-content]';
|
||||
const minLevel = parseInt(element.dataset.minLevel || '2');
|
||||
const maxLevel = parseInt(element.dataset.maxLevel || '3');
|
||||
const breakpoint = element.dataset.breakpoint || '(min-width: 1280px)';
|
||||
|
||||
const cleanup = initReportSidebar({
|
||||
sidebarId,
|
||||
targetSelector,
|
||||
minLevel,
|
||||
maxLevel,
|
||||
breakpoint
|
||||
});
|
||||
|
||||
if (cleanup) {
|
||||
cleanupFunctions.push(cleanup);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanupFunctions.forEach(cleanup => cleanup());
|
||||
};
|
||||
};
|
||||
|
||||
// 全局初始化
|
||||
if (typeof window !== 'undefined') {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', autoInitReportSidebars, { once: true });
|
||||
} else {
|
||||
autoInitReportSidebars();
|
||||
}
|
||||
}
|
||||
@@ -61,8 +61,8 @@ class ScrollRevealController {
|
||||
(entries) => this.handleIntersections(entries),
|
||||
{
|
||||
root: null,
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -12% 0px'
|
||||
threshold: 0.05,
|
||||
rootMargin: '0px 0px -1% 0px'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
333
src/scripts/toc-sidebar.ts
Normal file
333
src/scripts/toc-sidebar.ts
Normal file
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* TOC侧边栏脚本 - 修复窄屏幕状态下按钮不管用的问题
|
||||
* 支持响应式布局、滚动高亮、折叠展开等功能
|
||||
*/
|
||||
|
||||
export interface TOCSidebarConfig {
|
||||
sidebarId: string;
|
||||
targetSelector: string;
|
||||
}
|
||||
|
||||
// 生成ID的工具函数
|
||||
const generateId = (text: string): string => {
|
||||
return text.toLowerCase()
|
||||
.replace(/[\p{Extended_Pictographic}\u2600-\u27BF]/gu, '') // 移除emoji
|
||||
.replace(/[^\w\u4e00-\u9fa5\s-]/g, '') // 保留文字、数字、中文、空格、连字符
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
};
|
||||
|
||||
// 构建目录树数据结构
|
||||
interface TOCItem {
|
||||
level: number;
|
||||
text: string;
|
||||
id: string;
|
||||
children: TOCItem[];
|
||||
}
|
||||
|
||||
export const initTOCSidebar = (config: TOCSidebarConfig) => {
|
||||
const { sidebarId, targetSelector } = config;
|
||||
|
||||
const sidebar = document.getElementById(sidebarId);
|
||||
if (!sidebar) return;
|
||||
|
||||
const tocList = sidebar.querySelector('[data-toc-list]') as HTMLElement;
|
||||
const toggleBtn = sidebar.querySelector('[data-toc-toggle]') as HTMLButtonElement;
|
||||
const closeBtn = sidebar.querySelector('[data-toc-close]') as HTMLButtonElement;
|
||||
const backdrop = sidebar.querySelector('[data-toc-backdrop]') as HTMLElement;
|
||||
const panel = sidebar.querySelector('[data-toc-panel]') as HTMLElement;
|
||||
|
||||
if (!tocList || !toggleBtn || !closeBtn || !backdrop || !panel) return;
|
||||
|
||||
// 响应式断点
|
||||
const mediaQuery = window.matchMedia('(min-width: 1024px)');
|
||||
|
||||
// 构建目录树
|
||||
const buildTOC = () => {
|
||||
const targetRoot = document.querySelector(targetSelector) || document.body;
|
||||
const headings = Array.from(targetRoot.querySelectorAll('h2, h3, h4'))
|
||||
.filter(h => h.textContent?.trim()) as HTMLElement[];
|
||||
|
||||
if (!headings.length) return;
|
||||
|
||||
// 为标题添加ID
|
||||
const existingIds = new Set<string>();
|
||||
headings.forEach(heading => {
|
||||
if (!heading.id) {
|
||||
let baseId = generateId(heading.textContent?.trim() || '') || 'section';
|
||||
let finalId = baseId;
|
||||
let counter = 1;
|
||||
|
||||
while (existingIds.has(finalId) || document.getElementById(finalId)) {
|
||||
finalId = `${baseId}-${counter++}`;
|
||||
}
|
||||
|
||||
heading.id = finalId;
|
||||
existingIds.add(finalId);
|
||||
} else {
|
||||
existingIds.add(heading.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 构建嵌套结构
|
||||
const tocData: TOCItem[] = [];
|
||||
const stack: TOCItem[] = [];
|
||||
|
||||
headings.forEach(heading => {
|
||||
const level = parseInt(heading.tagName.charAt(1));
|
||||
const text = heading.textContent?.trim() || '';
|
||||
const id = heading.id;
|
||||
|
||||
const item: TOCItem = {
|
||||
level,
|
||||
text,
|
||||
id,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 找到合适的父级
|
||||
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
if (stack.length === 0) {
|
||||
tocData.push(item);
|
||||
} else {
|
||||
stack[stack.length - 1].children.push(item);
|
||||
}
|
||||
|
||||
stack.push(item);
|
||||
});
|
||||
|
||||
// 渲染TOC
|
||||
renderTOC(tocData);
|
||||
setupScrollSpy(headings);
|
||||
};
|
||||
|
||||
// 渲染TOC HTML
|
||||
const renderTOC = (items: TOCItem[], isNested = false): string => {
|
||||
if (!items.length) return '';
|
||||
|
||||
const html = items.map(item => {
|
||||
const hasChildren = item.children.length > 0;
|
||||
const isCollapsed = item.level >= 4; // h4及以下默认折叠
|
||||
|
||||
return `
|
||||
<li class="toc-item toc-item--level-${item.level}" data-level="${item.level}">
|
||||
<div class="toc-item-content">
|
||||
${hasChildren ?
|
||||
`<button class="toc-expand" type="button" aria-expanded="${!isCollapsed}" data-expand>
|
||||
<svg class="toc-expand-icon" viewBox="0 0 24 24" width="14" height="14">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M9 18l6-6-6-6"/>
|
||||
</svg>
|
||||
</button>` :
|
||||
'<span class="toc-spacer"></span>'
|
||||
}
|
||||
<a href="#${item.id}" class="toc-link" data-target="${item.id}" data-level="${item.level}">
|
||||
${item.text}
|
||||
</a>
|
||||
</div>
|
||||
${hasChildren ?
|
||||
`<ul class="toc-sublist ${isCollapsed ? 'toc-sublist--collapsed' : ''}">
|
||||
${renderTOC(item.children, true)}
|
||||
</ul>` : ''
|
||||
}
|
||||
</li>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (!isNested) {
|
||||
tocList.innerHTML = html;
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
// 侧边栏状态管理
|
||||
const setSidebarOpen = (isOpen: boolean) => {
|
||||
sidebar.classList.toggle('toc-sidebar--open', isOpen);
|
||||
sidebar.classList.toggle('toc-sidebar--closed', !isOpen);
|
||||
sidebar.setAttribute('aria-hidden', String(!isOpen));
|
||||
toggleBtn.setAttribute('aria-expanded', String(isOpen));
|
||||
|
||||
// 确保事件传播和状态更新
|
||||
if (isOpen) {
|
||||
// 延迟确保DOM更新后再处理
|
||||
requestAnimationFrame(() => {
|
||||
sidebar.classList.add('toc-sidebar--open');
|
||||
panel.style.transform = 'translateY(0)';
|
||||
});
|
||||
} else {
|
||||
panel.style.transform = 'translateY(100%)';
|
||||
}
|
||||
};
|
||||
|
||||
// 响应式处理
|
||||
const handleMediaChange = () => {
|
||||
if (mediaQuery.matches) {
|
||||
// 桌面端:默认展开
|
||||
setSidebarOpen(true);
|
||||
sidebar.classList.add('toc-sidebar--desktop');
|
||||
} else {
|
||||
// 移动端:默认收起
|
||||
setSidebarOpen(false);
|
||||
sidebar.classList.remove('toc-sidebar--desktop');
|
||||
}
|
||||
};
|
||||
|
||||
// 事件监听 - 修复点击无效的问题
|
||||
const handleToggleClick = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isOpen = sidebar.classList.contains('toc-sidebar--open');
|
||||
setSidebarOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleCloseClick = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setSidebarOpen(false);
|
||||
};
|
||||
|
||||
const handleBackdropClick = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setSidebarOpen(false);
|
||||
};
|
||||
|
||||
// 移除旧的事件监听器(如果存在)
|
||||
toggleBtn.removeEventListener('click', handleToggleClick);
|
||||
closeBtn.removeEventListener('click', handleCloseClick);
|
||||
backdrop.removeEventListener('click', handleBackdropClick);
|
||||
|
||||
// 添加新的事件监听器
|
||||
toggleBtn.addEventListener('click', handleToggleClick, { passive: false });
|
||||
closeBtn.addEventListener('click', handleCloseClick, { passive: false });
|
||||
backdrop.addEventListener('click', handleBackdropClick, { passive: false });
|
||||
|
||||
// 折叠/展开处理
|
||||
tocList.addEventListener('click', (e) => {
|
||||
const expandBtn = (e.target as Element).closest('[data-expand]') as HTMLElement;
|
||||
if (expandBtn) {
|
||||
e.preventDefault();
|
||||
const sublist = expandBtn.closest('.toc-item')?.querySelector('.toc-sublist') as HTMLElement;
|
||||
if (sublist) {
|
||||
const isCollapsed = sublist.classList.contains('toc-sublist--collapsed');
|
||||
sublist.classList.toggle('toc-sublist--collapsed', !isCollapsed);
|
||||
expandBtn.setAttribute('aria-expanded', String(isCollapsed));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const link = (e.target as Element).closest('.toc-link') as HTMLElement;
|
||||
if (link) {
|
||||
e.preventDefault();
|
||||
const targetId = link.getAttribute('data-target');
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
history.replaceState(null, '', `#${targetId}`);
|
||||
}
|
||||
|
||||
// 移动端点击后关闭侧边栏
|
||||
if (!mediaQuery.matches) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 滚动高亮
|
||||
let activeLink: HTMLElement | null = null;
|
||||
|
||||
const setActiveLink = (id: string) => {
|
||||
if (activeLink) {
|
||||
activeLink.classList.remove('toc-link--active');
|
||||
}
|
||||
|
||||
// 修复:使用正确的属性选择器
|
||||
const newActiveLink = tocList.querySelector(`[data-target="${id}"]`) as HTMLElement;
|
||||
if (newActiveLink) {
|
||||
newActiveLink.classList.add('toc-link--active');
|
||||
activeLink = newActiveLink;
|
||||
|
||||
// 自动展开父级
|
||||
let parent = newActiveLink.closest('.toc-sublist') as HTMLElement;
|
||||
while (parent) {
|
||||
parent.classList.remove('toc-sublist--collapsed');
|
||||
const expandBtn = parent.previousElementSibling?.querySelector('[data-expand]') as HTMLElement;
|
||||
if (expandBtn) {
|
||||
expandBtn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
const parentItem = parent.closest('.toc-item')?.parentElement?.closest('.toc-sublist') as HTMLElement;
|
||||
parent = parentItem;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setupScrollSpy = (headings: HTMLElement[]) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const visibleEntries = entries
|
||||
.filter(entry => entry.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
|
||||
|
||||
if (visibleEntries.length > 0) {
|
||||
setActiveLink(visibleEntries[0].target.id);
|
||||
}
|
||||
}, {
|
||||
rootMargin: '-20% 0px -70% 0px',
|
||||
threshold: [0, 0.25, 0.5, 0.75, 1]
|
||||
});
|
||||
|
||||
headings.forEach(heading => observer.observe(heading));
|
||||
};
|
||||
|
||||
// 键盘事件
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && sidebar.classList.contains('toc-sidebar--open')) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// 初始化
|
||||
buildTOC();
|
||||
mediaQuery.addEventListener('change', handleMediaChange);
|
||||
handleMediaChange();
|
||||
|
||||
// 返回清理函数
|
||||
return () => {
|
||||
toggleBtn.removeEventListener('click', handleToggleClick);
|
||||
closeBtn.removeEventListener('click', handleCloseClick);
|
||||
backdrop.removeEventListener('click', handleBackdropClick);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
mediaQuery.removeEventListener('change', handleMediaChange);
|
||||
};
|
||||
};
|
||||
|
||||
// 全局初始化函数
|
||||
export const initTOCFromElement = (sidebarElement: HTMLElement) => {
|
||||
const sidebarId = sidebarElement.id;
|
||||
const targetSelector = sidebarElement.dataset.targetSelector || '[data-report-content]';
|
||||
|
||||
return initTOCSidebar({ sidebarId, targetSelector });
|
||||
};
|
||||
|
||||
// 自动初始化所有TOC侧边栏
|
||||
export const autoInitAllTOCSidebars = () => {
|
||||
const sidebars = document.querySelectorAll('[data-toc-sidebar]');
|
||||
const cleanupFunctions: (() => void)[] = [];
|
||||
|
||||
sidebars.forEach(sidebar => {
|
||||
const cleanup = initTOCFromElement(sidebar as HTMLElement);
|
||||
if (cleanup) {
|
||||
cleanupFunctions.push(cleanup);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanupFunctions.forEach(cleanup => cleanup());
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user