2025-03-03 21:16:16 +08:00
|
|
|
|
---
|
|
|
|
|
import "@/styles/global.css";
|
2025-04-19 18:54:19 +08:00
|
|
|
|
import Header from "@/components/Header.astro";
|
|
|
|
|
import Footer from "@/components/Footer.astro";
|
2025-03-10 17:22:18 +08:00
|
|
|
|
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
|
2025-03-08 18:16:42 +08:00
|
|
|
|
|
|
|
|
|
// 定义Props接口
|
|
|
|
|
interface Props {
|
|
|
|
|
title?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
date?: Date;
|
|
|
|
|
author?: string;
|
|
|
|
|
tags?: string[];
|
|
|
|
|
image?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取完整的 URL
|
|
|
|
|
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|
|
|
|
|
|
|
|
|
// 从props中获取页面特定信息
|
2025-03-10 17:22:18 +08:00
|
|
|
|
const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, image } = Astro.props;
|
2025-03-03 21:16:16 +08:00
|
|
|
|
---
|
|
|
|
|
<!doctype html>
|
2025-03-08 18:16:42 +08:00
|
|
|
|
<html lang="zh-CN" class="m-0 w-full h-full">
|
2025-03-03 21:16:16 +08:00
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
|
<meta name="viewport" content="width=device-width" />
|
|
|
|
|
<meta name="referrer" content="no-referrer" />
|
|
|
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
|
|
|
<meta name="generator" content={Astro.generator} />
|
2025-03-08 18:16:42 +08:00
|
|
|
|
|
|
|
|
|
<!-- 基本元数据 -->
|
|
|
|
|
<title>{title}</title>
|
|
|
|
|
<meta name="description" content={description || `${SITE_NAME} - 个人博客`} />
|
|
|
|
|
<link rel="canonical" href={canonicalURL} />
|
|
|
|
|
|
|
|
|
|
<!-- Open Graph / Facebook -->
|
|
|
|
|
<meta property="og:type" content="article" />
|
|
|
|
|
<meta property="og:url" content={canonicalURL} />
|
|
|
|
|
<meta property="og:title" content={title} />
|
|
|
|
|
<meta property="og:description" content={description || `${SITE_NAME} - 个人博客`} />
|
|
|
|
|
{image && <meta property="og:image" content={new URL(image, Astro.site)} />}
|
|
|
|
|
|
|
|
|
|
<!-- Twitter -->
|
|
|
|
|
<meta property="twitter:card" content="summary_large_image" />
|
|
|
|
|
<meta property="twitter:url" content={canonicalURL} />
|
|
|
|
|
<meta property="twitter:title" content={title} />
|
|
|
|
|
<meta property="twitter:description" content={description || `${SITE_NAME} - 个人博客`} />
|
|
|
|
|
{image && <meta property="twitter:image" content={new URL(image, Astro.site)} />}
|
|
|
|
|
|
|
|
|
|
<!-- 文章特定元数据 -->
|
|
|
|
|
{date && <meta property="article:published_time" content={date.toISOString()} />}
|
|
|
|
|
{author && <meta name="author" content={author} />}
|
|
|
|
|
{tags && tags.map(tag => (
|
|
|
|
|
<meta property="article:tag" content={tag} />
|
|
|
|
|
))}
|
2025-03-03 21:16:16 +08:00
|
|
|
|
<script is:inline>
|
2025-04-20 15:34:45 +08:00
|
|
|
|
// 立即执行主题初始化,采用"无闪烁"加载方式
|
2025-04-19 16:19:39 +08:00
|
|
|
|
(function() {
|
2025-04-20 15:34:45 +08:00
|
|
|
|
try {
|
|
|
|
|
// 获取系统首选主题
|
|
|
|
|
const getSystemTheme = () => {
|
|
|
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const storedTheme = typeof localStorage !== 'undefined' ? localStorage.getItem('theme') : null;
|
|
|
|
|
const systemTheme = getSystemTheme();
|
|
|
|
|
let theme = 'light'; // 默认浅色主题
|
2025-04-19 16:19:39 +08:00
|
|
|
|
|
2025-04-20 15:34:45 +08:00
|
|
|
|
// 按照逻辑优先级应用主题
|
|
|
|
|
if (storedTheme) {
|
|
|
|
|
// 如果有存储的主题设置,则应用它
|
|
|
|
|
theme = storedTheme;
|
|
|
|
|
} else if (systemTheme) {
|
|
|
|
|
// 如果没有存储的设置,检查系统主题
|
|
|
|
|
theme = systemTheme;
|
|
|
|
|
}
|
2025-04-19 18:54:19 +08:00
|
|
|
|
|
2025-04-20 15:34:45 +08:00
|
|
|
|
// 立即设置文档主题,在DOM渲染前应用,避免闪烁
|
2025-04-19 16:19:39 +08:00
|
|
|
|
document.documentElement.dataset.theme = theme;
|
2025-04-19 18:54:19 +08:00
|
|
|
|
|
2025-04-20 15:34:45 +08:00
|
|
|
|
// 监听系统主题变化(只有当主题设为跟随系统时才响应)
|
|
|
|
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
|
|
|
|
|
|
|
|
const handleMediaChange = (e) => {
|
|
|
|
|
// 只有当主题设置为跟随系统时才更新主题
|
|
|
|
|
if (!localStorage.getItem('theme')) {
|
|
|
|
|
const newTheme = e.matches ? 'dark' : 'light';
|
|
|
|
|
document.documentElement.dataset.theme = newTheme;
|
|
|
|
|
}
|
2025-04-19 18:54:19 +08:00
|
|
|
|
};
|
2025-04-20 15:34:45 +08:00
|
|
|
|
|
|
|
|
|
// 添加系统主题变化监听
|
|
|
|
|
mediaQuery.addEventListener('change', handleMediaChange);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// 出错时应用默认浅色主题,确保页面正常显示
|
|
|
|
|
document.documentElement.dataset.theme = 'light';
|
2025-03-03 21:16:16 +08:00
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
</head>
|
2025-04-19 16:19:39 +08:00
|
|
|
|
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg flex flex-col min-h-screen">
|
2025-04-19 18:54:19 +08:00
|
|
|
|
<Header />
|
2025-03-08 18:16:42 +08:00
|
|
|
|
<main class="pt-16 flex-grow">
|
2025-03-03 21:16:16 +08:00
|
|
|
|
<slot />
|
|
|
|
|
</main>
|
2025-03-08 18:16:42 +08:00
|
|
|
|
<Footer icp={ICP} psbIcp={PSB_ICP} psbIcpUrl={PSB_ICP_URL} />
|
2025-04-20 17:22:38 +08:00
|
|
|
|
|
|
|
|
|
<!-- 预获取脚本 -->
|
|
|
|
|
<script>
|
|
|
|
|
// 在DOM加载完成后执行
|
|
|
|
|
document.addEventListener('astro:page-load', () => {
|
|
|
|
|
// 获取所有视口预获取链接
|
|
|
|
|
const viewportLinks = document.querySelectorAll('[data-astro-prefetch="viewport"]');
|
|
|
|
|
|
|
|
|
|
if (viewportLinks.length > 0) {
|
|
|
|
|
// 创建一个交叉观察器
|
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
|
|
|
entries.forEach(entry => {
|
|
|
|
|
if (entry.isIntersecting) {
|
|
|
|
|
const link = entry.target;
|
|
|
|
|
// 进入视口时,添加data-astro-prefetch="true"属性触发预获取
|
|
|
|
|
if (link.getAttribute('data-astro-prefetch') === 'viewport') {
|
|
|
|
|
link.setAttribute('data-astro-prefetch', 'true');
|
|
|
|
|
}
|
|
|
|
|
// 一旦预获取,就不再观察这个链接
|
|
|
|
|
observer.unobserve(link);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 观察所有视口预获取链接
|
|
|
|
|
viewportLinks.forEach(link => {
|
|
|
|
|
observer.observe(link);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
2025-03-03 21:16:16 +08:00
|
|
|
|
</body>
|
|
|
|
|
</html>
|