Skip to content

Commit cfa308c

Browse files
committed
feat: 添加SEO优化和移动端导航支持
- 在BaseLayout中添加完整的SEO配置,包括meta描述、Open Graph标签和JSON-LD结构化数据 - 生成sitemap.xml并更新robots.txt引用 - 为NavBar组件添加响应式移动端汉堡菜单 - 优化AdaptiveImage组件,添加width/height属性和异步解码 - 修复Footer中重复的img标签 - 更新package.json依赖顺序并添加@types/node - 增强数据验证函数的类型安全性 - 添加CollectionEntry类型定义
1 parent 0c0cd6b commit cfa308c

14 files changed

Lines changed: 859 additions & 53 deletions

File tree

.sisyphus/drafts/cveo-webpage-audit-report.md

Lines changed: 534 additions & 0 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@
1212
"check": "npm run build && npm run check:html && npm run check:links"
1313
},
1414
"dependencies": {
15-
"astro": "^4.0.0",
16-
"@astrojs/tailwind": "^5.0.0"
15+
"@astrojs/tailwind": "^5.0.0",
16+
"astro": "^4.0.0"
1717
},
1818
"devDependencies": {
19-
"tailwindcss": "^3.3.0",
19+
"@tailwindcss/typography": "^0.5.10",
2020
"autoprefixer": "^10.4.0",
21-
"postcss": "^8.4.0",
2221
"html-validate": "^9.0.0",
2322
"linkinator": "^4.0.0",
24-
"typescript": "^5.3.3",
25-
"@tailwindcss/typography": "^0.5.10"
23+
"postcss": "^8.4.0",
24+
"tailwindcss": "^3.3.0",
25+
"typescript": "^5.3.3"
2626
}
2727
}

public/robots.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
User-agent: *
22
Allow: /
3+
4+
# Sitemap
5+
Sitemap: https://www.whu-cveo.com/sitemap.xml

public/sitemap.xml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3+
<url>
4+
<loc>https://www.whu-cveo.com/</loc>
5+
<lastmod>2025-03-16</lastmod>
6+
<changefreq>weekly</changefreq>
7+
<priority>1.0</priority>
8+
</url>
9+
<url>
10+
<loc>https://www.whu-cveo.com/team/</loc>
11+
<lastmod>2025-03-16</lastmod>
12+
<changefreq>weekly</changefreq>
13+
<priority>0.8</priority>
14+
</url>
15+
<url>
16+
<loc>https://www.whu-cveo.com/research/</loc>
17+
<lastmod>2025-03-16</lastmod>
18+
<changefreq>weekly</changefreq>
19+
<priority>0.8</priority>
20+
</url>
21+
<url>
22+
<loc>https://www.whu-cveo.com/research-projects/</loc>
23+
<lastmod>2025-03-16</lastmod>
24+
<changefreq>weekly</changefreq>
25+
<priority>0.7</priority>
26+
</url>
27+
<url>
28+
<loc>https://www.whu-cveo.com/collaboration/</loc>
29+
<lastmod>2025-03-16</lastmod>
30+
<changefreq>weekly</changefreq>
31+
<priority>0.7</priority>
32+
</url>
33+
<url>
34+
<loc>https://www.whu-cveo.com/news/</loc>
35+
<lastmod>2025-03-16</lastmod>
36+
<changefreq>weekly</changefreq>
37+
<priority>0.6</priority>
38+
</url>
39+
</urlset>

