add some function and fix some problems.
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
296
src/components/navigation/SearchBar.astro
Normal file
296
src/components/navigation/SearchBar.astro
Normal 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>
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
516
src/pages/navigation-template.astro
Normal file
516
src/pages/navigation-template.astro
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/* 统一宽度配置 */
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user