add some function and fix some problems.

This commit is contained in:
Jiao77
2025-10-01 16:05:43 +08:00
parent 32a850f346
commit f152c8dc5d
14 changed files with 2639 additions and 65 deletions

View File

@@ -201,15 +201,12 @@ const {
entries.forEach(entry => {
const element = entry.target as HTMLElement;
if (entry.isIntersecting) {
// 确保元素在进入视口时正确触发动画
// 立即触发动画,无需等待滚动停止
if (!element.classList.contains('animate-visible')) {
// 重置动画状态以确保延迟正确工作
element.style.animationName = 'none';
element.offsetHeight; // 强制重绘
element.style.animationName = '';
// 添加可见类触发动画
element.classList.add('animate-visible');
// 使用 requestAnimationFrame 确保在下一帧立即触发
requestAnimationFrame(() => {
element.classList.add('animate-visible');
});
}
} else {
// 可选:元素离开视口时重置动画(支持重复动画)
@@ -217,8 +214,8 @@ const {
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
threshold: 0.05, // 降低阈值元素稍微进入视口就触发5%可见即触发)
rootMargin: '0px 0px 200px 0px' // 提前触发元素距离视口底部200px时就开始
});
animateElements.forEach(element => {
@@ -226,6 +223,16 @@ const {
});
}
// 页面加载时触发动画
function initLoadAnimations() {
const loadElements = document.querySelectorAll('[data-trigger="load"]');
loadElements.forEach(element => {
// 页面加载时立即添加动画类
element.classList.add('animate-visible');
});
}
// 点击触发动画
function initClickAnimations() {
const clickElements = document.querySelectorAll('[data-trigger="click"]');
@@ -239,12 +246,14 @@ const {
// 初始化
document.addEventListener('DOMContentLoaded', () => {
initLoadAnimations();
initScrollAnimations();
initClickAnimations();
});
// 页面导航时重新初始化适用于SPA或动态内容
document.addEventListener('astro:page-load', () => {
initLoadAnimations();
initScrollAnimations();
initClickAnimations();
});

View File

@@ -80,6 +80,7 @@ const revealAttributes = reveal
<article
class={`nav-card ${colorClasses[color]} ${sizeClasses[size]}`}
data-card
{...revealAttributes}
>
<div class="nav-card-content">
@@ -89,10 +90,10 @@ const revealAttributes = reveal
</div>
)}
<h3 class="nav-card-title">{title}</h3>
<h3 class="card-title nav-card-title">{title}</h3>
{description && (
<p class="nav-card-description">{description}</p>
<p class="card-description nav-card-description">{description}</p>
)}
<GlowButton
@@ -256,6 +257,22 @@ const revealAttributes = reveal
border-color: rgba(177, 217, 212, 0.3);
}
/* 搜索高亮效果 */
.nav-card.search-highlight {
animation: searchPulse 0.6s ease;
border-color: #2c4a6b;
box-shadow: 0 8px 30px rgba(44, 74, 107, 0.2);
}
@keyframes searchPulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
}
@media (max-width: 768px) {
.nav-card {
padding: 1.5rem;

View File

@@ -0,0 +1,296 @@
---
export interface Props {
placeholder?: string;
}
const { placeholder = "搜索..." } = Astro.props;
---
<div class="search-container">
<div class="search-wrapper">
<i class="fas fa-search search-icon"></i>
<input
type="text"
class="search-input"
placeholder={placeholder}
id="searchInput"
/>
<button class="search-clear" id="searchClear" style="display: none;">
<i class="fas fa-times"></i>
</button>
</div>
<div class="search-results" id="searchResults" style="display: none;">
<div class="results-header">
<span class="results-count" id="resultsCount">找到 0 个结果</span>
</div>
</div>
</div>
<style>
.search-container {
width: 100%;
max-width: 700px;
margin: 2.5rem auto 3rem;
position: relative;
}
.search-wrapper {
position: relative;
display: flex;
align-items: center;
/* 磨砂玻璃效果 */
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(44, 74, 107, 0.15);
border-radius: 16px;
padding: 1rem 1.75rem;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 4px 24px rgba(44, 74, 107, 0.08),
0 2px 8px rgba(44, 74, 107, 0.04),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
.search-wrapper:hover {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(44, 74, 107, 0.25);
box-shadow:
0 6px 32px rgba(44, 74, 107, 0.12),
0 3px 12px rgba(44, 74, 107, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
.search-wrapper:focus-within {
background: rgba(255, 255, 255, 0.9);
border-color: #2c4a6b;
box-shadow:
0 8px 40px rgba(44, 74, 107, 0.16),
0 4px 16px rgba(44, 74, 107, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.7),
0 0 0 3px rgba(44, 74, 107, 0.1);
transform: translateY(-2px);
}
.search-icon {
color: #2c4a6b;
font-size: 1.25rem;
margin-right: 1rem;
opacity: 0.6;
transition: all 0.3s ease;
}
.search-wrapper:focus-within .search-icon {
opacity: 1;
transform: scale(1.1);
}
.search-input {
flex: 1;
border: none;
outline: none;
background: transparent;
font-size: 1.05rem;
color: #011a2d;
font-weight: 400;
letter-spacing: 0.01em;
}
.search-input::placeholder {
color: #5b778e;
opacity: 0.5;
}
.search-clear {
background: rgba(44, 74, 107, 0.08);
border: none;
color: #5b778e;
font-size: 0.9rem;
cursor: pointer;
padding: 0.4rem 0.4rem;
border-radius: 8px;
transition: all 0.3s ease;
opacity: 0.6;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.search-clear:hover {
opacity: 1;
background: rgba(44, 74, 107, 0.15);
color: #2c4a6b;
transform: rotate(90deg);
}
.search-results {
position: absolute;
top: calc(100% + 0.75rem);
left: 0;
right: 0;
/* 磨砂玻璃效果 */
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(44, 74, 107, 0.15);
border-radius: 16px;
padding: 1.25rem;
box-shadow:
0 12px 48px rgba(44, 74, 107, 0.15),
0 6px 24px rgba(44, 74, 107, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
z-index: 100;
animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.results-header {
padding-bottom: 1rem;
border-bottom: 1px solid rgba(44, 74, 107, 0.1);
margin-bottom: 1rem;
}
.results-count {
font-size: 0.95rem;
color: #2c4a6b;
font-weight: 600;
letter-spacing: 0.01em;
}
@media (max-width: 768px) {
.search-container {
max-width: 100%;
margin: 2rem auto 2.5rem;
}
.search-wrapper {
padding: 0.85rem 1.25rem;
}
.search-input {
font-size: 1rem;
}
.search-icon {
font-size: 1.1rem;
margin-right: 0.85rem;
}
}
</style>
<script>
// 搜索功能脚本
const searchInput = document.getElementById('searchInput') as HTMLInputElement;
const searchClear = document.getElementById('searchClear') as HTMLButtonElement;
const searchResults = document.getElementById('searchResults') as HTMLDivElement;
const resultsCount = document.getElementById('resultsCount') as HTMLSpanElement;
let allCards: HTMLElement[] = [];
// 初始化 - 获取所有导航卡片
function initSearch() {
const navigationGrid = document.querySelector('.navigation-grid');
if (navigationGrid) {
allCards = Array.from(navigationGrid.querySelectorAll('[data-card]')) as HTMLElement[];
}
}
// 执行搜索
function performSearch(query: string) {
const lowerQuery = query.toLowerCase().trim();
if (!lowerQuery) {
// 没有搜索词,显示所有卡片
allCards.forEach(card => {
card.style.display = '';
card.classList.remove('search-highlight');
});
searchResults.style.display = 'none';
return;
}
let matchCount = 0;
allCards.forEach(card => {
const title = card.querySelector('.card-title')?.textContent?.toLowerCase() || '';
const description = card.querySelector('.card-description')?.textContent?.toLowerCase() || '';
const matches = title.includes(lowerQuery) || description.includes(lowerQuery);
if (matches) {
card.style.display = '';
card.classList.add('search-highlight');
matchCount++;
} else {
card.style.display = 'none';
card.classList.remove('search-highlight');
}
});
// 显示结果
resultsCount.textContent = `找到 ${matchCount} 个结果`;
searchResults.style.display = 'block';
// 如果没有结果3秒后自动隐藏提示
if (matchCount === 0) {
setTimeout(() => {
if (searchInput.value === query) {
searchResults.style.display = 'none';
}
}, 3000);
}
}
// 监听输入
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const query = (e.target as HTMLInputElement).value;
// 显示/隐藏清除按钮
if (searchClear) {
searchClear.style.display = query ? 'block' : 'none';
}
performSearch(query);
});
}
// 清除搜索
if (searchClear) {
searchClear.addEventListener('click', () => {
if (searchInput) {
searchInput.value = '';
searchInput.focus();
searchClear.style.display = 'none';
}
performSearch('');
});
}
// 点击外部关闭结果
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
if (!target.closest('.search-container')) {
searchResults.style.display = 'none';
}
});
// 初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSearch);
} else {
initSearch();
}
</script>

View File

@@ -4,6 +4,7 @@ import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import NavigationGrid from '../components/navigation/NavigationGrid.astro';
import NavigationCard from '../components/navigation/NavigationCard.astro';
import SearchBar from '../components/navigation/SearchBar.astro';
import Container from '../components/Container.astro';
import AnimatedElement from '../components/AnimatedElement.astro';
---
@@ -27,10 +28,12 @@ import AnimatedElement from '../components/AnimatedElement.astro';
</AnimatedElement>
</div>
<AnimatedElement animation="fadeInUp" delay={400} trigger="load">
<SearchBar placeholder="搜索服务..." />
</AnimatedElement>
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
<NavigationGrid
title="技术服务"
description="整合多种自建服务,提供便捷的技术解决方案"
columns={3}
gap="large"
>

View File

@@ -0,0 +1,516 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import AnimatedElement from '../components/AnimatedElement.astro';
import SearchBar from '../components/navigation/SearchBar.astro';
import NavigationCard from '../components/navigation/NavigationCard.astro';
import NavigationGrid from '../components/navigation/NavigationGrid.astro';
// 页面元数据配置
const pageConfig = {
title: '导航页面模板',
description: '这是一个导航页面模板,展示如何使用 SearchBar 和 NavigationCard 组件',
keywords: '导航, 模板, 示例'
};
// 导航卡片数据配置示例
// 每个卡片包含:标题、描述、链接、图标、标签、延迟时间
const navigationItems = [
{
title: '项目一',
description: '这是项目一的简短描述,介绍主要功能和特点。',
href: '/path/to/project-1',
icon: '🚀',
tags: ['标签1', '标签2'],
delay: 0
},
{
title: '项目二',
description: '这是项目二的简短描述,介绍主要功能和特点。',
href: '/path/to/project-2',
icon: '📊',
tags: ['数据分析', '可视化'],
delay: 100
},
{
title: '项目三',
description: '这是项目三的简短描述,介绍主要功能和特点。',
href: '/path/to/project-3',
icon: '🎨',
tags: ['设计', 'UI/UX'],
delay: 200
},
{
title: '项目四',
description: '这是项目四的简短描述,介绍主要功能和特点。',
href: '/path/to/project-4',
icon: '🔧',
tags: ['工具', '开发'],
delay: 300
},
{
title: '项目五',
description: '这是项目五的简短描述,介绍主要功能和特点。',
href: '/path/to/project-5',
icon: '📱',
tags: ['移动端', '应用'],
delay: 400
},
{
title: '项目六',
description: '这是项目六的简短描述,介绍主要功能和特点。',
href: '/path/to/project-6',
icon: '🌐',
tags: ['网络', '服务'],
delay: 500
}
];
// 分类导航配置(可选)
// 如果需要将导航项分组显示,可以使用以下结构
const categorizedNavigation = [
{
category: '技术项目',
icon: '💻',
items: [
{
title: '前端项目',
description: '现代前端开发项目和技术栈。',
href: '/projects/frontend',
icon: '🎨',
tags: ['React', 'Vue', 'Astro']
},
{
title: '后端项目',
description: '服务端开发和 API 设计。',
href: '/projects/backend',
icon: '⚙️',
tags: ['Node.js', 'Python', 'Database']
}
]
},
{
category: '学习资源',
icon: '📚',
items: [
{
title: '教程文档',
description: '技术教程和学习资料汇总。',
href: '/resources/tutorials',
icon: '📖',
tags: ['教程', '文档', '指南']
},
{
title: '工具集合',
description: '开发工具和实用资源推荐。',
href: '/resources/tools',
icon: '🔧',
tags: ['工具', '效率', '资源']
}
]
}
];
---
<BaseLayout
title={pageConfig.title}
description={pageConfig.description}
>
<Header />
<main class="navigation-page">
<div class="container mx-auto px-4 py-8">
<!-- 页面标题区域 -->
<AnimatedElement animation="fadeInUp" delay={0} trigger="load">
<div class="page-header text-center mb-12 mt-8">
<h1 class="page-title">导航页面模板</h1>
<p class="page-subtitle">使用 SearchBar 和 NavigationCard 创建优雅的导航页面</p>
</div>
</AnimatedElement>
<!-- 搜索栏 -->
<!--
SearchBar 组件说明:
- placeholder: 搜索框提示文字
- 自动支持实时搜索过滤
- 带有磨砂玻璃效果
- 使用 AnimatedElement 包裹来添加动画
-->
<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
<SearchBar placeholder="🔍 搜索项目、标签或关键词..." />
</AnimatedElement>
<!-- 方式一:简单的网格布局(推荐用于单一类别) -->
<section class="navigation-section mb-16">
<AnimatedElement animation="fadeInUp" delay={400} trigger="load">
<h2 class="section-title">所有项目</h2>
</AnimatedElement>
<NavigationGrid>
{navigationItems.map(item => (
<AnimatedElement
animation="fadeInUp"
delay={item.delay + 500}
trigger="load"
>
<NavigationCard
title={item.title}
description={item.description}
href={item.href}
icon={item.icon}
revealDirection="up"
revealDelay="0ms"
/>
</AnimatedElement>
))}
</NavigationGrid>
</section>
<!-- 方式二:分类导航布局(推荐用于多个类别) -->
<section class="categorized-navigation">
{categorizedNavigation.map((category, categoryIndex) => (
<div class="category-section mb-16">
<AnimatedElement
animation="fadeInUp"
delay={categoryIndex * 200 + 800}
trigger="scroll"
>
<h2 class="category-title">
<span class="category-icon">{category.icon}</span>
{category.category}
</h2>
</AnimatedElement>
<NavigationGrid>
{category.items.map((item, itemIndex) => (
<AnimatedElement
animation="fadeInUp"
delay={categoryIndex * 200 + itemIndex * 100 + 900}
trigger="scroll"
>
<NavigationCard
title={item.title}
description={item.description}
href={item.href}
icon={item.icon}
revealDirection="up"
revealDelay="0ms"
/>
</AnimatedElement>
))}
</NavigationGrid>
</div>
))}
</section>
<!-- 使用说明区域 -->
<AnimatedElement animation="fadeInUp" delay={0} trigger="scroll">
<section class="usage-guide">
<h2 class="guide-title">📋 使用说明</h2>
<div class="guide-content">
<div class="guide-item">
<h3 class="guide-subtitle">1. 配置导航数据</h3>
<p>在页面顶部的 frontmatter 中定义 <code>navigationItems</code> 数组:</p>
<pre class="code-example">{`const navigationItems = [
{
title: '项目名称',
description: '项目描述',
href: '/项目链接',
icon: '🚀',
tags: ['标签1', '标签2'],
delay: 0 // 动画延迟(毫秒)
}
];`}</pre>
</div>
<div class="guide-item">
<h3 class="guide-subtitle">2. 使用 SearchBar 组件</h3>
<p>添加搜索功能,支持实时过滤(使用 AnimatedElement 包裹添加动画):</p>
<pre class="code-example">{`<AnimatedElement animation="fadeInUp" delay={200} trigger="load">
<SearchBar placeholder="🔍 搜索..." />
</AnimatedElement>`}</pre>
</div>
<div class="guide-item">
<h3 class="guide-subtitle">3. 使用 NavigationCard</h3>
<p>显示导航卡片(使用 AnimatedElement 包裹添加动画):</p>
<pre class="code-example">{`<AnimatedElement animation="fadeInUp" delay={100} trigger="scroll">
<NavigationCard
title="标题"
description="描述"
href="/链接"
icon="🎨"
buttonLabel="查看详情"
revealDirection="up"
/>
</AnimatedElement>`}</pre>
</div>
<div class="guide-item">
<h3 class="guide-subtitle">4. 动画延迟配置</h3>
<p>建议的延迟时间配置:</p>
<ul>
<li><strong>页面标题:</strong> 0ms</li>
<li><strong>SearchBar</strong> 200-400ms</li>
<li><strong>区域标题:</strong> 400-600ms</li>
<li><strong>导航卡片:</strong> 500ms 起,每个卡片递增 100ms</li>
<li><strong>trigger="load"</strong> 用于首屏内容</li>
<li><strong>trigger="scroll"</strong> 用于滚动后的内容</li>
</ul>
</div>
<div class="guide-item">
<h3 class="guide-subtitle">5. 颜色和样式自定义</h3>
<p>使用 CSS 变量自定义莫兰迪蓝色系:</p>
<pre class="code-example">{`:root {
--nav-color-primary: #5b778e;
--nav-color-primary-dark: #2c4a6b;
--nav-color-accent: #b2c5d5;
}`}</pre>
</div>
</div>
</section>
</AnimatedElement>
</div>
</main>
<Footer />
</BaseLayout>
<style>
/* === 页面核心样式 === */
.navigation-page {
--nav-color-primary: #5b778e;
--nav-color-primary-dark: #2c4a6b;
--nav-color-primary-deeper: #1f3a52;
--nav-color-accent: #b2c5d5;
--nav-color-text: #2f3844;
--nav-color-subtext: #566171;
--nav-color-border: rgba(91, 119, 142, 0.2);
min-height: 100vh;
background: linear-gradient(135deg,
rgba(178, 197, 213, 0.1) 0%,
rgba(91, 119, 142, 0.05) 50%,
rgba(44, 74, 107, 0.08) 100%
);
padding-bottom: 4rem;
}
/* === 页面标题样式 === */
.page-header {
padding: 2rem 0;
}
.page-title {
font-size: 3rem;
font-weight: 800;
background: linear-gradient(135deg, var(--nav-color-primary-dark), var(--nav-color-primary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 1rem;
letter-spacing: -0.02em;
}
.page-subtitle {
font-size: 1.25rem;
color: var(--nav-color-subtext);
font-weight: 400;
max-width: 600px;
margin: 0 auto;
}
/* === 导航区域样式 === */
.navigation-section,
.categorized-navigation {
margin-top: 3rem;
}
.section-title,
.category-title {
font-size: 2rem;
font-weight: 700;
color: var(--nav-color-primary-dark);
margin-bottom: 2rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.category-icon {
font-size: 2.5rem;
display: inline-block;
}
.category-section {
scroll-margin-top: 6rem;
}
/* === 使用说明样式 === */
.usage-guide {
margin-top: 4rem;
padding: 3rem;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 1.5rem;
border: 1px solid var(--nav-color-border);
box-shadow: 0 8px 32px rgba(44, 74, 107, 0.12);
}
.guide-title {
font-size: 2rem;
font-weight: 700;
color: var(--nav-color-primary-dark);
margin-bottom: 2rem;
text-align: center;
}
.guide-content {
display: grid;
gap: 2rem;
}
.guide-item {
padding: 1.5rem;
background: rgba(178, 197, 213, 0.1);
border-radius: 1rem;
border: 1px solid var(--nav-color-border);
}
.guide-subtitle {
font-size: 1.25rem;
font-weight: 600;
color: var(--nav-color-primary);
margin-bottom: 0.75rem;
}
.guide-item p {
color: var(--nav-color-text);
line-height: 1.7;
margin-bottom: 1rem;
}
.guide-item ul {
list-style: none;
padding-left: 0;
}
.guide-item li {
padding: 0.5rem 0;
color: var(--nav-color-text);
line-height: 1.6;
}
.guide-item li strong {
color: var(--nav-color-primary-dark);
font-weight: 600;
}
.code-example {
background: var(--nav-color-primary-deeper);
color: #e5e7eb;
padding: 1rem 1.5rem;
border-radius: 0.75rem;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875rem;
line-height: 1.6;
border: 1px solid rgba(91, 119, 142, 0.3);
white-space: pre;
}
code {
background: rgba(91, 119, 142, 0.15);
color: var(--nav-color-primary-dark);
padding: 0.2rem 0.5rem;
border-radius: 0.375rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9em;
}
/* === 响应式设计 === */
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.page-subtitle {
font-size: 1rem;
}
.section-title,
.category-title {
font-size: 1.5rem;
}
.usage-guide {
padding: 2rem 1.5rem;
}
.guide-title {
font-size: 1.5rem;
}
.code-example {
font-size: 0.75rem;
padding: 0.75rem 1rem;
}
}
/* === 容器工具类 === */
.container {
max-width: 1200px;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.mb-12 {
margin-bottom: 3rem;
}
.mb-16 {
margin-bottom: 4rem;
}
.mt-8 {
margin-top: 2rem;
}
.text-center {
text-align: center;
}
</style>
<style is:global>
/* === 全局搜索高亮样式 === */
@keyframes search-highlight {
0%, 100% {
background-color: transparent;
}
50% {
background-color: rgba(91, 119, 142, 0.15);
}
}
.search-highlight {
animation: search-highlight 0.6s ease-in-out;
}
</style>

View File

@@ -249,10 +249,10 @@ const proofImages = {
{[
{
icon: "🌍",
iconColor: "text-blue-500",
iconColor: "text-[#2c4a6b]",
title: "正交视图生成 (Orthographic View Generation)",
subtitle: "组件一",
subtitleColor: "text-blue-600",
subtitleColor: "text-[#5b778e]",
content: `
<p class="text-gray-700 mb-3">RoRD认为尽管正交视图能增加视觉重叠以辅助匹配但仅有正交视图不足以应对极端视角变化仍需旋转鲁棒特征配合。此步骤旨在通过几何变换将输入的透视图像转换为标准的顶视鸟瞰图像为后续特征提取提供规范化输入。</p>
<h4 class="font-semibold text-lg mt-4 mb-2 text-blue-600">实现方式:</h4>
@@ -264,18 +264,18 @@ const proofImages = {
},
{
icon: "🔄",
iconColor: "text-green-500",
iconColor: "text-[#5b778e]",
title: "旋转鲁棒描述子学习 (RoRD Descriptors)",
subtitle: "组件二",
subtitleColor: "text-green-600",
subtitleColor: "text-[#2c4a6b]",
isSpecial: true // 标记为特殊组件,需要特殊处理
},
{
icon: "🔗",
iconColor: "text-purple-500",
iconColor: "text-[#b2c5d5]",
title: "对应关系集成与筛选",
subtitle: "组件三",
subtitleColor: "text-purple-600",
subtitleColor: "text-[#5b778e]",
content: `
<p class="text-gray-700 mb-3">为了进一步提升匹配的整体性能RoRD引入了一种对应关系集成技术并使用RANSAC算法进行几何验证以确保最终匹配结果的精确性。</p>
<h4 class="font-semibold text-lg mt-4 mb-2 text-blue-600">集成与匹配流程:</h4>
@@ -701,13 +701,13 @@ const proofImages = {
`;
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active', 'bg-blue-600', 'text-white', 'shadow-lg');
btn.classList.remove('active', 'bg-[#2c4a6b]', 'text-white', 'shadow-lg');
btn.classList.add('bg-gray-200', 'text-gray-700', 'hover:bg-gray-300');
const tech = btn.getAttribute('data-tech');
if (tech === techKey) {
btn.classList.remove('bg-gray-200', 'text-gray-700', 'hover:bg-gray-300');
btn.classList.add('active', 'bg-blue-600', 'text-white', 'shadow-lg');
btn.classList.add('active', 'bg-[#2c4a6b]', 'text-white', 'shadow-lg');
}
});
}
@@ -747,6 +747,24 @@ const proofImages = {
</script>
<style>
/* 莫兰蒂蓝色系全局覆盖 */
:global(.text-blue-600),
:global(.text-blue-800) {
color: #2c4a6b !important;
}
:global(.text-green-500) {
color: #5b778e !important;
}
:global(.bg-blue-600) {
background-color: #2c4a6b !important;
}
:global(.hover\:bg-blue-700:hover) {
background-color: #1f3a52 !important;
}
/* 报告布局样式 */
.report-layout {
display: flex;
@@ -782,13 +800,13 @@ const proofImages = {
}
.comparison-table :global(th) {
background-color: rgba(59, 130, 246, 0.1);
background-color: rgba(44, 74, 107, 0.12);
font-weight: 600;
color: #1e40af;
color: #2c4a6b;
}
.comparison-table :global(tbody tr:hover) {
background-color: rgba(59, 130, 246, 0.05);
background-color: rgba(91, 119, 142, 0.08);
}
/* 标签按钮样式 */
@@ -801,7 +819,7 @@ const proofImages = {
}
.tab-btn.active {
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
box-shadow: 0 4px 8px rgba(44, 74, 107, 0.3);
}
/* 统一宽度配置 */

View File

@@ -449,37 +449,6 @@ export function openModal(venueId: string, cardElement: HTMLElement): void {
</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 });
@@ -487,7 +456,11 @@ export function openModal(venueId: string, cardElement: HTMLElement): void {
modalOverlay.classList.remove('hidden');
const tl = gsap.timeline({
onComplete: () => { isAnimating = false; }
onComplete: () => {
isAnimating = false;
// 在动画完成后渲染图表,避免尺寸变化导致的抽搐
renderVenueChart(venue);
}
});
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
@@ -528,6 +501,47 @@ export function openModal(venueId: string, cardElement: HTMLElement): void {
}, ">-0.2");
}
/**
* 渲染图表(独立函数,在动画完成后调用)
*/
function renderVenueChart(venue: VenueData): void {
try {
const Chart = (window as any).Chart;
if (Chart && venue.acceptanceValue !== null) {
const canvas = document.getElementById('venueChart') as HTMLCanvasElement;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (ctx) {
// 确保画布尺寸已经稳定
requestAnimationFrame(() => {
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时使用
*/
@@ -607,6 +621,9 @@ function openSimpleModal(venueId: string): void {
</div>
</div>
`;
// 简化模式下也渲染图表
renderVenueChart(venue);
}
/**
@@ -650,7 +667,7 @@ export function closeModal(): void {
tl.to(modalContentWrapper, {
opacity: 0,
duration: 0.3,
duration: 0.15, // 从0.3秒加快到0.15秒
ease: 'power2.out'
})
.to(modal, {
@@ -660,13 +677,13 @@ export function closeModal(): void {
height: activeCardState.height,
x: '0%',
y: '0%',
duration: 0.5,
ease: 'expo.in'
duration: 0.3, // 从0.5秒加快到0.3秒
ease: 'power3.in' // 使用更快的缓动函数
}, ">-0.05")
.to(modalOverlay, {
opacity: 0,
duration: 0.4,
ease: 'power2.inOut'
duration: 0.2, // 从0.4秒加快到0.2秒
ease: 'power2.in'
}, "<")
.set(modalContentWrapper, { visibility: 'hidden' });
}

View File

@@ -4,6 +4,7 @@ import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import NavigationGrid from '../../components/navigation/NavigationGrid.astro';
import NavigationCard from '../../components/navigation/NavigationCard.astro';
import SearchBar from '../../components/navigation/SearchBar.astro';
import Container from '../../components/Container.astro';
import AnimatedElement from '../../components/AnimatedElement.astro';
---
@@ -27,10 +28,12 @@ import AnimatedElement from '../../components/AnimatedElement.astro';
</AnimatedElement>
</div>
<AnimatedElement animation="fadeInUp" delay={400} trigger="load">
<SearchBar placeholder="搜索报告..." />
</AnimatedElement>
<AnimatedElement animation="fadeInUp" delay={400} trigger="scroll">
<NavigationGrid
title="技术报告导航"
description="深入的技术研究与分析文档涵盖AI、EDA、集成电路等前沿领域"
columns={3}
gap="large"
>