将页面搜索和切换主题,代码块配色改为用别人的组件,添加seo和压缩代码组件,优化代码块样式,移除冗余样式和组件
This commit is contained in:
parent
6e96b59245
commit
25f4a89594
@ -11,9 +11,15 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import swup from "@swup/astro"
|
||||
import { SITE_URL } from "./src/consts";
|
||||
import pagefind from "astro-pagefind";
|
||||
import compressor from "astro-compressor";
|
||||
|
||||
import vercel from "@astrojs/vercel";
|
||||
|
||||
import expressiveCode from "astro-expressive-code";
|
||||
import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
|
||||
import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
|
||||
|
||||
function getArticleDate(articleId) {
|
||||
try {
|
||||
// 处理多级目录的文章路径
|
||||
@ -47,50 +53,55 @@ export default defineConfig({
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 手动分块配置
|
||||
manualChunks: {
|
||||
// 将地图组件单独打包
|
||||
"world-heatmap": ["./src/components/WorldHeatmap.tsx"],
|
||||
// 将 React 相关库单独打包
|
||||
"react-vendor": ["react", "react-dom"],
|
||||
// 其他大型依赖也可以单独打包
|
||||
"chart-vendor": ["chart.js"],
|
||||
// 将 ECharts 单独打包
|
||||
"echarts-vendor": ["echarts"],
|
||||
// 将其他组件打包到一起
|
||||
components: ["./src/components"],
|
||||
},
|
||||
},
|
||||
},
|
||||
// 提高警告阈值,避免不必要的警告
|
||||
chunkSizeWarningLimit: 1000,
|
||||
},
|
||||
},
|
||||
|
||||
integrations: [
|
||||
// MDX 集成配置
|
||||
mdx({
|
||||
// 不使用共享的 markdown 配置
|
||||
extendMarkdownConfig: false,
|
||||
// 为 MDX 单独配置所需功能
|
||||
remarkPlugins: [
|
||||
// 添加表情符号支持
|
||||
[remarkEmoji, { emoticon: false, padded: true }]
|
||||
expressiveCode({
|
||||
themes: ['github-light', 'dracula'],
|
||||
themeCssSelector: (theme) =>
|
||||
theme.name === 'dracula' ? '[data-theme=dark]' : '[data-theme=light]',
|
||||
useDarkModeMediaQuery: false,
|
||||
|
||||
plugins: [
|
||||
pluginLineNumbers(),
|
||||
pluginCollapsibleSections(),
|
||||
],
|
||||
rehypePlugins: [
|
||||
[rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }]
|
||||
],
|
||||
// 设置代码块处理行为
|
||||
remarkRehype: {
|
||||
allowDangerousHtml: false // 不解析 HTML
|
||||
defaultProps: {
|
||||
showLineNumbers: true,
|
||||
collapseStyle: 'collapsible-auto',
|
||||
},
|
||||
frames: {
|
||||
extractFileNameFromCode: true,
|
||||
},
|
||||
styleOverrides: {
|
||||
// 核心样式设置
|
||||
borderRadius: '0.5rem',
|
||||
borderWidth: '0.5px',
|
||||
codeFontSize: '0.9rem',
|
||||
codeFontFamily: "'JetBrains Mono', Menlo, Monaco, Consolas, 'Courier New', monospace",
|
||||
codeLineHeight: '1.5',
|
||||
codePaddingInline: '1.5rem',
|
||||
codePaddingBlock: '1.2rem',
|
||||
// 框架样式设置
|
||||
frames: {
|
||||
shadowColor: 'rgba(0, 0, 0, 0.12)',
|
||||
editorActiveTabBackground: '#ffffff',
|
||||
editorTabBarBackground: '#f5f5f5',
|
||||
terminalBackground: '#1a1a1a',
|
||||
terminalTitlebarBackground: '#333333',
|
||||
},
|
||||
// 文本标记样式
|
||||
textMarkers: {
|
||||
defaultChroma: 'rgba(255, 255, 0, 0.2)',
|
||||
},
|
||||
},
|
||||
gfm: true
|
||||
}),
|
||||
|
||||
// MDX 集成配置
|
||||
mdx(),
|
||||
swup(),
|
||||
react(),
|
||||
pagefind(),
|
||||
sitemap({
|
||||
filter: (page) => !page.includes("/api/"),
|
||||
serialize(item) {
|
||||
@ -129,11 +140,13 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
}),
|
||||
// 添加压缩插件 (必须放在最后位置)
|
||||
compressor()
|
||||
],
|
||||
|
||||
// Markdown 配置
|
||||
markdown: {
|
||||
syntaxHighlight: 'prism',
|
||||
syntaxHighlight: false, // 禁用默认的语法高亮,使用expressiveCode代替
|
||||
remarkPlugins: [
|
||||
[remarkEmoji, { emoticon: false, padded: true }]
|
||||
],
|
||||
@ -148,11 +161,6 @@ export default defineConfig({
|
||||
// 确保代码块内容不被解析
|
||||
passThrough: ['code']
|
||||
},
|
||||
shikiConfig: {
|
||||
theme: "github-dark",
|
||||
langs: [],
|
||||
wrap: true,
|
||||
},
|
||||
},
|
||||
|
||||
adapter: vercel(),
|
||||
|
1745
package-lock.json
generated
1745
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,12 +14,18 @@
|
||||
"@astrojs/react": "^4.2.4",
|
||||
"@astrojs/sitemap": "^3.3.0",
|
||||
"@astrojs/vercel": "^8.1.3",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@expressive-code/plugin-collapsible-sections": "^0.41.2",
|
||||
"@expressive-code/plugin-line-numbers": "^0.41.2",
|
||||
"@swup/astro": "^1.6.0",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/three": "^0.174.0",
|
||||
"astro": "^5.7.4",
|
||||
"astro-expressive-code": "^0.41.2",
|
||||
"astro-pagefind": "^1.8.3",
|
||||
"astro-theme-toggle": "^0.6.0",
|
||||
"cheerio": "^1.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"octokit": "^3.2.1",
|
||||
@ -31,6 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"astro-compressor": "^1.0.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"remark-emoji": "^5.0.1"
|
||||
}
|
||||
|
26908
src/assets/china.json
26908
src/assets/china.json
File diff suppressed because one or more lines are too long
@ -1,6 +1,7 @@
|
||||
---
|
||||
import { SITE_NAME, NAV_LINKS } from "@/consts.ts";
|
||||
import ThemeToggle from "./ThemeToggle.astro";
|
||||
import { Toggle } from "astro-theme-toggle";
|
||||
import Search from "astro-pagefind/components/Search";
|
||||
|
||||
// 获取当前路径
|
||||
const currentPath = Astro.url.pathname;
|
||||
@ -41,52 +42,24 @@ const normalizedPath =
|
||||
<!-- 导航链接 -->
|
||||
<div class="hidden md:flex md:items-center md:space-x-8">
|
||||
<!-- 桌面端搜索框 -->
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="desktop-search"
|
||||
class="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
|
||||
placeholder="搜索文章..."
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4 text-gray-400 dark:text-gray-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果容器(默认隐藏) -->
|
||||
<div
|
||||
id="desktop-search-results"
|
||||
class="absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 hidden"
|
||||
>
|
||||
<!-- 结果将通过JS动态填充 -->
|
||||
<div
|
||||
class="p-4 text-center text-gray-500 dark:text-gray-400"
|
||||
id="desktop-search-message"
|
||||
>
|
||||
<p>输入关键词开始搜索</p>
|
||||
</div>
|
||||
<ul
|
||||
class="divide-y divide-gray-200/70 dark:divide-gray-700/70"
|
||||
id="desktop-search-list"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<Search
|
||||
className="pagefind-ui"
|
||||
uiOptions={{
|
||||
showImages: false,
|
||||
resetStyles: false,
|
||||
showSubResults: true,
|
||||
translations: {
|
||||
placeholder: "搜索文章...",
|
||||
clear_search: "清除",
|
||||
load_more: "加载更多结果",
|
||||
search_label: "搜索网站",
|
||||
filters_label: "筛选",
|
||||
zero_results: "未找到 [SEARCH_TERM] 的结果",
|
||||
many_results: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
|
||||
one_result: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{
|
||||
NAV_LINKS.map((link) => (
|
||||
@ -103,7 +76,40 @@ const normalizedPath =
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<ThemeToggle />
|
||||
<div
|
||||
class="inline-flex items-center justify-center cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 group relative mt-1.5 w-8 h-8"
|
||||
>
|
||||
<Toggle class="flex items-center justify-center h-full w-full">
|
||||
<Fragment
|
||||
slot="icon-light"
|
||||
class="flex items-center justify-center h-full w-full"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
class="w-4 h-4 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
|
||||
></path>
|
||||
</svg>
|
||||
</Fragment>
|
||||
<Fragment
|
||||
slot="icon-dark"
|
||||
class="flex items-center justify-center h-full w-full"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
class="w-4 h-4 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
|
||||
></path>
|
||||
</svg>
|
||||
</Fragment>
|
||||
</Toggle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单按钮 -->
|
||||
@ -181,70 +187,25 @@ const normalizedPath =
|
||||
id="mobile-search-panel"
|
||||
class="hidden md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="mobile-search"
|
||||
class="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
|
||||
placeholder="搜索文章..."
|
||||
<div>
|
||||
<Search
|
||||
className="pagefind-ui"
|
||||
uiOptions={{
|
||||
showImages: false,
|
||||
resetStyles: false,
|
||||
showSubResults: true,
|
||||
translations: {
|
||||
placeholder: "搜索文章...",
|
||||
clear_search: "清除",
|
||||
load_more: "加载更多结果",
|
||||
search_label: "搜索网站",
|
||||
filters_label: "筛选",
|
||||
zero_results: "未找到 [SEARCH_TERM] 的结果",
|
||||
many_results: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
|
||||
one_result: "找到 [COUNT] 个 [SEARCH_TERM] 的结果",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 dark:text-gray-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<button
|
||||
id="mobile-search-close"
|
||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-label="关闭搜索"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 移动端搜索结果 -->
|
||||
<div
|
||||
id="mobile-search-results"
|
||||
class="mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm hidden"
|
||||
>
|
||||
<!-- 结果将通过JS动态填充 -->
|
||||
<div
|
||||
class="p-4 text-center text-gray-500 dark:text-gray-400"
|
||||
id="mobile-search-message"
|
||||
>
|
||||
<p>输入关键词开始搜索</p>
|
||||
</div>
|
||||
<ul
|
||||
class="divide-y divide-gray-200/70 dark:divide-gray-700/70"
|
||||
id="mobile-search-list"
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -281,7 +242,40 @@ const normalizedPath =
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-300"
|
||||
>切换主题</span
|
||||
>
|
||||
<ThemeToggle />
|
||||
<div
|
||||
class="group relative w-7 h-7 mt-1 flex items-center justify-center"
|
||||
>
|
||||
<Toggle class="flex items-center justify-center h-full w-full">
|
||||
<Fragment
|
||||
slot="icon-light"
|
||||
class="flex items-center justify-center h-full w-full"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
class="w-3.5 h-3.5 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
|
||||
></path>
|
||||
</svg>
|
||||
</Fragment>
|
||||
<Fragment
|
||||
slot="icon-dark"
|
||||
class="flex items-center justify-center h-full w-full"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
class="w-3.5 h-3.5 text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 fill-current flex-shrink-0 m-auto absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
|
||||
></path>
|
||||
</svg>
|
||||
</Fragment>
|
||||
</Toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -300,20 +294,6 @@ const normalizedPath =
|
||||
handler: EventListenerOrEventListenerObject;
|
||||
}> = [];
|
||||
|
||||
// 记录是否已加载文章数据
|
||||
interface Article {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string | Date;
|
||||
summary?: string;
|
||||
tags?: string[];
|
||||
image?: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
let articles: Article[] = [];
|
||||
let isArticlesLoaded = false;
|
||||
|
||||
// 添加事件监听器并记录,方便后续统一清理
|
||||
function addListener<K extends keyof HTMLElementEventMap>(
|
||||
element: EventTarget | null,
|
||||
@ -437,7 +417,6 @@ const normalizedPath =
|
||||
"mobile-search-button",
|
||||
);
|
||||
const mobileSearchPanel = document.getElementById("mobile-search-panel");
|
||||
const mobileSearch = document.getElementById("mobile-search");
|
||||
const mobileSearchClose = document.getElementById("mobile-search-close");
|
||||
|
||||
// 关闭移动端菜单的函数
|
||||
@ -457,6 +436,130 @@ const normalizedPath =
|
||||
}
|
||||
}
|
||||
|
||||
// 清空桌面搜索框的函数
|
||||
function clearDesktopSearch(): void {
|
||||
// 获取关键元素
|
||||
const searchInput = document.querySelector('.pagefind-ui__search-input');
|
||||
const clearButton = document.querySelector('.pagefind-ui__search-clear');
|
||||
const resultsContainer = document.querySelector('.pagefind-ui__results');
|
||||
|
||||
// 隐藏搜索结果面板
|
||||
if (resultsContainer) {
|
||||
// 直接隐藏结果容器
|
||||
resultsContainer.setAttribute('style', 'display: none !important');
|
||||
}
|
||||
|
||||
// 如果有清除按钮,点击它清空输入框
|
||||
if (clearButton) {
|
||||
(clearButton as HTMLElement).click();
|
||||
} else if (searchInput) {
|
||||
// 备选方案,设置输入框为空
|
||||
(searchInput as HTMLInputElement).value = '';
|
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
// 监听搜索结果链接点击事件
|
||||
function setupSearchResultListeners(): void {
|
||||
// 使用事件委托,监听整个搜索容器
|
||||
const searchContainer = document.querySelector(".pagefind-ui");
|
||||
if (searchContainer) {
|
||||
// 移除之前可能存在的事件监听器
|
||||
const existingHandler = (searchContainer as any)._clickHandler;
|
||||
if (existingHandler) {
|
||||
searchContainer.removeEventListener("click", existingHandler);
|
||||
}
|
||||
|
||||
// 添加新的事件监听器
|
||||
const clickHandler = (e: Event) => {
|
||||
const target = e.target as HTMLElement;
|
||||
// 检查是否点击了搜索结果链接或其父元素
|
||||
const linkElement = target.closest(
|
||||
".pagefind-ui__result-link, .pagefind-ui__result-title, .pagefind-ui__results a",
|
||||
);
|
||||
|
||||
if (linkElement && linkElement.tagName === "A") {
|
||||
// 不做任何处理,避免干扰导航
|
||||
// 让路由变化后的处理函数来隐藏搜索面板
|
||||
}
|
||||
};
|
||||
|
||||
// 存储处理函数以便后续可能的移除
|
||||
(searchContainer as any)._clickHandler = clickHandler;
|
||||
|
||||
// 添加事件监听
|
||||
addListener(searchContainer, "click", clickHandler, {
|
||||
capture: true,
|
||||
});
|
||||
} else {
|
||||
console.warn("[Header] 未找到搜索容器,无法添加点击事件");
|
||||
}
|
||||
|
||||
// 直接为文档添加事件监听,确保能捕获到所有点击
|
||||
addListener(
|
||||
document.body,
|
||||
"click",
|
||||
(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const isSearchResult = target.closest(
|
||||
".pagefind-ui__result-link, .pagefind-ui__result-title, .pagefind-ui__results a",
|
||||
);
|
||||
|
||||
if (isSearchResult && isSearchResult.tagName === "A") {
|
||||
// 不做任何处理,避免干扰导航
|
||||
// 让路由变化后的处理函数来隐藏搜索面板
|
||||
|
||||
// 只关闭移动端搜索面板,因为它不影响导航
|
||||
closeMobileSearch();
|
||||
}
|
||||
},
|
||||
{ capture: true },
|
||||
);
|
||||
}
|
||||
|
||||
// 初始化搜索功能并设置页面加载后的变化监听
|
||||
function initSearch(): void {
|
||||
setupSearchResultListeners();
|
||||
|
||||
// Pagefind可能在页面加载后动态添加内容,添加MutationObserver以监视这些变化
|
||||
const pageObserver = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.addedNodes.length > 0) {
|
||||
// 检查是否有新添加的搜索容器或结果
|
||||
const hasNewSearchElements = Array.from(mutation.addedNodes).some(
|
||||
(node) => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const element = node as HTMLElement;
|
||||
return (
|
||||
element.classList?.contains("pagefind-ui") ||
|
||||
element.querySelector(".pagefind-ui") !== null ||
|
||||
element.classList?.contains("pagefind-ui__results") ||
|
||||
element.querySelector(".pagefind-ui__results") !== null
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
if (hasNewSearchElements) {
|
||||
setupSearchResultListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 观察整个body以捕获所有变化
|
||||
pageObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// 添加导航后清空搜索框的监听
|
||||
addListener(document, "astro:page-load", () => {
|
||||
// 页面加载后清空搜索框和隐藏结果面板
|
||||
setTimeout(() => {
|
||||
clearDesktopSearch();
|
||||
}, 100); // 短暂延迟确保搜索组件已加载
|
||||
});
|
||||
}
|
||||
|
||||
if (mobileMenuButton && mobileMenu && menuOpenIcon && menuCloseIcon) {
|
||||
// 移动端菜单按钮点击事件
|
||||
(mobileMenuButton as HTMLElement).style.pointerEvents = "auto";
|
||||
@ -526,7 +629,6 @@ const normalizedPath =
|
||||
} else {
|
||||
closeMobileMenu();
|
||||
mobileSearchPanel.classList.remove("hidden");
|
||||
if (mobileSearch) mobileSearch.focus();
|
||||
}
|
||||
},
|
||||
{ capture: true },
|
||||
@ -548,6 +650,9 @@ const normalizedPath =
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化搜索功能
|
||||
initSearch();
|
||||
|
||||
// 处理移动端主题切换容器
|
||||
const themeToggleContainer = document.getElementById(
|
||||
"theme-toggle-container",
|
||||
@ -560,13 +665,12 @@ const normalizedPath =
|
||||
(e: Event) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.id !== "theme-toggle-button" &&
|
||||
!target.closest("#theme-toggle-button")
|
||||
target.tagName !== "ASTRO-THEME-TOGGLE" &&
|
||||
!target.closest("astro-theme-toggle")
|
||||
) {
|
||||
e.stopPropagation();
|
||||
const toggleButton = themeToggleContainer.querySelector(
|
||||
"#theme-toggle-button",
|
||||
);
|
||||
const toggleButton =
|
||||
themeToggleContainer.querySelector("astro-theme-toggle");
|
||||
if (toggleButton) {
|
||||
(toggleButton as HTMLElement).click();
|
||||
}
|
||||
@ -575,309 +679,47 @@ const normalizedPath =
|
||||
{ capture: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索功能逻辑
|
||||
function initSearch(): void {
|
||||
// 搜索节流函数
|
||||
function debounce<T extends (...args: any[]) => void>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
return function(this: any, ...args: Parameters<T>): void {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
}
|
||||
// 路由变化时处理
|
||||
const routeEvents = [
|
||||
"astro:page-load", // Astro路由导航完成
|
||||
"astro:after-swap" // Astro视图变化后
|
||||
];
|
||||
|
||||
// 获取DOM元素
|
||||
const desktopSearch = document.getElementById("desktop-search");
|
||||
const desktopResults = document.getElementById("desktop-search-results");
|
||||
const desktopList = document.getElementById("desktop-search-list");
|
||||
const desktopMessage = document.getElementById("desktop-search-message");
|
||||
|
||||
const mobileSearch = document.getElementById("mobile-search");
|
||||
const mobileResults = document.getElementById("mobile-search-results");
|
||||
const mobileList = document.getElementById("mobile-search-list");
|
||||
const mobileMessage = document.getElementById("mobile-search-message");
|
||||
|
||||
// 获取文章数据
|
||||
async function fetchArticles(): Promise<void> {
|
||||
if (isArticlesLoaded && articles.length > 0) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/search");
|
||||
if (!response.ok) {
|
||||
throw new Error("获取文章数据失败");
|
||||
}
|
||||
articles = await response.json();
|
||||
isArticlesLoaded = true;
|
||||
} catch (error) {
|
||||
console.error("获取文章失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 高亮文本中的匹配部分
|
||||
function highlightText(text: string, query: string): string {
|
||||
if (!text || !query.trim()) return text;
|
||||
|
||||
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(`(${escapedQuery})`, "gi");
|
||||
|
||||
return text.replace(
|
||||
regex,
|
||||
'<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>',
|
||||
);
|
||||
}
|
||||
|
||||
// 搜索文章
|
||||
function searchArticles(
|
||||
query: string,
|
||||
resultsList: HTMLElement,
|
||||
resultsMessage: HTMLElement
|
||||
): void {
|
||||
if (!query.trim()) {
|
||||
resultsList.innerHTML = "";
|
||||
resultsMessage.textContent = "输入关键词开始搜索";
|
||||
resultsMessage.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
if (articles.length === 0) {
|
||||
resultsMessage.textContent = "正在加载数据...";
|
||||
resultsMessage.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
// 过滤并排序结果
|
||||
const filteredArticles = articles
|
||||
.filter((article: Article) => {
|
||||
const title = article.title.toLowerCase();
|
||||
const tags = article.tags
|
||||
? article.tags.map((tag: string) => tag.toLowerCase())
|
||||
: [];
|
||||
const summary = article.summary ? article.summary.toLowerCase() : "";
|
||||
const content = article.content ? article.content.toLowerCase() : "";
|
||||
|
||||
return (
|
||||
title.includes(lowerQuery) ||
|
||||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
|
||||
summary.includes(lowerQuery) ||
|
||||
content.includes(lowerQuery)
|
||||
);
|
||||
})
|
||||
.sort((a: Article, b: Article) => {
|
||||
// 标题匹配优先
|
||||
const aTitle = a.title.toLowerCase();
|
||||
const bTitle = b.title.toLowerCase();
|
||||
|
||||
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 内容匹配次之
|
||||
const aContent = a.content ? a.content.toLowerCase() : "";
|
||||
const bContent = b.content ? b.content.toLowerCase() : "";
|
||||
|
||||
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 日期排序
|
||||
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
||||
})
|
||||
.slice(0, 10); // 限制结果数量
|
||||
|
||||
if (filteredArticles.length === 0) {
|
||||
resultsList.innerHTML = "";
|
||||
resultsMessage.textContent = "没有找到相关内容";
|
||||
resultsMessage.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
resultsMessage.style.display = "none";
|
||||
resultsList.innerHTML = filteredArticles
|
||||
.map((article: Article) => {
|
||||
// 生成匹配的内容片段
|
||||
let contentMatch = "";
|
||||
if (
|
||||
article.content &&
|
||||
article.content.toLowerCase().includes(lowerQuery)
|
||||
) {
|
||||
// 找到匹配文本在内容中的位置
|
||||
const matchIndex = article.content
|
||||
.toLowerCase()
|
||||
.indexOf(lowerQuery);
|
||||
// 计算片段的起始和结束位置
|
||||
const startPos = Math.max(0, matchIndex - 50);
|
||||
const endPos = Math.min(article.content.length, matchIndex + 100);
|
||||
// 提取片段
|
||||
let snippet = article.content.substring(startPos, endPos);
|
||||
if (startPos > 0) {
|
||||
snippet = "..." + snippet;
|
||||
}
|
||||
if (endPos < article.content.length) {
|
||||
snippet = snippet + "...";
|
||||
}
|
||||
snippet = highlightText(snippet, query);
|
||||
contentMatch = `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">${snippet}</p>`;
|
||||
}
|
||||
|
||||
// 高亮标题和摘要中的匹配文本
|
||||
const highlightedTitle = highlightText(article.title, query);
|
||||
const highlightedSummary = article.summary
|
||||
? highlightText(article.summary, query)
|
||||
: "";
|
||||
|
||||
return `
|
||||
<li>
|
||||
<a href="/articles/${article.id}" class="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70 search-result-link">
|
||||
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">${highlightedTitle}</h3>
|
||||
${article.summary ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 truncate">${highlightedSummary}</p>` : ""}
|
||||
${contentMatch}
|
||||
${
|
||||
article.tags && article.tags.length > 0
|
||||
? `
|
||||
<div class="flex flex-wrap gap-1 mt-1.5">
|
||||
${article.tags
|
||||
.slice(0, 3)
|
||||
.map(
|
||||
(tag: string) => `
|
||||
<span class="inline-block text-xs bg-primary-50/50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400 py-0.5 px-1.5 rounded-full">#${tag}</span>
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
${article.tags.length > 3 ? `<span class="text-xs text-gray-400 dark:text-gray-500">+${article.tags.length - 3}</span>` : ""}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
// 为搜索结果链接添加点击事件,点击后关闭搜索面板
|
||||
addSearchResultsClickListeners();
|
||||
}
|
||||
|
||||
// 节流搜索
|
||||
const debouncedDesktopSearch = debounce((value: string) => {
|
||||
if (desktopList && desktopMessage) {
|
||||
searchArticles(
|
||||
value,
|
||||
desktopList as HTMLElement,
|
||||
desktopMessage as HTMLElement,
|
||||
);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const debouncedMobileSearch = debounce((value: string) => {
|
||||
if (mobileList && mobileMessage) {
|
||||
searchArticles(
|
||||
value,
|
||||
mobileList as HTMLElement,
|
||||
mobileMessage as HTMLElement,
|
||||
);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// 桌面端搜索逻辑
|
||||
if (desktopSearch && desktopResults) {
|
||||
// 聚焦时显示结果
|
||||
addListener(desktopSearch, "focus", () => {
|
||||
desktopResults.classList.remove("hidden");
|
||||
if (!isArticlesLoaded) fetchArticles();
|
||||
routeEvents.forEach(eventName => {
|
||||
addListener(document, eventName, () => {
|
||||
// 页面加载后清空搜索框和隐藏结果面板
|
||||
clearDesktopSearch();
|
||||
closeMobileSearch();
|
||||
});
|
||||
});
|
||||
|
||||
// 输入时更新搜索结果
|
||||
addListener(desktopSearch, "input", (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target && target.value !== undefined) {
|
||||
debouncedDesktopSearch(target.value);
|
||||
}
|
||||
});
|
||||
// 直接监听popstate和pushstate事件
|
||||
addListener(window, "popstate", () => {
|
||||
clearDesktopSearch();
|
||||
closeMobileSearch();
|
||||
});
|
||||
|
||||
// 点击外部关闭结果
|
||||
const documentClickHandler = (e: MouseEvent) => {
|
||||
const target = e.target as Node;
|
||||
if (
|
||||
desktopSearch &&
|
||||
!desktopSearch.contains(target) &&
|
||||
!desktopResults.contains(target)
|
||||
) {
|
||||
desktopResults.classList.add("hidden");
|
||||
}
|
||||
};
|
||||
// 添加自定义监听处理history API
|
||||
const originalPushState = history.pushState;
|
||||
history.pushState = function(...args) {
|
||||
// 调用原始方法
|
||||
const result = originalPushState.apply(this, args as [any, string, string | URL | null]);
|
||||
|
||||
addListener(document, "click", documentClickHandler as EventListener);
|
||||
// 触发自定义事件
|
||||
const event = new Event("pushstate");
|
||||
window.dispatchEvent(event);
|
||||
|
||||
// ESC键关闭结果
|
||||
addListener(desktopSearch, "keydown", ((e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
desktopResults.classList.add("hidden");
|
||||
}
|
||||
}) as EventListener);
|
||||
}
|
||||
// 在pushstate时清空搜索面板
|
||||
clearDesktopSearch();
|
||||
closeMobileSearch();
|
||||
|
||||
// 移动端搜索逻辑
|
||||
if (mobileSearch && mobileResults) {
|
||||
// 输入时更新搜索结果
|
||||
addListener(mobileSearch, "input", (e: Event) => {
|
||||
mobileResults.classList.remove("hidden");
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target && target.value !== undefined) {
|
||||
debouncedMobileSearch(target.value);
|
||||
if (!isArticlesLoaded) fetchArticles();
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// ESC键关闭搜索面板
|
||||
addListener(mobileSearch, "keydown", ((e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
const mobileSearchPanel = document.getElementById(
|
||||
"mobile-search-panel",
|
||||
);
|
||||
if (mobileSearchPanel) {
|
||||
mobileSearchPanel.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
}) as EventListener);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭所有搜索面板的函数
|
||||
function closeSearchPanels(): void {
|
||||
const desktopSearchResults = document.getElementById("desktop-search-results");
|
||||
if (desktopSearchResults) {
|
||||
desktopSearchResults.classList.add("hidden");
|
||||
}
|
||||
|
||||
const mobileSearchPanel = document.getElementById("mobile-search-panel");
|
||||
if (mobileSearchPanel) {
|
||||
mobileSearchPanel.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// 为搜索结果链接添加点击事件
|
||||
function addSearchResultsClickListeners(): void {
|
||||
const searchResultLinks = document.querySelectorAll('.search-result-link');
|
||||
|
||||
searchResultLinks.forEach(link => {
|
||||
addListener(link, 'click', () => {
|
||||
closeSearchPanels();
|
||||
});
|
||||
addListener(window, "pushstate", () => {
|
||||
clearDesktopSearch();
|
||||
closeMobileSearch();
|
||||
});
|
||||
}
|
||||
|
||||
@ -897,12 +739,7 @@ const normalizedPath =
|
||||
function setupHeader(): void {
|
||||
cleanup();
|
||||
initHeader();
|
||||
initSearch();
|
||||
registerCleanup();
|
||||
|
||||
// 在页面路由变化时关闭搜索面板
|
||||
addListener(document, 'astro:page-load', closeSearchPanels);
|
||||
addListener(document, 'astro:after-swap', closeSearchPanels);
|
||||
}
|
||||
|
||||
// 在页面加载时初始化
|
||||
|
@ -3,6 +3,8 @@ import "@/styles/global.css";
|
||||
import Header from "@/components/Header.astro";
|
||||
import Footer from "@/components/Footer.astro";
|
||||
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
|
||||
import { ThemeScript } from 'astro-theme-toggle';
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
|
||||
// 定义Props接口
|
||||
interface Props {
|
||||
@ -27,72 +29,41 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, tags } = Astro.
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- 基本元数据 -->
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description || `${SITE_NAME} - 个人博客`} />
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
<!-- 使用 AstroSeo 组件替换原有的 SEO 标签 -->
|
||||
<AstroSeo
|
||||
title={title}
|
||||
description={description || `${SITE_NAME} - 个人博客`}
|
||||
canonical={canonicalURL.toString()}
|
||||
openGraph={{
|
||||
type: 'article',
|
||||
url: canonicalURL.toString(),
|
||||
title: title,
|
||||
description: description || `${SITE_NAME} - 个人博客`,
|
||||
site_name: SITE_NAME,
|
||||
...(date && { article: {
|
||||
publishedTime: date.toISOString(),
|
||||
tags: tags || []
|
||||
}}),
|
||||
}}
|
||||
twitter={{
|
||||
cardType: 'summary_large_image',
|
||||
site: SITE_NAME,
|
||||
handle: SITE_NAME,
|
||||
}}
|
||||
additionalMetaTags={[
|
||||
{
|
||||
property: 'article:published_time',
|
||||
content: date ? date.toISOString() : '',
|
||||
},
|
||||
...(tags?.map(tag => ({
|
||||
property: 'article:tag',
|
||||
content: tag,
|
||||
})) || []),
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- 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} - 个人博客`} />
|
||||
|
||||
<!-- 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} - 个人博客`} />
|
||||
|
||||
<!-- 文章特定元数据 -->
|
||||
{date && <meta property="article:published_time" content={date.toISOString()} />}
|
||||
{tags && tags.map(tag => (
|
||||
<meta property="article:tag" content={tag} />
|
||||
))}
|
||||
<script is:inline>
|
||||
// 立即执行主题初始化,采用"无闪烁"加载方式
|
||||
(function() {
|
||||
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'; // 默认浅色主题
|
||||
|
||||
// 按照逻辑优先级应用主题
|
||||
if (storedTheme) {
|
||||
// 如果有存储的主题设置,则应用它
|
||||
theme = storedTheme;
|
||||
} else if (systemTheme) {
|
||||
// 如果没有存储的设置,检查系统主题
|
||||
theme = systemTheme;
|
||||
}
|
||||
|
||||
// 立即设置文档主题,在DOM渲染前应用,避免闪烁
|
||||
document.documentElement.dataset.theme = theme;
|
||||
|
||||
// 监听系统主题变化(只有当主题设为跟随系统时才响应)
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// 添加系统主题变化监听
|
||||
mediaQuery.addEventListener('change', handleMediaChange);
|
||||
} catch (error) {
|
||||
// 出错时应用默认浅色主题,确保页面正常显示
|
||||
document.documentElement.dataset.theme = 'light';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<!-- 主题切换脚本 -->
|
||||
<ThemeScript />
|
||||
</head>
|
||||
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg flex flex-col min-h-screen">
|
||||
<Header />
|
||||
|
@ -1,434 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
height?: number;
|
||||
width?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
height = 16,
|
||||
width = 16,
|
||||
fill = "currentColor",
|
||||
className = ""
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<button
|
||||
id="theme-toggle-button"
|
||||
class={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
|
||||
aria-label="切换主题"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<!-- 月亮图标 (暗色模式) -->
|
||||
<svg
|
||||
id="dark-icon"
|
||||
style={`height: ${height}px; width: ${width}px;`}
|
||||
fill={fill}
|
||||
viewBox="0 0 16 16"
|
||||
class="hover:scale-110 hidden dark:block"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
|
||||
</svg>
|
||||
|
||||
<!-- 太阳图标 (亮色模式) -->
|
||||
<svg
|
||||
id="light-icon"
|
||||
style={`height: ${height}px; width: ${width}px;`}
|
||||
fill={fill}
|
||||
viewBox="0 0 16 16"
|
||||
class="hover:scale-110 block dark:hidden"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script is:inline>
|
||||
// 主题切换逻辑
|
||||
(function() {
|
||||
|
||||
// 页面导航计数器(跟踪页面跳转次数)
|
||||
let pageNavigationCount = 0;
|
||||
|
||||
// 存储事件监听器,便于统一清理
|
||||
const listeners = [];
|
||||
|
||||
// 定时器
|
||||
let transitionTimeout = null;
|
||||
|
||||
// 直接从按钮移除事件监听器
|
||||
function cleanupButtonListeners() {
|
||||
// 查找所有主题切换按钮
|
||||
const buttons = document.querySelectorAll('#theme-toggle-button');
|
||||
|
||||
buttons.forEach(button => {
|
||||
// 移除所有可能的事件
|
||||
if (button._clickHandler) {
|
||||
button.removeEventListener('click', button._clickHandler, { capture: true });
|
||||
delete button._clickHandler;
|
||||
}
|
||||
|
||||
if (button._keydownHandler) {
|
||||
button.removeEventListener('keydown', button._keydownHandler);
|
||||
delete button._keydownHandler;
|
||||
}
|
||||
|
||||
// 清除其他可能的事件
|
||||
const otherClickHandlers = button.__themeToggleClickHandlers || [];
|
||||
otherClickHandlers.forEach(handler => {
|
||||
try {
|
||||
button.removeEventListener('click', handler, { capture: true });
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
});
|
||||
|
||||
const otherKeydownHandlers = button.__themeToggleKeydownHandlers || [];
|
||||
otherKeydownHandlers.forEach(handler => {
|
||||
try {
|
||||
button.removeEventListener('keydown', handler);
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
});
|
||||
|
||||
// 重置处理函数数组
|
||||
button.__themeToggleClickHandlers = [];
|
||||
button.__themeToggleKeydownHandlers = [];
|
||||
});
|
||||
|
||||
// 清理容器
|
||||
const container = document.getElementById('theme-toggle-container');
|
||||
if (container) {
|
||||
if (container._clickHandler) {
|
||||
container.removeEventListener('click', container._clickHandler);
|
||||
delete container._clickHandler;
|
||||
}
|
||||
|
||||
// 清除其他可能的事件
|
||||
const otherClickHandlers = container.__themeToggleClickHandlers || [];
|
||||
otherClickHandlers.forEach(handler => {
|
||||
try {
|
||||
container.removeEventListener('click', handler);
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
});
|
||||
|
||||
// 重置处理函数数组
|
||||
container.__themeToggleClickHandlers = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 添加事件监听器并记录,方便后续统一清理
|
||||
function addListener(element, eventType, handler, options) {
|
||||
if (!element) return null;
|
||||
|
||||
// 确保先移除可能已存在的同类型事件处理函数
|
||||
if (eventType === 'click' && element.id === 'theme-toggle-button') {
|
||||
if (element._clickHandler) {
|
||||
element.removeEventListener('click', element._clickHandler, { capture: true });
|
||||
}
|
||||
element._clickHandler = handler;
|
||||
|
||||
// 保存到数组中以便清理
|
||||
if (!element.__themeToggleClickHandlers) {
|
||||
element.__themeToggleClickHandlers = [];
|
||||
}
|
||||
element.__themeToggleClickHandlers.push(handler);
|
||||
}
|
||||
|
||||
if (eventType === 'keydown' && element.id === 'theme-toggle-button') {
|
||||
if (element._keydownHandler) {
|
||||
element.removeEventListener('keydown', element._keydownHandler);
|
||||
}
|
||||
element._keydownHandler = handler;
|
||||
|
||||
// 保存到数组中以便清理
|
||||
if (!element.__themeToggleKeydownHandlers) {
|
||||
element.__themeToggleKeydownHandlers = [];
|
||||
}
|
||||
element.__themeToggleKeydownHandlers.push(handler);
|
||||
}
|
||||
|
||||
if (eventType === 'click' && element.id === 'theme-toggle-container') {
|
||||
if (element._clickHandler) {
|
||||
element.removeEventListener('click', element._clickHandler);
|
||||
}
|
||||
element._clickHandler = handler;
|
||||
|
||||
// 保存到数组中以便清理
|
||||
if (!element.__themeToggleClickHandlers) {
|
||||
element.__themeToggleClickHandlers = [];
|
||||
}
|
||||
element.__themeToggleClickHandlers.push(handler);
|
||||
}
|
||||
|
||||
element.addEventListener(eventType, handler, options);
|
||||
listeners.push({ element, eventType, handler, options });
|
||||
return handler;
|
||||
}
|
||||
|
||||
// 清理函数 - 移除所有事件监听器
|
||||
function cleanup() {
|
||||
// 先直接从按钮清理事件
|
||||
cleanupButtonListeners();
|
||||
|
||||
// 移除所有监听器
|
||||
listeners.forEach(({ element, eventType, handler, options }) => {
|
||||
try {
|
||||
element.removeEventListener(eventType, handler, options);
|
||||
} catch (err) {
|
||||
// 忽略错误
|
||||
}
|
||||
});
|
||||
|
||||
// 清空数组
|
||||
listeners.length = 0;
|
||||
|
||||
// 清理任何定时器
|
||||
if (transitionTimeout) {
|
||||
clearTimeout(transitionTimeout);
|
||||
transitionTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化主题切换功能
|
||||
function setupThemeToggle() {
|
||||
// 确保当前没有活动的主题切换按钮事件
|
||||
cleanup();
|
||||
|
||||
// 获取所有主题切换按钮
|
||||
const themeToggleButtons = document.querySelectorAll('#theme-toggle-button');
|
||||
|
||||
if (!themeToggleButtons.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let transitioning = false;
|
||||
|
||||
// 获取系统首选主题
|
||||
const getSystemTheme = () => {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
|
||||
// 初始化主题
|
||||
const initializeTheme = () => {
|
||||
const storedTheme = localStorage.getItem('theme');
|
||||
const systemTheme = getSystemTheme();
|
||||
|
||||
// 按照逻辑优先级应用主题
|
||||
if (storedTheme) {
|
||||
document.documentElement.dataset.theme = storedTheme;
|
||||
} else if (systemTheme) {
|
||||
document.documentElement.dataset.theme = systemTheme;
|
||||
} else {
|
||||
document.documentElement.dataset.theme = 'light';
|
||||
}
|
||||
};
|
||||
|
||||
// 切换主题
|
||||
const toggleTheme = () => {
|
||||
if (transitioning) {
|
||||
return;
|
||||
}
|
||||
|
||||
transitioning = true;
|
||||
|
||||
// 获取当前主题
|
||||
const currentTheme = document.documentElement.dataset.theme;
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
|
||||
// 更新 HTML 属性
|
||||
document.documentElement.dataset.theme = newTheme;
|
||||
|
||||
// 更新本地存储
|
||||
const systemTheme = getSystemTheme();
|
||||
|
||||
if (newTheme === systemTheme) {
|
||||
localStorage.removeItem('theme');
|
||||
} else {
|
||||
localStorage.setItem('theme', newTheme);
|
||||
}
|
||||
|
||||
// 添加防抖
|
||||
if (transitionTimeout) {
|
||||
clearTimeout(transitionTimeout);
|
||||
}
|
||||
|
||||
transitionTimeout = setTimeout(() => {
|
||||
transitioning = false;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// 监听系统主题变化
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// 添加系统主题变化监听
|
||||
addListener(mediaQuery, 'change', handleMediaChange);
|
||||
|
||||
// 为每个按钮添加事件
|
||||
themeToggleButtons.forEach((button, index) => {
|
||||
// 确保移除旧的事件监听
|
||||
if (button._clickHandler) {
|
||||
button.removeEventListener('click', button._clickHandler, { capture: true });
|
||||
}
|
||||
if (button._keydownHandler) {
|
||||
button.removeEventListener('keydown', button._keydownHandler);
|
||||
}
|
||||
|
||||
try {
|
||||
button.style.pointerEvents = 'auto';
|
||||
} catch (e) {
|
||||
// 忽略样式错误
|
||||
}
|
||||
|
||||
// 创建点击处理函数
|
||||
const clickHandler = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleTheme();
|
||||
};
|
||||
|
||||
// 点击事件 - 使用捕获模式并保存引用
|
||||
addListener(button, 'click', clickHandler, { capture: true });
|
||||
|
||||
// 键盘事件
|
||||
const keydownHandler = (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
toggleTheme();
|
||||
}
|
||||
};
|
||||
addListener(button, 'keydown', keydownHandler);
|
||||
});
|
||||
|
||||
// 处理移动端主题切换容器
|
||||
const themeToggleContainer = document.getElementById('theme-toggle-container');
|
||||
if (themeToggleContainer) {
|
||||
// 确保移除旧的事件监听
|
||||
if (themeToggleContainer._clickHandler) {
|
||||
themeToggleContainer.removeEventListener('click', themeToggleContainer._clickHandler);
|
||||
}
|
||||
|
||||
const containerClickHandler = (e) => {
|
||||
const target = e.target;
|
||||
if (target.id !== 'theme-toggle-button' && !target.closest('#theme-toggle-button')) {
|
||||
e.stopPropagation();
|
||||
toggleTheme();
|
||||
}
|
||||
};
|
||||
|
||||
addListener(themeToggleContainer, 'click', containerClickHandler);
|
||||
}
|
||||
|
||||
// 初始化主题
|
||||
initializeTheme();
|
||||
}
|
||||
|
||||
// 注册清理函数 - 确保在每次页面转换前清理事件
|
||||
function registerCleanup() {
|
||||
const cleanupEvents = [
|
||||
'astro:before-preparation',
|
||||
'astro:before-swap',
|
||||
'swup:willReplaceContent'
|
||||
];
|
||||
|
||||
// 为每个事件注册一次性清理函数
|
||||
cleanupEvents.forEach(eventName => {
|
||||
const handler = () => {
|
||||
cleanup();
|
||||
};
|
||||
|
||||
document.addEventListener(eventName, handler, { once: true });
|
||||
});
|
||||
|
||||
// 页面卸载时清理
|
||||
window.addEventListener('beforeunload', () => {
|
||||
cleanup();
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
pageNavigationCount++;
|
||||
setupThemeToggle();
|
||||
registerCleanup();
|
||||
}
|
||||
|
||||
// 监听页面转换事件
|
||||
function setupPageTransitionEvents() {
|
||||
// 确保事件处理程序唯一性的函数
|
||||
function setupUniqueEvent(eventName, callback) {
|
||||
const eventKey = `__theme_toggle_event_${eventName.replace(/:/g, '_')}`;
|
||||
|
||||
// 移除可能存在的旧处理函数
|
||||
if (window[eventKey]) {
|
||||
document.removeEventListener(eventName, window[eventKey]);
|
||||
}
|
||||
|
||||
// 保存新处理函数并注册
|
||||
window[eventKey] = callback;
|
||||
document.addEventListener(eventName, window[eventKey]);
|
||||
}
|
||||
|
||||
// 页面转换后事件
|
||||
const pageTransitionEvents = [
|
||||
{ name: 'astro:after-swap', delay: 10 },
|
||||
{ name: 'astro:page-load', delay: 10 },
|
||||
{ name: 'swup:contentReplaced', delay: 10 }
|
||||
];
|
||||
|
||||
// 设置每个页面转换事件
|
||||
pageTransitionEvents.forEach(({ name, delay }) => {
|
||||
setupUniqueEvent(name, () => {
|
||||
cleanupButtonListeners(); // 立即清理按钮上的事件
|
||||
|
||||
// 延迟初始化,确保DOM完全更新
|
||||
setTimeout(() => {
|
||||
cleanupButtonListeners(); // 再次清理,确保没有遗漏
|
||||
init();
|
||||
}, delay);
|
||||
});
|
||||
});
|
||||
|
||||
// 特别处理 swup:pageView 事件
|
||||
setupUniqueEvent('swup:pageView', () => {
|
||||
// 对于偶数次页面跳转,特别确保事件被正确重新绑定
|
||||
if (pageNavigationCount % 2 === 0) {
|
||||
setTimeout(() => {
|
||||
const buttons = document.querySelectorAll('#theme-toggle-button');
|
||||
if (buttons.length > 0) {
|
||||
cleanupButtonListeners();
|
||||
setupThemeToggle();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 设置页面转换事件监听
|
||||
setupPageTransitionEvents();
|
||||
|
||||
// 在页面加载后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init();
|
||||
}, { once: true });
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
init();
|
||||
}, 0);
|
||||
}
|
||||
})();
|
||||
</script>
|
@ -4,8 +4,8 @@ export const SITE_DESCRIPTION = "记录生活,分享所思";
|
||||
|
||||
export const NAV_LINKS = [
|
||||
{ href: '/', text: '首页' },
|
||||
{ href: '/articles', text: '文章' },
|
||||
{ href: '/filtered', text: '筛选' },
|
||||
{ href: '/articles', text: '文章' },
|
||||
{ href: '/movies', text: '观影' },
|
||||
{ href: '/books', text: '读书' },
|
||||
{ href: '/projects', text: '项目' },
|
||||
|
@ -582,75 +582,11 @@ function getArticleUrl(articleId: string) {
|
||||
updateActiveHeading();
|
||||
}
|
||||
|
||||
function setupCodeBlocks() {
|
||||
const codeBlocks = document.querySelectorAll("pre");
|
||||
if (!codeBlocks.length) return;
|
||||
|
||||
codeBlocks.forEach(pre => {
|
||||
const code = pre.querySelector("code");
|
||||
if (!code || pre.querySelector('.code-header')) return;
|
||||
|
||||
const className = code.className;
|
||||
const languageMatch = className.match(/language-(\w+)/);
|
||||
const language = languageMatch ? languageMatch[1] : "text";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "code-header flex justify-between items-center text-xs px-4 py-2 bg-secondary-800 dark:bg-gray-900 text-secondary-300 dark:text-secondary-400 rounded-t-lg";
|
||||
|
||||
const languageLabel = document.createElement("span");
|
||||
languageLabel.className = "code-language font-mono";
|
||||
languageLabel.textContent = language;
|
||||
|
||||
const copyButton = document.createElement("button");
|
||||
copyButton.className = "code-copy-button flex items-center gap-1 hover:text-white dark:hover:text-primary-400 transition-colors";
|
||||
|
||||
const copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
|
||||
const successIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M20 6L9 17l-5-5"></path></svg>`;
|
||||
const errorIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`;
|
||||
|
||||
copyButton.innerHTML = `${copyIcon}<span>复制</span>`;
|
||||
copyButton.setAttribute("aria-label", "复制代码");
|
||||
copyButton.setAttribute("title", "复制代码到剪贴板");
|
||||
|
||||
addListener(copyButton, "click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
navigator.clipboard.writeText(code.textContent || "")
|
||||
.then(() => {
|
||||
copyButton.innerHTML = `${successIcon}<span>已复制</span>`;
|
||||
copyButton.classList.add("text-green-400");
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML = `${copyIcon}<span>复制</span>`;
|
||||
copyButton.classList.remove("text-green-400");
|
||||
}, 2000);
|
||||
})
|
||||
.catch(() => {
|
||||
copyButton.innerHTML = `${errorIcon}<span>失败</span>`;
|
||||
copyButton.classList.add("text-red-400");
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML = `${copyIcon}<span>复制</span>`;
|
||||
copyButton.classList.remove("text-red-400");
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
header.appendChild(languageLabel);
|
||||
header.appendChild(copyButton);
|
||||
pre.insertBefore(header, pre.firstChild);
|
||||
|
||||
pre.classList.add("rounded-b-lg", "mt-0");
|
||||
pre.style.marginTop = "0";
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (!document.querySelector("article")) return;
|
||||
|
||||
setupProgressBar();
|
||||
setupTableOfContents();
|
||||
setupCodeBlocks();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
|
@ -309,12 +309,12 @@ function getArticleUrl(articleId: string) {
|
||||
</div>
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<div id="noResultsMessage" class="hidden text-center py-16 bg-white rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 mb-12">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto text-primary-200 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div id="noResultsMessage" class="hidden text-center py-16 bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 mb-12">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto text-primary-200 dark:text-primary-700 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
|
||||
</svg>
|
||||
<h3 class="text-2xl font-bold text-gray-700 mb-2">没有找到符合条件的文章</h3>
|
||||
<p class="text-gray-500 max-w-md mx-auto">请尝试调整筛选条件或者清除筛选条件</p>
|
||||
<h3 class="text-2xl font-bold text-gray-700 dark:text-gray-200 mb-2">没有找到符合条件的文章</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 max-w-md mx-auto">请尝试调整筛选条件或者清除筛选条件</p>
|
||||
</div>
|
||||
|
||||
<!-- 筛选功能的客户端脚本 -->
|
||||
|
1
src/pages/table-test.md
Normal file
1
src/pages/table-test.md
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,175 +0,0 @@
|
||||
/* 代码块容器样式 */
|
||||
.prose pre {
|
||||
position: relative;
|
||||
margin: 1.5em 0;
|
||||
padding: 0;
|
||||
border-radius: 0.5rem;
|
||||
/* 调整背景色和边框 */
|
||||
background-color: #282c34 !important;
|
||||
border: 1px solid #374151;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 代码块顶部栏 - 调整颜色使其更突出 */
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* 修改背景色为更深的颜色 */
|
||||
background-color: #21252b;
|
||||
padding: 0.5rem 1rem;
|
||||
/* 调整边框颜色 */
|
||||
border-bottom: 1px solid #374151;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 语言标签 */
|
||||
.code-language {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
/* 调整颜色使其更明显 */
|
||||
color: #d1d5db;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* 复制按钮 - 增强视觉效果 */
|
||||
.code-copy-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 0.35rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
/* 调整按钮颜色 */
|
||||
color: #d1d5db;
|
||||
background-color: #2c313a;
|
||||
border: 1px solid #4b5563;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.code-copy-button svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
stroke: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.code-copy-button span {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.code-copy-button:hover {
|
||||
background-color: #374151;
|
||||
color: #f3f4f6;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
.code-copy-button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 代码内容区域 */
|
||||
.prose pre code {
|
||||
display: block;
|
||||
padding: 1.5em 1.25em;
|
||||
overflow-x: auto;
|
||||
font-family: "JetBrains Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.6;
|
||||
tab-size: 2;
|
||||
/* 调整文字颜色 */
|
||||
color: #abb2bf !important;
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
.prose pre code::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
background-color: #282c34;
|
||||
}
|
||||
|
||||
.prose pre code::-webkit-scrollbar-thumb {
|
||||
background-color: #4b5363;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.prose pre code::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #5a6377;
|
||||
}
|
||||
|
||||
.prose pre code::-webkit-scrollbar-track {
|
||||
background-color: #21252b;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Firefox滚动条 */
|
||||
.prose pre code {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #4b5363 #282c34;
|
||||
}
|
||||
|
||||
/* 代码行高亮 */
|
||||
.prose pre .highlight-line {
|
||||
background-color: rgba(171, 178, 191, 0.1);
|
||||
display: block;
|
||||
margin: 0 -1em;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
/* 行号 */
|
||||
.prose pre code {
|
||||
counter-reset: line;
|
||||
}
|
||||
|
||||
.prose pre code .line {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 内联代码样式 */
|
||||
.prose :not(pre) > code {
|
||||
background-color: rgba(171, 178, 191, 0.1);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25em;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
border: 1px solid rgba(171, 178, 191, 0.2);
|
||||
font-family: "JetBrains Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.85em;
|
||||
color: #e06c75;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
[data-theme="dark"] .prose pre,
|
||||
[data-theme="dark"] .code-header {
|
||||
background-color: #282c34 !important;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .code-copy-button {
|
||||
background-color: #2c313a;
|
||||
border-color: #4b5563;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .code-copy-button:hover {
|
||||
background-color: #374151;
|
||||
color: #f3f4f6;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose pre code {
|
||||
/* 调整暗色模式文字颜色 */
|
||||
color: #abb2bf;
|
||||
}
|
@ -1,186 +1,3 @@
|
||||
/* 增强表格样式 */
|
||||
.prose table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
margin: 1.5em 0;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
border: 2px solid var(--color-secondary-500);
|
||||
}
|
||||
|
||||
/* 确保表格边框在所有浏览器中都能正确显示 */
|
||||
.prose table * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.prose thead {
|
||||
background-color: var(--color-primary-50);
|
||||
}
|
||||
|
||||
.prose thead th {
|
||||
padding: 1rem 1.25rem;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
color: var(--color-primary-900);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5rem;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
border-bottom: 2px solid var(--color-secondary-500);
|
||||
border-right: 2px solid var(--color-secondary-500);
|
||||
}
|
||||
|
||||
.prose thead th:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.prose tbody tr {
|
||||
border-bottom: 2px solid var(--color-secondary-500);
|
||||
}
|
||||
|
||||
.prose tbody tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.prose tbody tr:hover {
|
||||
background-color: var(--color-secondary-50);
|
||||
}
|
||||
|
||||
.prose tbody td {
|
||||
padding: 0.875rem 1.25rem;
|
||||
color: var(--color-secondary-800);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
border-right: 2px solid var(--color-secondary-500);
|
||||
border-bottom: 2px solid var(--color-secondary-500);
|
||||
}
|
||||
|
||||
.prose tbody td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.prose tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.prose tbody tr:nth-child(even) {
|
||||
background-color: var(--color-secondary-50/80);
|
||||
}
|
||||
|
||||
.prose tbody tr:nth-child(even):hover {
|
||||
background-color: var(--color-secondary-50);
|
||||
}
|
||||
|
||||
/* 表格内容对齐样式 */
|
||||
.prose th[align="center"],
|
||||
.prose td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.prose th[align="right"],
|
||||
.prose td[align="right"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.prose th[align="left"],
|
||||
.prose td[align="left"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 表格内容样式增强 */
|
||||
.prose td code {
|
||||
background-color: var(--color-secondary-100);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25em;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose td code {
|
||||
background-color: var(--color-secondary-800);
|
||||
}
|
||||
|
||||
/* 表格内的链接样式 */
|
||||
.prose td a {
|
||||
color: var(--color-primary-600);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--color-primary-300);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.prose td a:hover {
|
||||
color: var(--color-primary-700);
|
||||
border-bottom-color: var(--color-primary-500);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose td a {
|
||||
color: var(--color-primary-400);
|
||||
border-bottom-color: var(--color-primary-700);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose td a:hover {
|
||||
color: var(--color-primary-300);
|
||||
border-bottom-color: var(--color-primary-500);
|
||||
}
|
||||
|
||||
/* 表格内复选框样式 */
|
||||
.prose td input[type="checkbox"] {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
accent-color: var(--color-primary-500);
|
||||
}
|
||||
|
||||
/* 表格内图标样式 */
|
||||
.prose td svg,
|
||||
.prose td img:not(.emoji) {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: 1.25em;
|
||||
margin: 0 0.25em;
|
||||
}
|
||||
|
||||
/* 表格内的状态标记 */
|
||||
.prose td .status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25em 0.75em;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75em;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.prose td .status-success {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: rgb(6, 95, 70);
|
||||
}
|
||||
|
||||
.prose td .status-warning {
|
||||
background-color: rgba(245, 158, 11, 0.1);
|
||||
color: rgb(146, 64, 14);
|
||||
}
|
||||
|
||||
.prose td .status-error {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: rgb(153, 27, 27);
|
||||
}
|
||||
|
||||
.prose td .status-info {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
color: rgb(30, 64, 175);
|
||||
}
|
||||
|
||||
/* 增强列表样式 */
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
@ -432,206 +249,9 @@
|
||||
border-bottom-color: var(--color-primary-400);
|
||||
}
|
||||
|
||||
/* 响应式表格样式 */
|
||||
/* 响应式样式 */
|
||||
@media (max-width: 640px) {
|
||||
.prose table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.prose thead th {
|
||||
white-space: nowrap;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.prose tbody td {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格内容溢出处理 */
|
||||
.prose td p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.prose td > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.prose td > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.prose td.truncate {
|
||||
max-width: 20rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 确保表格边框在所有情况下都能正确显示 */
|
||||
.prose table tr td:last-child {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.prose table tr th:last-child {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.prose table tr:last-child td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 暗色模式下的边框颜色 */
|
||||
[data-theme="dark"] .prose table {
|
||||
--table-border-color: var(--color-dark-border);
|
||||
}
|
||||
|
||||
/* 增强列表样式 */
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
padding-left: 1.625em;
|
||||
}
|
||||
|
||||
.prose ul li {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 0.375em;
|
||||
}
|
||||
|
||||
.prose ul li::marker {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.prose ul li ul {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
padding-left: 1.625em;
|
||||
}
|
||||
|
||||
.prose ol li {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 0.375em;
|
||||
}
|
||||
|
||||
.prose ol li::marker {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* 添加明确的表格边框样式 */
|
||||
.prose table {
|
||||
border: 2px solid var(--color-secondary-500) !important;
|
||||
}
|
||||
|
||||
.prose th {
|
||||
border-right: 2px solid var(--color-secondary-500) !important;
|
||||
border-bottom: 2px solid var(--color-secondary-500) !important;
|
||||
}
|
||||
|
||||
.prose td {
|
||||
border-right: 2px solid var(--color-secondary-500) !important;
|
||||
border-bottom: 2px solid var(--color-secondary-500) !important;
|
||||
}
|
||||
|
||||
.prose th:last-child,
|
||||
.prose td:last-child {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.prose tr:last-child td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose th,
|
||||
[data-theme="dark"] .prose td {
|
||||
border-color: var(--color-dark-border) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose table {
|
||||
border-color: var(--color-dark-border) !important;
|
||||
}
|
||||
|
||||
/* 暗色模式表格样式 */
|
||||
[data-theme="dark"] .prose table {
|
||||
border-color: var(--color-dark-border);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 0.75rem;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose thead {
|
||||
background-color: var(--color-dark-surface);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose thead th {
|
||||
color: var(--color-primary-300);
|
||||
border-bottom: 2px solid var(--color-dark-border);
|
||||
border-right: 2px solid var(--color-dark-border);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose thead th:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody tr {
|
||||
border-bottom: 2px solid var(--color-dark-border);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody td {
|
||||
color: var(--color-secondary-200);
|
||||
border-right: 2px solid var(--color-dark-border);
|
||||
border-bottom: 2px solid var(--color-dark-border);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody tr:hover {
|
||||
background-color: var(--color-dark-surface/60);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody tr:nth-child(even) {
|
||||
background-color: var(--color-dark-surface/40);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose tbody tr:nth-child(even):hover {
|
||||
background-color: var(--color-dark-surface/60);
|
||||
}
|
||||
|
||||
/* 暗色模式下的状态标记样式 */
|
||||
[data-theme="dark"] .prose td .status-success {
|
||||
background-color: rgba(16, 185, 129, 0.2);
|
||||
color: rgb(110, 231, 183);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose td .status-warning {
|
||||
background-color: rgba(245, 158, 11, 0.2);
|
||||
color: rgb(253, 186, 116);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prose td .status-error {
|
||||
background-color: rgba(239, 68, 68, 0.2);
|
||||
color: rgb(252, 165, 165);
|
||||
/* 移除所有表格相关样式 */
|
||||
}
|
||||
|
||||
/* 收纳内容样式 */
|
||||
|
@ -1,8 +1,8 @@
|
||||
@import "tailwindcss";
|
||||
@import "./prism.css";
|
||||
@import "./content-styles.css";
|
||||
@import "./table-styles.css";
|
||||
@import "./emoji.css";
|
||||
@import "./code-blocks.css";
|
||||
@import "./header.css";
|
||||
|
||||
/* 定义深色模式选择器 */
|
||||
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
||||
@ -12,25 +12,6 @@
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* 专门为卡片元素添加过渡属性,修复悬停问题 */
|
||||
.recent-article,
|
||||
[class*="hover:-translate-y"] {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
|
||||
/* 添加transform-style属性以优化渲染 */
|
||||
transform-style: preserve-3d;
|
||||
/* 添加will-change提示浏览器将使用GPU加速 */
|
||||
will-change: transform;
|
||||
/* 确保初始状态是稳定的 */
|
||||
transform: translateY(0);
|
||||
/* 防止鼠标在卡片内移动时触发重新计算 */
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
/* 使用更具体的选择器优先级来控制悬停行为 */
|
||||
.recent-article:hover,
|
||||
[class*="hover:-translate-y"]:hover {
|
||||
transform: translateY(-0.25rem) !important;
|
||||
}
|
||||
|
||||
@theme {
|
||||
/* 主色调 - 使用更现代的蓝紫色 */
|
||||
|
@ -4,14 +4,185 @@
|
||||
#header-bg.scrolled {
|
||||
backdrop-filter: blur(6px);
|
||||
background: rgba(249, 250, 251, 0.8);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.04),
|
||||
0 2px 4px rgba(0, 0, 0, 0.04),
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 2px 4px rgba(0, 0, 0, 0.04),
|
||||
0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 黑暗模式样式 */
|
||||
[data-theme="dark"] #header-bg.scrolled {
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 通用搜索框样式 */
|
||||
.pagefind-ui .pagefind-ui__form {
|
||||
margin: 0;
|
||||
max-width: none;
|
||||
position: relative; /* 确保定位上下文 */
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__search-input {
|
||||
border-radius: 9999px !important;
|
||||
font-size: 0.875rem !important;
|
||||
height: 32px !important;
|
||||
width: 12rem !important;
|
||||
padding-top: 0.25rem !important;
|
||||
padding-bottom: 0.25rem !important;
|
||||
padding-left: 35px !important;
|
||||
padding-right: 47px !important;
|
||||
border-color: var(--color-gray-200) !important;
|
||||
border-width: 1px !important;
|
||||
color: var(--color-gray-700) !important;
|
||||
font-weight: 400 !important;
|
||||
background-color: var(--color-gray-50) !important;
|
||||
}
|
||||
|
||||
/* 移动端搜索框样式 */
|
||||
#mobile-search-panel .pagefind-ui .pagefind-ui__search-input {
|
||||
width: 100% !important;
|
||||
height:35px!important;
|
||||
}
|
||||
|
||||
|
||||
#mobile-search-panel .pagefind-ui__search-input {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input {
|
||||
color: var(--color-gray-200) !important;
|
||||
border-color: var(--color-gray-700) !important;
|
||||
background-color: var(--color-gray-800) !important;
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__search-input::placeholder {
|
||||
color: var(--color-gray-500) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input::placeholder {
|
||||
color: var(--color-gray-400) !important;
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__search-input:hover {
|
||||
border-color: var(--color-primary-300) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input:hover {
|
||||
border-color: var(--color-primary-600) !important;
|
||||
}
|
||||
|
||||
|
||||
.pagefind-ui *:focus-visible {
|
||||
outline:0 transparent !important;
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__search-input:focus {
|
||||
background-color: white !important;
|
||||
opacity: 1 !important;
|
||||
border-width: 2px !important;
|
||||
border-color: var(--color-primary-300) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__search-input:focus {
|
||||
background-color: var(--color-gray-800) !important;
|
||||
border-color: var(--color-primary-600) !important;
|
||||
}
|
||||
|
||||
|
||||
/* 修复搜索图标位置 */
|
||||
.pagefind-ui .pagefind-ui__form::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
left: 12px !important;
|
||||
z-index: 10;
|
||||
background-color: var(--color-gray-400) !important;
|
||||
}
|
||||
|
||||
|
||||
/* 搜索框清除按钮垂直居中 */
|
||||
.pagefind-ui .pagefind-ui__search-clear {
|
||||
transform: translateY(-20%);
|
||||
background: none !important;
|
||||
font-size: 0.8125rem !important;
|
||||
color: var(--color-gray-400) !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 搜索结果抽屉 */
|
||||
.pagefind-ui .pagefind-ui__drawer{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 0.25rem;
|
||||
z-index: 50;
|
||||
padding: 0.75rem;
|
||||
overflow-y: auto;
|
||||
max-height: 70vh;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-gray-200);
|
||||
background-color: var(--color-gray-50);
|
||||
width: 100%; /* 确保抽屉宽度不超过容器 */
|
||||
max-width: 100%; /* 限制最大宽度 */
|
||||
overflow-x: hidden; /* 防止横向滚动 */
|
||||
color:var(--color-gray-500);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__drawer{
|
||||
border: 1px solid var(--color-gray-700);
|
||||
background-color: var(--color-gray-800);
|
||||
color:var(--color-gray-400);
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__results-area {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 搜索结果消息文本 */
|
||||
.pagefind-ui .pagefind-ui__message {
|
||||
font-size: 0.55rem;
|
||||
word-wrap: break-word; /* 允许长消息文本换行 */
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
/* 搜索结果内容 */
|
||||
.pagefind-ui .pagefind-ui__drawer ol,
|
||||
.pagefind-ui .pagefind-ui__drawer div,
|
||||
.pagefind-ui .pagefind-ui__drawer p,
|
||||
.pagefind-ui .pagefind-ui__drawer mark {
|
||||
word-wrap: break-word;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__result {
|
||||
padding: 2px 0px !important;
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__result-link{
|
||||
color:var(--color-gray-800) !important;
|
||||
font-size: 1rem; /* 等于 14px */
|
||||
line-height: 1.5rem; /* 等于 20px */
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__result-link{
|
||||
color:var(--color-gray-200) !important;
|
||||
}
|
||||
|
||||
.pagefind-ui .pagefind-ui__result-title{
|
||||
color:var(--color-gray-600) !important;
|
||||
font-size: 0.875rem; /* 等于 14px */
|
||||
line-height: 1.25rem; /* 等于 20px */
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pagefind-ui .pagefind-ui__result-title{
|
||||
color:var(--color-gray-400) !important;
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+bash+jsx+tsx+typescript */
|
||||
/**
|
||||
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
|
||||
* Based on https://github.com/chriskempson/tomorrow-theme
|
||||
* @author Rose Pritchard
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #abb2bf !important;
|
||||
background: none;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
border: 1px solid #374151;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.5rem;
|
||||
background: #282c34 !important;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #282c34 !important;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #7f848e;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.token.tag,
|
||||
.token.attr-name,
|
||||
.token.namespace,
|
||||
.token.deleted {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.token.function-name {
|
||||
color: #61afef;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.class-name,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: #e5c07b;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.important,
|
||||
.token.atrule,
|
||||
.token.keyword,
|
||||
.token.builtin {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.attr-value,
|
||||
.token.regex,
|
||||
.token.variable {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: green;
|
||||
}
|
||||
|
||||
[data-theme="dark"] code[class*="language-"],
|
||||
[data-theme="dark"] pre[class*="language-"] {
|
||||
color: #abb2bf !important;
|
||||
background: #282c34 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] :not(pre) > code[class*="language-"],
|
||||
[data-theme="dark"] pre[class*="language-"] {
|
||||
background: #282c34 !important;
|
||||
}
|
665
src/styles/table-styles.css
Normal file
665
src/styles/table-styles.css
Normal file
@ -0,0 +1,665 @@
|
||||
/* 表格基础样式 */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5em 0;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 4px 10px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* 表格边框样式 */
|
||||
table, th, td {
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
/* 表头样式 */
|
||||
thead {
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 1rem;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
border-bottom: 2px solid #d6e0ff;
|
||||
color: #3451db;
|
||||
}
|
||||
|
||||
/* 表格单元格样式 */
|
||||
td {
|
||||
padding: 0.875rem 1rem;
|
||||
vertical-align: middle;
|
||||
color: #1e293b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 内容对齐方式 */
|
||||
th[align="center"],
|
||||
td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th[align="right"],
|
||||
td[align="right"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th[align="left"],
|
||||
td[align="left"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 表格行交替颜色 */
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 鼠标悬停效果 */
|
||||
tbody tr:hover {
|
||||
background-color: #f5f7ff;
|
||||
}
|
||||
|
||||
/* 响应式表格 */
|
||||
@media (max-width: 640px) {
|
||||
table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 特定针对于内联样式的表格 */
|
||||
table[style] th,
|
||||
table[style] td {
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
}
|
||||
|
||||
/* 处理嵌套样式的情况 */
|
||||
.prose table {
|
||||
border-collapse: collapse !important;
|
||||
border-radius: 0.5rem !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.prose table th,
|
||||
.prose table td {
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
}
|
||||
|
||||
/* 勾号和叉号样式 - 通用方式 */
|
||||
.tick, .yes, .true {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.cross, .no, .false {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
/* 添加内容 */
|
||||
.tick::before, .yes::before, .true::before {
|
||||
content: "✓";
|
||||
}
|
||||
|
||||
.cross::before, .no::before, .false::before {
|
||||
content: "✗";
|
||||
}
|
||||
|
||||
/* 直接使用✓或✗的样式 */
|
||||
.check-mark {
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cross-mark {
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 状态标识符号样式 */
|
||||
td:has(> :is(svg[aria-label="yes"], .yes, .tick, .true)) {
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
td:has(> :is(svg[aria-label="no"], .no, .cross, .false)) {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
/* 对表格中直接使用的✓和✗符号进行样式处理 */
|
||||
td:only-child {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 检查整个表格的样式 */
|
||||
table.feature-comparison td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.feature-comparison td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 添加全局样式识别 */
|
||||
.text-success,
|
||||
.text-green,
|
||||
.success-mark {
|
||||
color: #059669 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-danger,
|
||||
.text-red,
|
||||
.danger-mark {
|
||||
color: #dc2626 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 直接针对表格中的✓和✗符号 - 移除不兼容的:contains选择器 */
|
||||
/* 改用更通用的选择器和类 */
|
||||
|
||||
/* 针对行内样式的表格 */
|
||||
table[style] td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 使用属性选择器 */
|
||||
td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 为常见的功能对比表格添加专用样式 */
|
||||
.feature-table th,
|
||||
.function-table th,
|
||||
.comparison-table th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-table th:first-child,
|
||||
.function-table th:first-child,
|
||||
.comparison-table th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.feature-table td,
|
||||
.function-table td,
|
||||
.comparison-table td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-table td:first-child,
|
||||
.function-table td:first-child,
|
||||
.comparison-table td:first-child {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 直接样式化 ✓ 和 ✗ 字符 */
|
||||
.checkmark, .check, .✓ {
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.crossmark, .cross-symbol, .✗ {
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
/* 暗色模式 - 只使用 [data-theme="dark"] 选择器 */
|
||||
[data-theme="dark"] {
|
||||
table {
|
||||
box-shadow: 0 4px 12px -1px rgba(0, 0, 0, 0.3), 0 2px 6px -1px rgba(0, 0, 0, 0.2);
|
||||
border-color: #475569;
|
||||
background-color: #0f172a;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
table[style] th,
|
||||
table[style] td,
|
||||
.prose table th,
|
||||
.prose table td {
|
||||
border-color: #475569 !important;
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: #1a2e6a;
|
||||
border-bottom: 2px solid #4b6bff;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom-color: #4b6bff;
|
||||
color: #adc2ff;
|
||||
background-color: transparent;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
th:not(:last-child) {
|
||||
border-right-color: #374151;
|
||||
}
|
||||
|
||||
td {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
td:not(:last-child) {
|
||||
border-right-color: #374151;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #1e293b;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #111827;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #272f45;
|
||||
box-shadow: inset 0 0 0 2px rgba(75, 107, 255, 0.3);
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 表格圆角样式在暗色模式下的处理 */
|
||||
table {
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 勾号和叉号颜色调整 */
|
||||
.tick, .yes, .true, .check-mark {
|
||||
color: #10b981;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.cross, .no, .false, .cross-mark {
|
||||
color: #ef4444;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.text-success,
|
||||
.text-green,
|
||||
.success-mark {
|
||||
color: #10b981 !important;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.text-danger,
|
||||
.text-red,
|
||||
.danger-mark {
|
||||
color: #ef4444 !important;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.checkmark, .check, .✓ {
|
||||
color: #10b981;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.crossmark, .cross-symbol, .✗ {
|
||||
color: #ef4444;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
}/* 表格基础样式 */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5em 0;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 4px 10px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* 表格边框样式 */
|
||||
table, th, td {
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
/* 表头样式 */
|
||||
thead {
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 1rem;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
border-bottom: 2px solid #d6e0ff;
|
||||
color: #3451db;
|
||||
}
|
||||
|
||||
/* 表格单元格样式 */
|
||||
td {
|
||||
padding: 0.875rem 1rem;
|
||||
vertical-align: middle;
|
||||
color: #1e293b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 内容对齐方式 */
|
||||
th[align="center"],
|
||||
td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th[align="right"],
|
||||
td[align="right"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th[align="left"],
|
||||
td[align="left"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 表格行交替颜色 */
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 鼠标悬停效果 */
|
||||
tbody tr:hover {
|
||||
background-color: #f5f7ff;
|
||||
}
|
||||
|
||||
/* 响应式表格 */
|
||||
@media (max-width: 640px) {
|
||||
table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 特定针对于内联样式的表格 */
|
||||
table[style] th,
|
||||
table[style] td {
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
}
|
||||
|
||||
/* 处理嵌套样式的情况 */
|
||||
.prose table {
|
||||
border-collapse: collapse !important;
|
||||
border-radius: 0.5rem !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.prose table th,
|
||||
.prose table td {
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
}
|
||||
|
||||
/* 勾号和叉号样式 - 通用方式 */
|
||||
.tick, .yes, .true {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.cross, .no, .false {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
/* 添加内容 */
|
||||
.tick::before, .yes::before, .true::before {
|
||||
content: "✓";
|
||||
}
|
||||
|
||||
.cross::before, .no::before, .false::before {
|
||||
content: "✗";
|
||||
}
|
||||
|
||||
/* 直接使用✓或✗的样式 */
|
||||
.check-mark {
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cross-mark {
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 状态标识符号样式 */
|
||||
td:has(> :is(svg[aria-label="yes"], .yes, .tick, .true)) {
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
td:has(> :is(svg[aria-label="no"], .no, .cross, .false)) {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
/* 对表格中直接使用的✓和✗符号进行样式处理 */
|
||||
td:only-child {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 检查整个表格的样式 */
|
||||
table.feature-comparison td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.feature-comparison td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 添加全局样式识别 */
|
||||
.text-success,
|
||||
.text-green,
|
||||
.success-mark {
|
||||
color: #059669 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-danger,
|
||||
.text-red,
|
||||
.danger-mark {
|
||||
color: #dc2626 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 直接针对表格中的✓和✗符号 - 移除不兼容的:contains选择器 */
|
||||
/* 改用更通用的选择器和类 */
|
||||
|
||||
/* 针对行内样式的表格 */
|
||||
table[style] td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 使用属性选择器 */
|
||||
td[align="center"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 为常见的功能对比表格添加专用样式 */
|
||||
.feature-table th,
|
||||
.function-table th,
|
||||
.comparison-table th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-table th:first-child,
|
||||
.function-table th:first-child,
|
||||
.comparison-table th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.feature-table td,
|
||||
.function-table td,
|
||||
.comparison-table td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-table td:first-child,
|
||||
.function-table td:first-child,
|
||||
.comparison-table td:first-child {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 直接样式化 ✓ 和 ✗ 字符 */
|
||||
.checkmark, .check, .✓ {
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.crossmark, .cross-symbol, .✗ {
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
/* 暗色模式 - 只使用 [data-theme="dark"] 选择器 */
|
||||
[data-theme="dark"] {
|
||||
table {
|
||||
box-shadow: 0 4px 12px -1px rgba(0, 0, 0, 0.3), 0 2px 6px -1px rgba(0, 0, 0, 0.2);
|
||||
border-color: #475569;
|
||||
background-color: #0f172a;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
table[style] th,
|
||||
table[style] td,
|
||||
.prose table th,
|
||||
.prose table td {
|
||||
border-color: #475569 !important;
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: #1e293b;
|
||||
border-bottom: 2px solid #4b6bff;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom-color: #4b6bff;
|
||||
color: #adc2ff;
|
||||
background-color: transparent;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
th:not(:last-child) {
|
||||
border-right-color: #374151;
|
||||
}
|
||||
|
||||
td {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
td:not(:last-child) {
|
||||
border-right-color: #374151;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #1e293b;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #111827;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #272f45;
|
||||
box-shadow: inset 0 0 0 2px rgba(75, 107, 255, 0.3);
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 表格圆角样式在暗色模式下的处理 */
|
||||
table {
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 勾号和叉号颜色调整 */
|
||||
.tick, .yes, .true, .check-mark {
|
||||
color: #10b981;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.cross, .no, .false, .cross-mark {
|
||||
color: #ef4444;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.text-success,
|
||||
.text-green,
|
||||
.success-mark {
|
||||
color: #10b981 !important;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.text-danger,
|
||||
.text-red,
|
||||
.danger-mark {
|
||||
color: #ef4444 !important;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.checkmark, .check, .✓ {
|
||||
color: #10b981;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.crossmark, .cross-symbol, .✗ {
|
||||
color: #ef4444;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user