src/components/AdaptiveImage.astro

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
---
2+
import { Image } from 'astro:assets';
3+
24
interface Props {
35
src: string;
46
alt: string;
5-
aspectRatio?: string; // 例如 "3/4" 表示 3:4 竖排头像,"1/1" 表示正方形
7+
aspectRatio?: string;
68
className?: string;
79
lazy?: boolean;
810
fallbackSrc?: string;
11+
width?: number;
12+
height?: number;
913
}
1014
1115
const {
@@ -14,7 +18,9 @@ const {
1418
aspectRatio = "1/1",
1519
className = "",
1620
lazy = true,
17-
fallbackSrc = "/assets/avatars/placeholder.svg"
21+
fallbackSrc = "/assets/avatars/placeholder.svg",
22+
width = 400,
23+
height = 400
1824
} = Astro.props;
1925
2026
const aspectRatioClass = {
@@ -33,7 +39,10 @@ const handleError = `
3339
<img
3440
src={src}
3541
alt={alt}
42+
width={width}
43+
height={height}
3644
class={`object-cover rounded ${aspectRatioClass} ${className}`}
3745
loading={lazy ? "lazy" : "eager"}
46+
decoding={lazy ? "async" : "auto"}
3847
onerror={handleError}
39-
/>
48+
/>

src/components/Footer.astro

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ const friendLinks = [
127127
扫码关注CVEO课题组
128128
</p>
129129
<img
130+
src="/assets/social/wechat-cveo.png"
131+
alt="CVEO课题组微信公众号二维码"
132+
width="128"
133+
height="128"
134+
class="w-32 h-32 object-contain rounded-lg"
135+
loading="lazy"
136+
decoding="async"
137+
/>
130138
src="/assets/social/wechat-cveo.png"
131139
alt="CVEO课题组微信公众号二维码"
132140
class="w-32 h-32 object-contain rounded-lg"
@@ -172,6 +180,14 @@ const friendLinks = [
172180
扫码关注珞珈经纬
173181
</p>
174182
<img
183+
src="/assets/social/wechat-luojiagingwei.png"
184+
alt="珞珈经纬微信公众号二维码"
185+
width="128"
186+
height="128"
187+
class="w-32 h-32 object-contain rounded-lg"
188+
loading="lazy"
189+
decoding="async"
190+
/>
175191
src="/assets/social/wechat-luojiagingwei.png"
176192
alt="珞珈经纬微信公众号二维码"
177193
class="w-32 h-32 object-contain rounded-lg"

src/components/NavBar.astro

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,117 @@
33
<header class="bg-white/75 backdrop-blur border-b border-slate-200 sticky top-0 z-50">
44
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
55
<div class="flex items-center justify-between h-16">
6+
<!-- Logo -->
67
<div class="flex items-center gap-4">
78
<a href="/" class="flex items-center gap-3">
89
<img
910
src="/assets/logos/logo.png"
1011
alt="CVEO课题组logo"
12+
width="128"
13+
height="40"
1114
class="w-32 h-auto rounded-lg shadow-sm hover:shadow-md transition-shadow"
15+
loading="eager"
16+
decoding="async"
1217
/>
1318
<span class="font-semibold text-brand-900 text-lg hidden md:inline">CVEO课题组</span>
1419
</a>
1520
</div>
16-
<nav class="flex items-center gap-6 text-sm" id="main-nav">
17-
<a href="/" class="text-slate-700 hover:text-brand-900">首页</a>
18-
<a href="#news" class="text-slate-700 hover:text-brand-900">新闻动态</a>
19-
<a href="#research" class="text-slate-700 hover:text-brand-900">科研成果</a>
20-
<a href="/team" class="text-slate-700 hover:text-brand-900">团队成员</a>
21-
<a href="/research-projects" class="text-slate-700 hover:text-brand-900">科研项目</a>
22-
<a href="/collaboration" class="text-slate-700 hover:text-brand-900">合作平台</a>
23-
<!-- <a href="/en" class="text-slate-700 hover:text-brand-700">EN</a> -->
21+
22+
<!-- Desktop Navigation -->
23+
<nav class="hidden md:flex items-center gap-6 text-sm" id="main-nav">
24+
<a href="/" class="text-slate-700 hover:text-brand-900 transition-colors">首页</a>
25+
<a href="#news" class="text-slate-700 hover:text-brand-900 transition-colors">新闻动态</a>
26+
<a href="#research" class="text-slate-700 hover:text-brand-900 transition-colors">科研成果</a>
27+
<a href="/team" class="text-slate-700 hover:text-brand-900 transition-colors">团队成员</a>
28+
<a href="/research-projects" class="text-slate-700 hover:text-brand-900 transition-colors">科研项目</a>
29+
<a href="/collaboration" class="text-slate-700 hover:text-brand-900 transition-colors">合作平台</a>
2430
</nav>
31+
32+
<!-- Mobile Menu Button -->
33+
<button
34+
type="button"
35+
id="mobile-menu-btn"
36+
class="md:hidden p-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-100 transition-colors"
37+
aria-label="打开菜单"
38+
aria-expanded="false"
39+
>
40+
<svg id="menu-icon-open" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
42+
</svg>
43+
<svg id="menu-icon-close" class="w-6 h-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
45+
</svg>
46+
</button>
2547
</div>
48+
49+
<!-- Mobile Navigation -->
50+
<nav
51+
id="mobile-nav"
52+
class="hidden md:hidden py-4 border-t border-slate-200"
53+
aria-label="移动端导航"
54+
>
55+
<div class="flex flex-col space-y-2">
56+
<a href="/" class="px-3 py-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-50 transition-colors">首页</a>
57+
<a href="#news" class="px-3 py-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-50 transition-colors mobile-anchor">新闻动态</a>
58+
<a href="#research" class="px-3 py-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-50 transition-colors mobile-anchor">科研成果</a>
59+
<a href="/team" class="px-3 py-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-50 transition-colors">团队成员</a>
60+
<a href="/research-projects" class="px-3 py-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-50 transition-colors">科研项目</a>
61+
<a href="/collaboration" class="px-3 py-2 rounded-lg text-slate-700 hover:text-brand-900 hover:bg-slate-50 transition-colors">合作平台</a>
62+
</div>
63+
</nav>
2664
</div>
2765
</header>
2866

2967
<script>
3068
function setupNav() {
3169
const nav = document.getElementById('main-nav');
32-
if (!nav) return;
70+
const mobileNav = document.getElementById('mobile-nav');
71+
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
72+
const menuIconOpen = document.getElementById('menu-icon-open');
73+
const menuIconClose = document.getElementById('menu-icon-close');
74+
75+
if (!nav || !mobileNav || !mobileMenuBtn) return;
76+
77+
// Mobile menu toggle
78+
let isMenuOpen = false;
79+
80+
function toggleMenu() {
81+
isMenuOpen = !isMenuOpen;
82+
mobileNav.classList.toggle('hidden', !isMenuOpen);
83+
menuIconOpen?.classList.toggle('hidden', isMenuOpen);
84+
menuIconClose?.classList.toggle('hidden', !isMenuOpen);
85+
mobileMenuBtn.setAttribute('aria-expanded', isMenuOpen.toString());
86+
}
87+
88+
function closeMenu() {
89+
if (isMenuOpen) {
90+
isMenuOpen = false;
91+
mobileNav.classList.add('hidden');
92+
menuIconOpen?.classList.remove('hidden');
93+
menuIconClose?.classList.add('hidden');
94+
mobileMenuBtn.setAttribute('aria-expanded', 'false');
95+
}
96+
}
97+
98+
mobileMenuBtn.addEventListener('click', toggleMenu);
99+
100+
// Close menu when clicking on mobile nav links
101+
const mobileLinks = mobileNav.querySelectorAll('a');
102+
mobileLinks.forEach(link => {
103+
link.addEventListener('click', () => {
104+
closeMenu();
105+
});
106+
});
107+
108+
// Close menu when clicking outside
109+
document.addEventListener('click', (e) => {
110+
const target = e.target;
111+
if (!mobileNav.contains(target) && !mobileMenuBtn.contains(target)) {
112+
closeMenu();
113+
}
114+
});
33115

116+
// Desktop nav anchor links
34117
const links = nav.querySelectorAll('a');
35118
const isHomePage = window.location.pathname === '/' || window.location.pathname === '/index.html';
36119

@@ -44,26 +127,42 @@
44127
(targetId === 'teams' ? document.getElementById('team') : null);
45128

46129
if (isHomePage) {
47-
// On homepage, let the default anchor jump work (BaseLayout has scroll-smooth)
48130
if (targetId === 'teams') {
49131
e.preventDefault();
50132
document.getElementById('team')?.scrollIntoView({ behavior: 'smooth' });
51133
}
52134
} else {
53-
// On non-homepage
54135
if (targetElement) {
55-
// Target exists on current page
56136
e.preventDefault();
57137
targetElement.scrollIntoView({ behavior: 'smooth' });
58138
history.pushState(null, '', href);
59139
} else {
60-
// Target does not exist, go to homepage + anchor
61140
e.preventDefault();
62141
window.location.href = '/' + href;
63142
}
64143
}
65144
});
66145
});
146+
147+
// Mobile anchor links
148+
const mobileAnchors = document.querySelectorAll('.mobile-anchor');
149+
mobileAnchors.forEach(link => {
150+
const href = link.getAttribute('href');
151+
if (!href || !href.startsWith('#')) return;
152+
153+
link.addEventListener('click', (e) => {
154+
const targetId = href.substring(1);
155+
const targetElement = document.getElementById(targetId);
156+
157+
if (!isHomePage && targetElement) {
158+
e.preventDefault();
159+
window.location.href = '/' + href;
160+
} else if (targetElement) {
161+
e.preventDefault();
162+
targetElement.scrollIntoView({ behavior: 'smooth' });
163+
}
164+
});
165+
});
67166
}
68167

69168
// Run on load

0 commit comments

Comments
 (0)