将代码高亮改为astro集成配置,mermaid改为客户端生成,优化导航栏样式,将地图数据动态导入
This commit is contained in:
parent
af0dee8d1d
commit
d3e0eddff7
@ -14,7 +14,6 @@ import { SITE_URL } from "./src/consts";
|
|||||||
import compressor from "astro-compressor";
|
import compressor from "astro-compressor";
|
||||||
import vercel from "@astrojs/vercel";
|
import vercel from "@astrojs/vercel";
|
||||||
import { articleIndexerIntegration } from "./src/plugins/build-article-index.js";
|
import { articleIndexerIntegration } from "./src/plugins/build-article-index.js";
|
||||||
import customCodeBlocksIntegration from "./src/plugins/custom-code-blocks.js";
|
|
||||||
|
|
||||||
function getArticleDate(articleId) {
|
function getArticleDate(articleId) {
|
||||||
try {
|
try {
|
||||||
@ -52,21 +51,15 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
integrations: [
|
integrations: [
|
||||||
// 使用我们自己的代码块集成替代expressiveCode
|
// 使用Astro官方的MDX支持
|
||||||
customCodeBlocksIntegration(),
|
|
||||||
|
|
||||||
// MDX 集成配置
|
|
||||||
mdx(),
|
mdx(),
|
||||||
swup({
|
swup({
|
||||||
cache: true,
|
cache: true,
|
||||||
preload: true,
|
preload: true,
|
||||||
}
|
}),
|
||||||
|
|
||||||
),
|
|
||||||
react(),
|
react(),
|
||||||
// 使用我们自己的文章索引生成器(替换pagefind)
|
// 使用文章索引生成器
|
||||||
articleIndexerIntegration(),
|
articleIndexerIntegration(),
|
||||||
|
|
||||||
sitemap({
|
sitemap({
|
||||||
filter: (page) => !page.includes("/api/"),
|
filter: (page) => !page.includes("/api/"),
|
||||||
serialize(item) {
|
serialize(item) {
|
||||||
@ -109,9 +102,24 @@ export default defineConfig({
|
|||||||
compressor()
|
compressor()
|
||||||
],
|
],
|
||||||
|
|
||||||
// Markdown 配置
|
// Markdown 配置 - 使用官方语法高亮
|
||||||
markdown: {
|
markdown: {
|
||||||
syntaxHighlight: false, // 禁用默认的语法高亮,使用我们自定义的高亮
|
// 配置语法高亮
|
||||||
|
syntaxHighlight: {
|
||||||
|
// 使用shiki作为高亮器
|
||||||
|
type: 'shiki',
|
||||||
|
// 排除mermaid语言,不进行高亮处理
|
||||||
|
excludeLangs: ['mermaid']
|
||||||
|
},
|
||||||
|
// Shiki主题配置
|
||||||
|
shikiConfig: {
|
||||||
|
theme: 'github-light',
|
||||||
|
themes: {
|
||||||
|
light: 'github-light',
|
||||||
|
dark: 'github-dark'
|
||||||
|
},
|
||||||
|
wrap: true
|
||||||
|
},
|
||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
[remarkEmoji, { emoticon: false, padded: true }]
|
[remarkEmoji, { emoticon: false, padded: true }]
|
||||||
],
|
],
|
||||||
@ -119,13 +127,6 @@ export default defineConfig({
|
|||||||
[rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }]
|
[rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] }]
|
||||||
],
|
],
|
||||||
gfm: true,
|
gfm: true,
|
||||||
// 设置 remark-rehype 选项,以控制HTML处理
|
|
||||||
remarkRehype: {
|
|
||||||
// 保留原始HTML格式,但仅在非代码块区域
|
|
||||||
allowDangerousHtml: true,
|
|
||||||
// 确保代码块内容不被解析
|
|
||||||
passThrough: ['code']
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
adapter: vercel(),
|
adapter: vercel(),
|
||||||
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -25,7 +25,7 @@
|
|||||||
"astro": "^5.7.5",
|
"astro": "^5.7.5",
|
||||||
"astro-expressive-code": "^0.41.2",
|
"astro-expressive-code": "^0.41.2",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"highlight.js": "^11.11.1",
|
"mermaid": "^11.6.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"octokit": "^4.1.3",
|
"octokit": "^4.1.3",
|
||||||
"puppeteer": "^23.11.1",
|
"puppeteer": "^23.11.1",
|
||||||
@ -9955,15 +9955,6 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/highlight.js": {
|
|
||||||
"version": "11.11.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz",
|
|
||||||
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-3.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"astro": "^5.7.5",
|
"astro": "^5.7.5",
|
||||||
"astro-expressive-code": "^0.41.2",
|
"astro-expressive-code": "^0.41.2",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"highlight.js": "^11.11.1",
|
"mermaid": "^11.6.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"octokit": "^4.1.3",
|
"octokit": "^4.1.3",
|
||||||
"puppeteer": "^23.11.1",
|
"puppeteer": "^23.11.1",
|
||||||
|
@ -29,7 +29,7 @@ const breadcrumbs: Breadcrumb[] = pathSegments
|
|||||||
});
|
});
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex items-center justify-between w-full flex-wrap sm:flex-nowrap" transition:persist transition:name="breadcrumb">
|
<div class="flex items-center justify-between w-full flex-wrap sm:flex-nowrap">
|
||||||
<div class="flex items-center text-sm overflow-hidden">
|
<div class="flex items-center text-sm overflow-hidden">
|
||||||
<!-- 文章列表链接 - 根据当前页面类型决定链接 -->
|
<!-- 文章列表链接 - 根据当前页面类型决定链接 -->
|
||||||
<a href={'/articles/'} class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center flex-shrink-0">
|
<a href={'/articles/'} class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center flex-shrink-0">
|
||||||
|
@ -57,7 +57,7 @@ let secondaryHighlightStyle = activeSubItem ? "opacity: 1;" : "opacity: 0;";
|
|||||||
const activeClass = "font-medium";
|
const activeClass = "font-medium";
|
||||||
const itemClass = "px-4 py-2 text-sm font-medium";
|
const itemClass = "px-4 py-2 text-sm font-medium";
|
||||||
const primaryHighlightClass = "bg-primary-100 dark:bg-primary-800/30 rounded-xl shadow-md backdrop-blur-sm";
|
const primaryHighlightClass = "bg-primary-100 dark:bg-primary-800/30 rounded-xl shadow-md backdrop-blur-sm";
|
||||||
const secondaryHighlightClass = "bg-primary-300/80 dark:bg-primary-700/60 rounded-md shadow-md backdrop-blur-sm";
|
const secondaryHighlightClass = "bg-primary-300/80 dark:bg-primary-700/60 rounded-xl shadow-md backdrop-blur-sm";
|
||||||
const transitionDuration = 300;
|
const transitionDuration = 300;
|
||||||
const navSelectorClassName = "mr-4";
|
const navSelectorClassName = "mr-4";
|
||||||
---
|
---
|
||||||
@ -98,7 +98,7 @@ const navSelectorClassName = "mr-4";
|
|||||||
class={`absolute z-0 hover:shadow-lg rounded-xl ${primaryHighlightClass}`}
|
class={`absolute z-0 hover:shadow-lg rounded-xl ${primaryHighlightClass}`}
|
||||||
style={primaryHighlightStyle}></div>
|
style={primaryHighlightStyle}></div>
|
||||||
<div id="nav-secondary-highlight"
|
<div id="nav-secondary-highlight"
|
||||||
class={`absolute z-10 hover:shadow-lg rounded-md ${secondaryHighlightClass}`}
|
class={`absolute z-10 hover:shadow-lg rounded-xl ${secondaryHighlightClass}`}
|
||||||
style={secondaryHighlightStyle}></div>
|
style={secondaryHighlightStyle}></div>
|
||||||
|
|
||||||
<!-- 导航菜单项 -->
|
<!-- 导航菜单项 -->
|
||||||
@ -130,7 +130,7 @@ const navSelectorClassName = "mr-4";
|
|||||||
|
|
||||||
<!-- 二级菜单容器 -->
|
<!-- 二级菜单容器 -->
|
||||||
<div class={`nav-group-items relative min-w-full ${activeGroupId === item.id ? 'menu-visible' : 'hidden'}`}>
|
<div class={`nav-group-items relative min-w-full ${activeGroupId === item.id ? 'menu-visible' : 'hidden'}`}>
|
||||||
<div class="flex flex-row flex-wrap gap-2 justify-center items-center">
|
<div class="flex flex-row flex-wrap justify-center items-center">
|
||||||
{item.items.map((subItem) => (
|
{item.items.map((subItem) => (
|
||||||
<a
|
<a
|
||||||
href={subItem.href}
|
href={subItem.href}
|
||||||
|
@ -165,8 +165,6 @@ const {
|
|||||||
|
|
||||||
// 立即设置文档主题,在DOM渲染前应用,避免闪烁
|
// 立即设置文档主题,在DOM渲染前应用,避免闪烁
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
// 确保同步classList,提高兼容性
|
|
||||||
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
||||||
|
|
||||||
// 监听系统主题变化(只有当主题设为跟随系统时才响应)
|
// 监听系统主题变化(只有当主题设为跟随系统时才响应)
|
||||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
@ -176,7 +174,6 @@ const {
|
|||||||
if (!localStorage.getItem("theme")) {
|
if (!localStorage.getItem("theme")) {
|
||||||
const newTheme = e.matches ? "dark" : "light";
|
const newTheme = e.matches ? "dark" : "light";
|
||||||
document.documentElement.dataset.theme = newTheme;
|
document.documentElement.dataset.theme = newTheme;
|
||||||
document.documentElement.classList.toggle('dark', e.matches);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -191,11 +188,9 @@ const {
|
|||||||
// 重新初始化主题
|
// 重新初始化主题
|
||||||
if (storedTheme) {
|
if (storedTheme) {
|
||||||
document.documentElement.dataset.theme = storedTheme;
|
document.documentElement.dataset.theme = storedTheme;
|
||||||
document.documentElement.classList.toggle('dark', storedTheme === 'dark');
|
|
||||||
} else {
|
} else {
|
||||||
const systemTheme = getSystemTheme();
|
const systemTheme = getSystemTheme();
|
||||||
document.documentElement.dataset.theme = systemTheme;
|
document.documentElement.dataset.theme = systemTheme;
|
||||||
document.documentElement.classList.toggle('dark', systemTheme === 'dark');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +201,6 @@ const {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 出错时应用默认浅色主题,确保页面正常显示
|
// 出错时应用默认浅色主题,确保页面正常显示
|
||||||
document.documentElement.dataset.theme = "light";
|
document.documentElement.dataset.theme = "light";
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
@ -454,7 +454,7 @@ const {
|
|||||||
callback();
|
callback();
|
||||||
|
|
||||||
// 确保DOM已更新
|
// 确保DOM已更新
|
||||||
document.documentElement.classList.toggle('dark', toTheme === 'dark');
|
document.documentElement.dataset.theme = toTheme;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成动画需要的SVG资源
|
// 生成动画需要的SVG资源
|
||||||
@ -632,10 +632,6 @@ const {
|
|||||||
} else {
|
} else {
|
||||||
document.documentElement.dataset.theme = "light";
|
document.documentElement.dataset.theme = "light";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保同步类名
|
|
||||||
document.documentElement.classList.toggle('dark',
|
|
||||||
document.documentElement.dataset.theme === 'dark');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换主题
|
// 切换主题
|
||||||
@ -714,7 +710,6 @@ const {
|
|||||||
if (!localStorage.getItem("theme")) {
|
if (!localStorage.getItem("theme")) {
|
||||||
const newTheme = e.matches ? "dark" : "light";
|
const newTheme = e.matches ? "dark" : "light";
|
||||||
document.documentElement.dataset.theme = newTheme;
|
document.documentElement.dataset.theme = newTheme;
|
||||||
document.documentElement.classList.toggle('dark', e.matches);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,29 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import * as THREE from "three";
|
import {
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
Scene,
|
||||||
import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";
|
PerspectiveCamera,
|
||||||
|
WebGLRenderer,
|
||||||
|
SphereGeometry,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
Mesh,
|
||||||
|
AmbientLight,
|
||||||
|
DirectionalLight,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
Raycaster,
|
||||||
|
Group,
|
||||||
|
BufferGeometry,
|
||||||
|
LineBasicMaterial,
|
||||||
|
Line,
|
||||||
|
FrontSide
|
||||||
|
} from "three";
|
||||||
|
import type { Side } from "three";
|
||||||
|
|
||||||
|
// 需要懒加载的模块
|
||||||
|
const loadControlsAndRenderers = () => Promise.all([
|
||||||
|
import("three/examples/jsm/controls/OrbitControls.js"),
|
||||||
|
import("three/examples/jsm/renderers/CSS2DRenderer.js")
|
||||||
|
]);
|
||||||
|
|
||||||
// WASM模块接口
|
// WASM模块接口
|
||||||
interface GeoWasmModule {
|
interface GeoWasmModule {
|
||||||
@ -45,25 +67,26 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
const [mapLoading, setMapLoading] = useState(true);
|
const [mapLoading, setMapLoading] = useState(true);
|
||||||
const [mapError, setMapError] = useState<string | null>(null);
|
const [mapError, setMapError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// 修改场景引用类型以适应新的导入方式
|
||||||
const sceneRef = useRef<{
|
const sceneRef = useRef<{
|
||||||
scene: THREE.Scene;
|
scene: Scene;
|
||||||
camera: THREE.PerspectiveCamera;
|
camera: PerspectiveCamera;
|
||||||
renderer: THREE.WebGLRenderer;
|
renderer: WebGLRenderer;
|
||||||
labelRenderer: CSS2DRenderer;
|
labelRenderer: any; // 后面会动态设置具体类型
|
||||||
controls: OrbitControls;
|
controls: any; // 后面会动态设置具体类型
|
||||||
earth: THREE.Mesh;
|
earth: Mesh;
|
||||||
countries: Map<string, THREE.Object3D>;
|
countries: Map<string, Group>;
|
||||||
raycaster: THREE.Raycaster;
|
raycaster: Raycaster;
|
||||||
mouse: THREE.Vector2;
|
mouse: Vector2;
|
||||||
animationId: number | null;
|
animationId: number | null;
|
||||||
lastCameraPosition: THREE.Vector3 | null;
|
lastCameraPosition: Vector3 | null;
|
||||||
lastMouseEvent: MouseEvent | null;
|
lastMouseEvent: MouseEvent | null;
|
||||||
lastClickedCountry: string | null;
|
lastClickedCountry: string | null;
|
||||||
lastMouseX: number | null;
|
lastMouseX: number | null;
|
||||||
lastMouseY: number | null;
|
lastMouseY: number | null;
|
||||||
lastHoverTime: number | null;
|
lastHoverTime: number | null;
|
||||||
lineToCountryMap: Map<THREE.Line, string>;
|
lineToCountryMap: Map<Line, string>;
|
||||||
allLineObjects: THREE.Line[];
|
allLineObjects: Line[];
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
// 监听主题变化
|
// 监听主题变化
|
||||||
@ -104,22 +127,29 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 动态加载地图数据
|
// 从公共目录加载地图数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadMapData = async () => {
|
const loadMapData = async () => {
|
||||||
try {
|
try {
|
||||||
setMapLoading(true);
|
setMapLoading(true);
|
||||||
setMapError(null);
|
setMapError(null);
|
||||||
|
|
||||||
// 并行加载两个地图数据
|
// 从公共目录加载地图数据
|
||||||
const [worldDataModule, chinaDataModule] = await Promise.all([
|
const [worldDataResponse, chinaDataResponse] = await Promise.all([
|
||||||
import("@/assets/map/world.zh.json"),
|
fetch('/maps/world.zh.json'),
|
||||||
import("@/assets/map/china.json")
|
fetch('/maps/china.json')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!worldDataResponse.ok || !chinaDataResponse.ok) {
|
||||||
|
throw new Error('无法获取地图数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
const worldData = await worldDataResponse.json();
|
||||||
|
const chinaData = await chinaDataResponse.json();
|
||||||
|
|
||||||
setMapData({
|
setMapData({
|
||||||
worldData: worldDataModule.default || worldDataModule,
|
worldData,
|
||||||
chinaData: chinaDataModule.default || chinaDataModule
|
chinaData
|
||||||
});
|
});
|
||||||
|
|
||||||
setMapLoading(false);
|
setMapLoading(false);
|
||||||
@ -196,6 +226,19 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
containerRef.current.innerHTML = "";
|
containerRef.current.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建一个引用,用于清理函数
|
||||||
|
let mounted = true;
|
||||||
|
let cleanupFunctions: Array<() => void> = [];
|
||||||
|
|
||||||
|
// 动态加载Three.js控制器和渲染器
|
||||||
|
const initThreeScene = async () => {
|
||||||
|
try {
|
||||||
|
// 加载OrbitControls和CSS2DRenderer
|
||||||
|
const [{ OrbitControls }, { CSS2DRenderer }] = await loadControlsAndRenderers();
|
||||||
|
|
||||||
|
// 如果组件已卸载,退出初始化
|
||||||
|
if (!mounted || !containerRef.current) return;
|
||||||
|
|
||||||
// 检查当前是否为暗色模式
|
// 检查当前是否为暗色模式
|
||||||
const isDarkMode =
|
const isDarkMode =
|
||||||
document.documentElement.classList.contains("dark") ||
|
document.documentElement.classList.contains("dark") ||
|
||||||
@ -217,16 +260,16 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
const colors = getColors();
|
const colors = getColors();
|
||||||
|
|
||||||
// 创建场景
|
// 创建场景
|
||||||
const scene = new THREE.Scene();
|
const scene = new Scene();
|
||||||
scene.background = null;
|
scene.background = null;
|
||||||
|
|
||||||
// 创建材质的辅助函数
|
// 创建材质的辅助函数
|
||||||
const createMaterial = (
|
const createMaterial = (
|
||||||
color: string,
|
color: string,
|
||||||
side: THREE.Side = THREE.FrontSide,
|
side: Side = FrontSide,
|
||||||
opacity: number = 1.0,
|
opacity: number = 1.0,
|
||||||
) => {
|
) => {
|
||||||
return new THREE.MeshBasicMaterial({
|
return new MeshBasicMaterial({
|
||||||
color: color,
|
color: color,
|
||||||
side: side,
|
side: side,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
@ -235,24 +278,24 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 创建地球几何体
|
// 创建地球几何体
|
||||||
const earthGeometry = new THREE.SphereGeometry(2.0, 64, 64);
|
const earthGeometry = new SphereGeometry(2.0, 64, 64);
|
||||||
const earthMaterial = createMaterial(
|
const earthMaterial = createMaterial(
|
||||||
colors.earthBase,
|
colors.earthBase,
|
||||||
THREE.FrontSide,
|
FrontSide,
|
||||||
isDarkMode ? 0.9 : 0.9, // 调整明亮模式下的不透明度
|
isDarkMode ? 0.9 : 0.9, // 调整明亮模式下的不透明度
|
||||||
);
|
);
|
||||||
const earth = new THREE.Mesh(earthGeometry, earthMaterial);
|
const earth = new Mesh(earthGeometry, earthMaterial);
|
||||||
earth.renderOrder = 1;
|
earth.renderOrder = 1;
|
||||||
scene.add(earth);
|
scene.add(earth);
|
||||||
|
|
||||||
// 添加光源
|
// 添加光源
|
||||||
const ambientLight = new THREE.AmbientLight(
|
const ambientLight = new AmbientLight(
|
||||||
0xffffff,
|
0xffffff,
|
||||||
isDarkMode ? 0.7 : 0.85, // 微调明亮模式下的光照强度
|
isDarkMode ? 0.7 : 0.85, // 微调明亮模式下的光照强度
|
||||||
);
|
);
|
||||||
scene.add(ambientLight);
|
scene.add(ambientLight);
|
||||||
|
|
||||||
const directionalLight = new THREE.DirectionalLight(
|
const directionalLight = new DirectionalLight(
|
||||||
isDarkMode ? 0xeeeeff : 0xffffff, // 恢复明亮模式下的纯白光源
|
isDarkMode ? 0xeeeeff : 0xffffff, // 恢复明亮模式下的纯白光源
|
||||||
isDarkMode ? 0.6 : 0.65, // 微调明亮模式下的定向光强度
|
isDarkMode ? 0.6 : 0.65, // 微调明亮模式下的定向光强度
|
||||||
);
|
);
|
||||||
@ -260,7 +303,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
scene.add(directionalLight);
|
scene.add(directionalLight);
|
||||||
|
|
||||||
// 创建相机
|
// 创建相机
|
||||||
const camera = new THREE.PerspectiveCamera(
|
const camera = new PerspectiveCamera(
|
||||||
45,
|
45,
|
||||||
containerRef.current.clientWidth / containerRef.current.clientHeight,
|
containerRef.current.clientWidth / containerRef.current.clientHeight,
|
||||||
0.1,
|
0.1,
|
||||||
@ -269,7 +312,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
camera.position.z = 8;
|
camera.position.z = 8;
|
||||||
|
|
||||||
// 创建渲染器
|
// 创建渲染器
|
||||||
const renderer = new THREE.WebGLRenderer({
|
const renderer = new WebGLRenderer({
|
||||||
antialias: true,
|
antialias: true,
|
||||||
alpha: true,
|
alpha: true,
|
||||||
logarithmicDepthBuffer: true,
|
logarithmicDepthBuffer: true,
|
||||||
@ -317,15 +360,15 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 保存所有线条对象的引用,用于快速检测
|
// 保存所有线条对象的引用,用于快速检测
|
||||||
const allLineObjects: THREE.Line[] = [];
|
const allLineObjects: Line[] = [];
|
||||||
const lineToCountryMap = new Map<THREE.Line, string>();
|
const lineToCountryMap = new Map<Line, string>();
|
||||||
|
|
||||||
// 创建国家边界组
|
// 创建国家边界组
|
||||||
const countryGroup = new THREE.Group();
|
const countryGroup = new Group();
|
||||||
earth.add(countryGroup);
|
earth.add(countryGroup);
|
||||||
|
|
||||||
// 创建国家边界
|
// 创建国家边界
|
||||||
const countries = new Map<string, THREE.Object3D>();
|
const countries = new Map<string, Group>();
|
||||||
|
|
||||||
// 从WASM获取边界线数据
|
// 从WASM获取边界线数据
|
||||||
const boundaryLines = geoProcessor.get_boundary_lines();
|
const boundaryLines = geoProcessor.get_boundary_lines();
|
||||||
@ -337,15 +380,15 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
const { points, region_name, is_visited } = boundaryLine;
|
const { points, region_name, is_visited } = boundaryLine;
|
||||||
|
|
||||||
// 创建区域组
|
// 创建区域组
|
||||||
const regionObject = new THREE.Group();
|
const regionObject = new Group();
|
||||||
regionObject.userData = { name: region_name, isVisited: is_visited };
|
regionObject.userData = { name: region_name, isVisited: is_visited };
|
||||||
|
|
||||||
// 转换点数组为THREE.Vector3数组
|
// 转换点数组为Vector3数组
|
||||||
const threePoints = points.map((p: { x: number; y: number; z: number }) => new THREE.Vector3(p.x, p.y, p.z));
|
const threePoints = points.map((p: { x: number; y: number; z: number }) => new Vector3(p.x, p.y, p.z));
|
||||||
|
|
||||||
// 创建边界线
|
// 创建边界线
|
||||||
if (threePoints.length > 1) {
|
if (threePoints.length > 1) {
|
||||||
const lineGeometry = new THREE.BufferGeometry().setFromPoints(threePoints);
|
const lineGeometry = new BufferGeometry().setFromPoints(threePoints);
|
||||||
|
|
||||||
// 确定线条颜色
|
// 确定线条颜色
|
||||||
const isChina = region_name === "中国" || region_name.startsWith("中国-");
|
const isChina = region_name === "中国" || region_name.startsWith("中国-");
|
||||||
@ -362,14 +405,14 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
borderColor = colors.border;
|
borderColor = colors.border;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineMaterial = new THREE.LineBasicMaterial({
|
const lineMaterial = new LineBasicMaterial({
|
||||||
color: borderColor,
|
color: borderColor,
|
||||||
linewidth: is_visited ? 1.8 : 1.2, // 微调线条宽度,保持已访问区域更明显
|
linewidth: is_visited ? 1.8 : 1.2, // 微调线条宽度,保持已访问区域更明显
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: is_visited ? 0.95 : 0.85, // 调整不透明度,使边界明显但不突兀
|
opacity: is_visited ? 0.95 : 0.85, // 调整不透明度,使边界明显但不突兀
|
||||||
});
|
});
|
||||||
|
|
||||||
const line = new THREE.Line(lineGeometry, lineMaterial);
|
const line = new Line(lineGeometry, lineMaterial);
|
||||||
line.userData = {
|
line.userData = {
|
||||||
name: region_name,
|
name: region_name,
|
||||||
isVisited: is_visited,
|
isVisited: is_visited,
|
||||||
@ -402,10 +445,10 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
let fixedPosition;
|
let fixedPosition;
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
// 小屏幕显示距离更远,以便看到更多地球
|
// 小屏幕显示距离更远,以便看到更多地球
|
||||||
fixedPosition = new THREE.Vector3(-2.1, 3.41, -8.0);
|
fixedPosition = new Vector3(-2.1, 3.41, -8.0);
|
||||||
} else {
|
} else {
|
||||||
// 大屏幕使用原来的位置
|
// 大屏幕使用原来的位置
|
||||||
fixedPosition = new THREE.Vector3(-2.1, 3.41, -6.5);
|
fixedPosition = new Vector3(-2.1, 3.41, -6.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用位置
|
// 应用位置
|
||||||
@ -425,8 +468,8 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
positionCameraToFaceChina();
|
positionCameraToFaceChina();
|
||||||
|
|
||||||
// 创建射线投射器用于鼠标交互
|
// 创建射线投射器用于鼠标交互
|
||||||
const raycaster = new THREE.Raycaster();
|
const raycaster = new Raycaster();
|
||||||
const mouse = new THREE.Vector2();
|
const mouse = new Vector2();
|
||||||
|
|
||||||
// 添加节流函数,限制鼠标移动事件的触发频率
|
// 添加节流函数,限制鼠标移动事件的触发频率
|
||||||
const throttle = (func: Function, limit: number) => {
|
const throttle = (func: Function, limit: number) => {
|
||||||
@ -474,7 +517,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
|
|
||||||
// 重置所有线条颜色
|
// 重置所有线条颜色
|
||||||
allLineObjects.forEach((line) => {
|
allLineObjects.forEach((line) => {
|
||||||
if (line.material instanceof THREE.LineBasicMaterial) {
|
if (line.material instanceof LineBasicMaterial) {
|
||||||
line.material.color.set(line.userData.originalColor);
|
line.material.color.set(line.userData.originalColor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -485,7 +528,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
allLineObjects.forEach((line) => {
|
allLineObjects.forEach((line) => {
|
||||||
if (
|
if (
|
||||||
lineToCountryMap.get(line) === result.countryName &&
|
lineToCountryMap.get(line) === result.countryName &&
|
||||||
line.material instanceof THREE.LineBasicMaterial
|
line.material instanceof LineBasicMaterial
|
||||||
) {
|
) {
|
||||||
line.material.color.set(line.userData.highlightColor);
|
line.material.color.set(line.userData.highlightColor);
|
||||||
}
|
}
|
||||||
@ -515,7 +558,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
const clearSelection = () => {
|
const clearSelection = () => {
|
||||||
// 恢复所有线条的原始颜色
|
// 恢复所有线条的原始颜色
|
||||||
allLineObjects.forEach((line) => {
|
allLineObjects.forEach((line) => {
|
||||||
if (line.material instanceof THREE.LineBasicMaterial) {
|
if (line.material instanceof LineBasicMaterial) {
|
||||||
line.material.color.set(line.userData.originalColor);
|
line.material.color.set(line.userData.originalColor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -547,7 +590,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
if (result && result.countryName) {
|
if (result && result.countryName) {
|
||||||
// 重置所有线条颜色
|
// 重置所有线条颜色
|
||||||
allLineObjects.forEach((line) => {
|
allLineObjects.forEach((line) => {
|
||||||
if (line.material instanceof THREE.LineBasicMaterial) {
|
if (line.material instanceof LineBasicMaterial) {
|
||||||
line.material.color.set(line.userData.originalColor);
|
line.material.color.set(line.userData.originalColor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -556,7 +599,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
allLineObjects.forEach((line) => {
|
allLineObjects.forEach((line) => {
|
||||||
if (
|
if (
|
||||||
lineToCountryMap.get(line) === result.countryName &&
|
lineToCountryMap.get(line) === result.countryName &&
|
||||||
line.material instanceof THREE.LineBasicMaterial
|
line.material instanceof LineBasicMaterial
|
||||||
) {
|
) {
|
||||||
line.material.color.set(line.userData.highlightColor);
|
line.material.color.set(line.userData.highlightColor);
|
||||||
}
|
}
|
||||||
@ -589,21 +632,100 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
containerRef.current.addEventListener("click", onClick, { passive: false });
|
containerRef.current.addEventListener("click", onClick, { passive: false });
|
||||||
containerRef.current.addEventListener("dblclick", onDoubleClick, { passive: false });
|
containerRef.current.addEventListener("dblclick", onDoubleClick, { passive: false });
|
||||||
|
|
||||||
|
// 保存清理函数
|
||||||
|
cleanupFunctions.push(() => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
containerRef.current.removeEventListener("mousemove", onMouseMove);
|
||||||
|
containerRef.current.removeEventListener("click", onClick);
|
||||||
|
containerRef.current.removeEventListener("dblclick", onDoubleClick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取球面上的点对应的国家/地区
|
||||||
|
const getPointOnSphere = (
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
camera: PerspectiveCamera,
|
||||||
|
radius: number,
|
||||||
|
): { point: Vector3, countryName: string | null } | null => {
|
||||||
|
// 计算鼠标在画布中的归一化坐标
|
||||||
|
const rect = containerRef.current!.getBoundingClientRect();
|
||||||
|
const x = ((mouseX - rect.left) / rect.width) * 2 - 1;
|
||||||
|
const y = -((mouseY - rect.top) / rect.height) * 2 + 1;
|
||||||
|
|
||||||
|
// 创建射线
|
||||||
|
const ray = new Raycaster();
|
||||||
|
ray.setFromCamera(new Vector2(x, y), camera);
|
||||||
|
|
||||||
|
// 检测射线与实际地球模型的相交
|
||||||
|
const earthIntersects = ray.intersectObject(earth, false);
|
||||||
|
if (earthIntersects.length > 0) {
|
||||||
|
const point = earthIntersects[0].point;
|
||||||
|
|
||||||
|
// 使用WASM查找最近的国家/地区
|
||||||
|
const countryName = geoProcessor.find_nearest_country(
|
||||||
|
point.x, point.y, point.z, radius
|
||||||
|
);
|
||||||
|
|
||||||
|
return { point, countryName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有直接相交,使用球体辅助检测
|
||||||
|
const sphereGeom = new SphereGeometry(radius, 32, 32);
|
||||||
|
const sphereMesh = new Mesh(sphereGeom);
|
||||||
|
|
||||||
|
const intersects = ray.intersectObject(sphereMesh);
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const point = intersects[0].point;
|
||||||
|
|
||||||
|
// 使用WASM查找最近的国家/地区
|
||||||
|
const countryName = geoProcessor.find_nearest_country(
|
||||||
|
point.x, point.y, point.z, radius
|
||||||
|
);
|
||||||
|
|
||||||
|
return { point, countryName };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
// 简化的动画循环函数
|
// 简化的动画循环函数
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (!sceneRef.current) return;
|
if (!sceneRef.current) return;
|
||||||
|
|
||||||
// 更新控制器
|
// 更新控制器
|
||||||
sceneRef.current.controls.update();
|
controls.update();
|
||||||
|
|
||||||
// 渲染
|
// 渲染
|
||||||
sceneRef.current.renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
sceneRef.current.labelRenderer.render(scene, camera);
|
labelRenderer.render(scene, camera);
|
||||||
|
|
||||||
// 请求下一帧
|
// 请求下一帧
|
||||||
sceneRef.current.animationId = requestAnimationFrame(animate);
|
sceneRef.current.animationId = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理窗口大小变化
|
||||||
|
const handleResize = () => {
|
||||||
|
if (!containerRef.current || !sceneRef.current) return;
|
||||||
|
|
||||||
|
const width = containerRef.current.clientWidth;
|
||||||
|
const height = containerRef.current.clientHeight;
|
||||||
|
|
||||||
|
camera.aspect = width / height;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
labelRenderer.setSize(width, height);
|
||||||
|
|
||||||
|
// 立即渲染一次
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
labelRenderer.render(scene, camera);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleResize, { passive: true });
|
||||||
|
cleanupFunctions.push(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
// 保存场景引用
|
// 保存场景引用
|
||||||
sceneRef.current = {
|
sceneRef.current = {
|
||||||
scene,
|
scene,
|
||||||
@ -629,76 +751,21 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
// 开始动画
|
// 开始动画
|
||||||
sceneRef.current.animationId = requestAnimationFrame(animate);
|
sceneRef.current.animationId = requestAnimationFrame(animate);
|
||||||
|
|
||||||
// 获取球面上的点对应的国家/地区
|
} catch (error) {
|
||||||
const getPointOnSphere = (
|
console.error("Three.js初始化失败:", error);
|
||||||
mouseX: number,
|
|
||||||
mouseY: number,
|
|
||||||
camera: THREE.Camera,
|
|
||||||
radius: number,
|
|
||||||
): { point: THREE.Vector3, countryName: string | null } | null => {
|
|
||||||
// 计算鼠标在画布中的归一化坐标
|
|
||||||
const rect = containerRef.current!.getBoundingClientRect();
|
|
||||||
const x = ((mouseX - rect.left) / rect.width) * 2 - 1;
|
|
||||||
const y = -((mouseY - rect.top) / rect.height) * 2 + 1;
|
|
||||||
|
|
||||||
// 创建射线
|
|
||||||
const ray = new THREE.Raycaster();
|
|
||||||
ray.setFromCamera(new THREE.Vector2(x, y), camera);
|
|
||||||
|
|
||||||
// 检测射线与实际地球模型的相交
|
|
||||||
const earthIntersects = ray.intersectObject(earth, false);
|
|
||||||
if (earthIntersects.length > 0) {
|
|
||||||
const point = earthIntersects[0].point;
|
|
||||||
|
|
||||||
// 使用WASM查找最近的国家/地区
|
|
||||||
const countryName = geoProcessor.find_nearest_country(
|
|
||||||
point.x, point.y, point.z, radius
|
|
||||||
);
|
|
||||||
|
|
||||||
return { point, countryName };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有直接相交,使用球体辅助检测
|
|
||||||
const sphereGeom = new THREE.SphereGeometry(radius, 32, 32);
|
|
||||||
const sphereMesh = new THREE.Mesh(sphereGeom);
|
|
||||||
|
|
||||||
const intersects = ray.intersectObject(sphereMesh);
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
const point = intersects[0].point;
|
|
||||||
|
|
||||||
// 使用WASM查找最近的国家/地区
|
|
||||||
const countryName = geoProcessor.find_nearest_country(
|
|
||||||
point.x, point.y, point.z, radius
|
|
||||||
);
|
|
||||||
|
|
||||||
return { point, countryName };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理窗口大小变化
|
// 执行初始化
|
||||||
const handleResize = () => {
|
initThreeScene();
|
||||||
if (!containerRef.current || !sceneRef.current) return;
|
|
||||||
|
|
||||||
const { camera, renderer, labelRenderer } = sceneRef.current;
|
|
||||||
const width = containerRef.current.clientWidth;
|
|
||||||
const height = containerRef.current.clientHeight;
|
|
||||||
|
|
||||||
camera.aspect = width / height;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
labelRenderer.setSize(width, height);
|
|
||||||
|
|
||||||
// 立即渲染一次
|
|
||||||
renderer.render(sceneRef.current.scene, camera);
|
|
||||||
labelRenderer.render(sceneRef.current.scene, camera);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize, { passive: true });
|
|
||||||
|
|
||||||
// 清理函数
|
// 清理函数
|
||||||
return () => {
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
|
||||||
|
// 执行所有保存的清理函数
|
||||||
|
cleanupFunctions.forEach(fn => fn());
|
||||||
|
|
||||||
// 清理资源和事件监听器
|
// 清理资源和事件监听器
|
||||||
if (sceneRef.current) {
|
if (sceneRef.current) {
|
||||||
// 取消动画帧
|
// 取消动画帧
|
||||||
@ -721,16 +788,6 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
|||||||
sceneRef.current.controls.dispose();
|
sceneRef.current.controls.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除事件监听器
|
|
||||||
if (containerRef.current) {
|
|
||||||
containerRef.current.removeEventListener("mousemove", onMouseMove);
|
|
||||||
containerRef.current.removeEventListener("click", onClick);
|
|
||||||
containerRef.current.removeEventListener("dblclick", onDoubleClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除窗口事件监听器
|
|
||||||
window.removeEventListener("resize", handleResize);
|
|
||||||
};
|
};
|
||||||
}, [visitedPlaces, theme, wasmReady, geoProcessor]); // 添加geoProcessor依赖
|
}, [visitedPlaces, theme, wasmReady, geoProcessor]); // 添加geoProcessor依赖
|
||||||
|
|
||||||
|
@ -14,7 +14,10 @@ tags: []
|
|||||||
3. 如果变量没有使用可以前置下划线,消除警告
|
3. 如果变量没有使用可以前置下划线,消除警告
|
||||||
4. 强制类型转换
|
4. 强制类型转换
|
||||||
|
|
||||||
|
```rust
|
||||||
let a = 3.1;let b = a as i32
|
let a = 3.1;let b = a as i32
|
||||||
|
```
|
||||||
|
|
||||||
5. 打印变量
|
5. 打印变量
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -4,7 +4,7 @@ import { getSpecialPath } from "@/content.config";
|
|||||||
import Layout from "@/components/Layout.astro";
|
import Layout from "@/components/Layout.astro";
|
||||||
import Breadcrumb from "@/components/Breadcrumb.astro";
|
import Breadcrumb from "@/components/Breadcrumb.astro";
|
||||||
import { ARTICLE_EXPIRY_CONFIG } from "@/consts";
|
import { ARTICLE_EXPIRY_CONFIG } from "@/consts";
|
||||||
import "@/styles/content-styles.css";
|
import "@/styles/articles.css";
|
||||||
|
|
||||||
// 定义文章类型
|
// 定义文章类型
|
||||||
interface ArticleEntry {
|
interface ArticleEntry {
|
||||||
@ -500,105 +500,196 @@ const tableOfContents = generateTableOfContents(headings);
|
|||||||
listeners.length = 0;
|
listeners.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 应用高亮代码
|
// 1. 增强代码块功能 - 添加标题栏、语言显示和复制按钮
|
||||||
function applyCodeHighlighting() {
|
function enhanceCodeBlocks() {
|
||||||
const codeElements = document.querySelectorAll('code[data-highlighted]');
|
// 查找所有代码块元素
|
||||||
|
const codeBlocks = document.querySelectorAll('pre > code');
|
||||||
|
if (codeBlocks.length === 0) return;
|
||||||
|
|
||||||
codeElements.forEach(codeElement => {
|
codeBlocks.forEach(codeBlock => {
|
||||||
const highlightedCode = codeElement.getAttribute('data-highlighted');
|
const pre = codeBlock.parentElement;
|
||||||
if (highlightedCode) {
|
// 如果已经处理过,跳过
|
||||||
codeElement.innerHTML = highlightedCode;
|
if (pre.parentElement.classList.contains('code-block-container')) return;
|
||||||
codeElement.removeAttribute('data-highlighted');
|
|
||||||
}
|
// 获取语言类 - 简化语言提取逻辑
|
||||||
});
|
let language = '';
|
||||||
|
|
||||||
|
// 从data-language属性中提取(最常见的方式)
|
||||||
|
language = pre.getAttribute('data-language') || codeBlock.getAttribute('data-language') || '';
|
||||||
|
|
||||||
|
// 如果没有找到语言,则默认为 'text'
|
||||||
|
if (!language || language === 'mermaid') {
|
||||||
|
// 跳过 mermaid 图表
|
||||||
|
if (language === 'mermaid') return;
|
||||||
|
language = 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 加载Mermaid SVG内容
|
// 获取原始代码文本,保留换行和格式
|
||||||
function loadMermaidSvg() {
|
let originalCode = '';
|
||||||
const mermaidContainers = document.querySelectorAll('.mermaid-figure [data-mermaid-src], .mermaid-svg-container[data-mermaid-src]');
|
const hasLineElements = codeBlock.querySelectorAll('.line').length > 0 ||
|
||||||
|
codeBlock.innerHTML.includes('<span class="line">');
|
||||||
|
|
||||||
mermaidContainers.forEach(container => {
|
if (hasLineElements) {
|
||||||
const svgSrc = container.getAttribute('data-mermaid-src');
|
// 从行元素中提取文本
|
||||||
if (svgSrc) {
|
const lines = codeBlock.querySelectorAll('.line');
|
||||||
fetch(svgSrc)
|
if (lines.length > 0) {
|
||||||
.then(response => {
|
originalCode = Array.from(lines)
|
||||||
if (!response.ok) {
|
.map(line => line.textContent)
|
||||||
throw new Error('SVG加载失败: ' + response.status);
|
.join('\n');
|
||||||
}
|
|
||||||
return response.text();
|
|
||||||
})
|
|
||||||
.then(svgContent => {
|
|
||||||
// 直接插入SVG内容,而不是作为图像
|
|
||||||
container.innerHTML = svgContent;
|
|
||||||
|
|
||||||
// 确保SVG元素正确应用样式
|
|
||||||
const svgElement = container.querySelector('svg');
|
|
||||||
if (svgElement) {
|
|
||||||
// 设置高度为auto以允许CSS控制
|
|
||||||
svgElement.setAttribute('height', 'auto');
|
|
||||||
|
|
||||||
// 确保viewBox属性存在并正确设置
|
|
||||||
if (!svgElement.hasAttribute('viewBox') ||
|
|
||||||
!svgElement.getAttribute('viewBox')?.match(/^\d+\s+\d+\s+\d+\s+\d+$/)) {
|
|
||||||
// 如果没有有效的viewBox,尝试从宽高创建一个
|
|
||||||
const width = svgElement.getAttribute('width');
|
|
||||||
const height = svgElement.getAttribute('height');
|
|
||||||
if (width && height && !isNaN(parseFloat(width)) && !isNaN(parseFloat(height))) {
|
|
||||||
svgElement.setAttribute('viewBox', `0 0 ${parseFloat(width)} ${parseFloat(height)}`);
|
|
||||||
} else {
|
} else {
|
||||||
// 使用默认viewBox
|
// 尝试解析HTML中的行
|
||||||
svgElement.setAttribute('viewBox', '0 0 100 100');
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = codeBlock.innerHTML;
|
||||||
|
const lineSpans = tempDiv.querySelectorAll('.line');
|
||||||
|
originalCode = Array.from(lineSpans)
|
||||||
|
.map(span => span.textContent)
|
||||||
|
.join('\n');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 直接使用textContent (这种情况下可能没有特殊格式化)
|
||||||
|
originalCode = codeBlock.textContent || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保具有mermaid-svg类
|
// 创建代码块容器
|
||||||
svgElement.classList.add('mermaid-svg');
|
const container = document.createElement('div');
|
||||||
}
|
container.className = 'code-block-container';
|
||||||
|
|
||||||
// 移除data属性避免重复加载
|
// 创建标题栏
|
||||||
container.removeAttribute('data-mermaid-src');
|
const header = document.createElement('div');
|
||||||
})
|
header.className = 'code-block-header';
|
||||||
.catch(error => {
|
|
||||||
console.error('加载SVG失败:', error);
|
|
||||||
container.innerHTML = '<div class="mermaid-error-message">图表加载失败</div>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 处理代码复制功能
|
// 语言标签
|
||||||
function setupCodeCopy() {
|
const langDiv = document.createElement('div');
|
||||||
const handleCopyClick = (e) => {
|
langDiv.className = 'code-block-lang';
|
||||||
const target = e.target;
|
langDiv.innerHTML = `
|
||||||
const copyButton = target.closest('[data-copy]');
|
<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">
|
||||||
if (!copyButton) return;
|
<polyline points="16 18 22 12 16 6"></polyline>
|
||||||
|
<polyline points="8 6 2 12 8 18"></polyline>
|
||||||
|
</svg>
|
||||||
|
${language.toUpperCase()}
|
||||||
|
`;
|
||||||
|
|
||||||
const container = copyButton.closest('.code-block-container');
|
// 复制按钮
|
||||||
if (!container) return;
|
const copyButton = document.createElement('button');
|
||||||
|
copyButton.className = 'code-block-copy';
|
||||||
|
copyButton.innerHTML = `
|
||||||
|
<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 codeElement = container.querySelector('pre code');
|
// 代码内容容器
|
||||||
if (!codeElement) return;
|
const codeContent = document.createElement('div');
|
||||||
|
codeContent.className = 'code-block-content';
|
||||||
|
|
||||||
const code = codeElement.textContent || '';
|
// 添加复制功能
|
||||||
|
addListener(copyButton, 'click', async () => {
|
||||||
navigator.clipboard.writeText(code)
|
try {
|
||||||
.then(() => {
|
// 使用优化后的方法获取代码文本
|
||||||
const originalText = copyButton.textContent || '';
|
await navigator.clipboard.writeText(originalCode);
|
||||||
copyButton.textContent = '已复制!';
|
copyButton.classList.add('copied');
|
||||||
copyButton.disabled = true;
|
copyButton.innerHTML = `
|
||||||
|
<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>
|
||||||
|
已复制
|
||||||
|
`;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyButton.textContent = originalText;
|
copyButton.classList.remove('copied');
|
||||||
copyButton.disabled = false;
|
copyButton.innerHTML = `
|
||||||
|
<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>
|
||||||
|
复制
|
||||||
|
`;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
})
|
} catch (err) {
|
||||||
.catch(err => {
|
|
||||||
console.error('复制失败:', err);
|
console.error('复制失败:', err);
|
||||||
alert('复制失败,请手动复制');
|
copyButton.innerHTML = `
|
||||||
|
<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="12" y1="8" x2="12" y2="12"></line>
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||||
|
</svg>
|
||||||
|
失败
|
||||||
|
`;
|
||||||
|
setTimeout(() => {
|
||||||
|
copyButton.innerHTML = `
|
||||||
|
<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>
|
||||||
|
复制
|
||||||
|
`;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
addListener(document, 'click', handleCopyClick);
|
// 组装标题栏
|
||||||
|
header.appendChild(langDiv);
|
||||||
|
header.appendChild(copyButton);
|
||||||
|
|
||||||
|
// 添加行号
|
||||||
|
const preHasLineNumbers = pre.classList.contains('line-numbers') || pre.classList.contains('has-line-numbers');
|
||||||
|
|
||||||
|
// 不改变原有结构,只在外层包装
|
||||||
|
// 保留原有的代码块,将其移入新容器
|
||||||
|
pre.parentNode.insertBefore(container, pre);
|
||||||
|
container.appendChild(header);
|
||||||
|
container.appendChild(codeContent);
|
||||||
|
codeContent.appendChild(pre);
|
||||||
|
|
||||||
|
// 始终添加行号 - 修改此部分,确保所有代码块都有行号
|
||||||
|
if (!preHasLineNumbers) {
|
||||||
|
pre.classList.add('line-numbers');
|
||||||
|
|
||||||
|
// 如果代码块内部没有行号结构,则添加
|
||||||
|
const hasLineElements = codeBlock.querySelectorAll('.line').length > 0 ||
|
||||||
|
codeBlock.innerHTML.includes('<span class="line">');
|
||||||
|
|
||||||
|
if (!hasLineElements) {
|
||||||
|
// 特殊处理已经高亮的代码,防止破坏高亮效果
|
||||||
|
const isPreHighlighted = codeBlock.innerHTML.includes('span style=') ||
|
||||||
|
pre.classList.contains('shiki') ||
|
||||||
|
pre.classList.contains('astro-code');
|
||||||
|
|
||||||
|
if (isPreHighlighted) {
|
||||||
|
// 已经高亮的代码,只添加行号类,不修改结构
|
||||||
|
// 在这种情况下依赖CSS来显示行号,而不是修改DOM结构
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只处理未高亮的代码块
|
||||||
|
// 将代码按行分割
|
||||||
|
const lines = originalCode.split('\n');
|
||||||
|
let newHtml = '';
|
||||||
|
|
||||||
|
// 优化行处理,确保正确处理行内容
|
||||||
|
lines.forEach((line, index) => {
|
||||||
|
// 避免末尾空行
|
||||||
|
if (index === lines.length - 1 && line.trim() === '') return;
|
||||||
|
|
||||||
|
// 处理HTML实体,防止XSS并保留格式
|
||||||
|
const escapedLine = line
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
|
||||||
|
// 添加行标记(不添加换行符)
|
||||||
|
newHtml += `<span class="line">${escapedLine}</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新代码块内容,仅在有内容时更新
|
||||||
|
if (newHtml) {
|
||||||
|
codeBlock.innerHTML = newHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 设置阅读进度条
|
// 4. 设置阅读进度条
|
||||||
@ -773,24 +864,137 @@ const tableOfContents = generateTableOfContents(headings);
|
|||||||
updateActiveHeading();
|
updateActiveHeading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. 处理Mermaid图表渲染
|
||||||
|
function setupMermaid() {
|
||||||
|
// 查找所有mermaid代码块 - 支持多种可能的类名和选择器
|
||||||
|
const mermaidBlocks = document.querySelectorAll(
|
||||||
|
'pre.language-mermaid, pre > code.language-mermaid, .mermaid'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mermaidBlocks.length === 0) return;
|
||||||
|
|
||||||
|
console.log('找到Mermaid代码块:', mermaidBlocks.length);
|
||||||
|
|
||||||
|
// 动态加载mermaid库
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
|
||||||
|
|
||||||
|
script.onload = function() {
|
||||||
|
console.log('Mermaid库加载完成,开始渲染图表');
|
||||||
|
|
||||||
|
// 初始化mermaid配置 - 始终使用默认主题,通过CSS控制样式
|
||||||
|
window.mermaid.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
theme: 'default',
|
||||||
|
securityLevel: 'loose'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将所有mermaid代码块转换为可渲染的格式
|
||||||
|
mermaidBlocks.forEach((block, index) => {
|
||||||
|
// 获取mermaid代码
|
||||||
|
let code = '';
|
||||||
|
|
||||||
|
// 检查元素类型并相应处理
|
||||||
|
if (block.tagName === 'CODE' && block.classList.contains('language-mermaid')) {
|
||||||
|
// 处理 code.language-mermaid 元素
|
||||||
|
code = block.textContent || '';
|
||||||
|
const pre = block.closest('pre');
|
||||||
|
if (pre) {
|
||||||
|
// 创建新的div元素替换整个pre
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'mermaid';
|
||||||
|
div.id = 'mermaid-diagram-' + index;
|
||||||
|
div.textContent = code;
|
||||||
|
pre.parentNode.replaceChild(div, pre);
|
||||||
|
}
|
||||||
|
} else if (block.tagName === 'PRE' && block.classList.contains('language-mermaid')) {
|
||||||
|
// 处理 pre.language-mermaid 元素
|
||||||
|
code = block.textContent || '';
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'mermaid';
|
||||||
|
div.id = 'mermaid-diagram-' + index;
|
||||||
|
div.textContent = code;
|
||||||
|
block.parentNode.replaceChild(div, block);
|
||||||
|
} else if (block.classList.contains('mermaid') && block.tagName !== 'DIV') {
|
||||||
|
// 如果是其他带mermaid类的元素但不是div,转换为div
|
||||||
|
code = block.textContent || '';
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'mermaid';
|
||||||
|
div.id = 'mermaid-diagram-' + index;
|
||||||
|
div.textContent = code;
|
||||||
|
block.parentNode.replaceChild(div, block);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化渲染
|
||||||
|
try {
|
||||||
|
console.log('开始渲染Mermaid图表');
|
||||||
|
window.mermaid.run().catch(err => {
|
||||||
|
console.error('Mermaid渲染出错:', err);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化Mermaid渲染失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
script.onerror = function() {
|
||||||
|
console.error('加载Mermaid库失败');
|
||||||
|
// 显示错误信息
|
||||||
|
mermaidBlocks.forEach(block => {
|
||||||
|
if (block.tagName === 'CODE') block = block.closest('pre');
|
||||||
|
if (block) {
|
||||||
|
block.innerHTML = '<div class="mermaid-error-message">无法加载Mermaid图表库</div>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
// 添加到清理列表,确保后续页面跳转时能删除脚本
|
||||||
|
listeners.push({
|
||||||
|
element: script,
|
||||||
|
eventType: 'remove',
|
||||||
|
handler: () => {
|
||||||
|
if (script.parentNode) {
|
||||||
|
script.parentNode.removeChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除全局mermaid对象
|
||||||
|
if (window.mermaid) {
|
||||||
|
window.mermaid = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除页面上可能留下的mermaid相关元素
|
||||||
|
try {
|
||||||
|
// 移除所有可能的mermaid样式和元素
|
||||||
|
const mermaidElements = [
|
||||||
|
'#mermaid-style',
|
||||||
|
'#mermaid-cloned-styles',
|
||||||
|
'.mermaid-svg-reference',
|
||||||
|
'style[id^="mermaid-"]'
|
||||||
|
];
|
||||||
|
|
||||||
|
document.querySelectorAll(mermaidElements.join(', ')).forEach(el => {
|
||||||
|
if (el && el.parentNode) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('清理Mermaid元素时出错:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化所有功能
|
// 初始化所有功能
|
||||||
function init() {
|
function init() {
|
||||||
if (!document.querySelector("article")) return;
|
if (!document.querySelector("article")) return;
|
||||||
|
|
||||||
// 应用代码高亮
|
enhanceCodeBlocks(); // 添加新的代码块增强函数
|
||||||
applyCodeHighlighting();
|
|
||||||
|
|
||||||
// 加载Mermaid SVG图表
|
|
||||||
loadMermaidSvg();
|
|
||||||
|
|
||||||
// 设置代码复制功能
|
|
||||||
setupCodeCopy();
|
|
||||||
|
|
||||||
// 设置阅读进度条
|
|
||||||
setupProgressBar();
|
setupProgressBar();
|
||||||
|
|
||||||
// 设置目录交互
|
|
||||||
setupTableOfContents();
|
setupTableOfContents();
|
||||||
|
setupMermaid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册清理函数
|
// 注册清理函数
|
||||||
|
File diff suppressed because it is too large
Load Diff
200
src/styles/articles-code.css
Normal file
200
src/styles/articles-code.css
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/* 代码块容器样式 - 简化背景和阴影 */
|
||||||
|
.code-block-container {
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background-color: transparent;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码块标题栏 */
|
||||||
|
.code-block-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码语言标签 */
|
||||||
|
.code-block-lang {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-lang svg {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 复制按钮 */
|
||||||
|
.code-block-copy {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #475569;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-copy:hover {
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-copy.copied {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码内容容器 - 移除背景 */
|
||||||
|
.code-block-content {
|
||||||
|
position: relative;
|
||||||
|
overflow-x: auto;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 基础代码块样式 - 减小内边距 */
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.2rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行号样式 - 缩小间距 */
|
||||||
|
.line-numbers {
|
||||||
|
counter-reset: line;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行号背景条 - 减小宽度 */
|
||||||
|
.line-numbers::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3rem;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-right: 1px solid #e2e8f0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行样式 - 进一步减小行间距和缩进 */
|
||||||
|
.line-numbers .line {
|
||||||
|
position: relative;
|
||||||
|
counter-increment: line;
|
||||||
|
padding-left: 3.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
min-height: 1.5rem;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行号 - 调整位置 */
|
||||||
|
.line-numbers .line::before {
|
||||||
|
content: counter(line);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 3rem;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式 */
|
||||||
|
[data-theme='dark'] .code-block-container {
|
||||||
|
border-color: #334155;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .code-block-header {
|
||||||
|
background-color: #1e293b;
|
||||||
|
border-bottom-color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .code-block-lang {
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .code-block-copy {
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .code-block-copy:hover {
|
||||||
|
background-color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除代码内容区域的黑暗模式背景 */
|
||||||
|
[data-theme='dark'] .code-block-content {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式行号样式 */
|
||||||
|
[data-theme='dark'] .line-numbers::before {
|
||||||
|
background-color: #1e293b;
|
||||||
|
border-right-color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .line-numbers .line::before {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 兼容原有高亮 - 确保不添加背景 */
|
||||||
|
[data-theme='dark'] .shiki,
|
||||||
|
[data-theme='dark'] .shiki span {
|
||||||
|
color: var(--shiki-dark) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .astro-code,
|
||||||
|
[data-theme='dark'] .astro-code span {
|
||||||
|
color: var(--shiki-dark) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保所有代码元素没有背景 */
|
||||||
|
code, pre, .code-block-content,
|
||||||
|
.code-block-content pre.shiki,
|
||||||
|
.code-block-content pre.astro-code,
|
||||||
|
.code-block-content pre code,
|
||||||
|
.code-block-content pre code span,
|
||||||
|
pre.shiki, pre.astro-code,
|
||||||
|
.line, .line span {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 高亮行样式 - 仅保留边框而不添加背景 */
|
||||||
|
.line.highlighted {
|
||||||
|
border-left: 2px solid #eab308;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .line.highlighted {
|
||||||
|
border-left: 2px solid #fbbf24;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
150
src/styles/articles-mermaid.css
Normal file
150
src/styles/articles-mermaid.css
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
:root {
|
||||||
|
--mermaid-text-color: #1e293b;
|
||||||
|
--mermaid-bg-color: #ffffff;
|
||||||
|
--mermaid-primary-color: #4f46e5;
|
||||||
|
--mermaid-secondary-color: #6366f1;
|
||||||
|
--mermaid-border-color: #9ca3af;
|
||||||
|
--mermaid-line-color: #4b5563;
|
||||||
|
--mermaid-edge-label-bg: #ffffff;
|
||||||
|
--mermaid-title-color: #0f172a;
|
||||||
|
--mermaid-section-bg: #f8fafc;
|
||||||
|
--mermaid-node-bg: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式样式变量 */
|
||||||
|
[data-theme='dark'], .dark {
|
||||||
|
--mermaid-text-color: #e2e8f0;
|
||||||
|
--mermaid-bg-color: #1e293b;
|
||||||
|
--mermaid-primary-color: #818cf8;
|
||||||
|
--mermaid-secondary-color: #a5b4fc;
|
||||||
|
--mermaid-border-color: #64748b;
|
||||||
|
--mermaid-line-color: #94a3b8;
|
||||||
|
--mermaid-edge-label-bg: #1e293b;
|
||||||
|
--mermaid-title-color: #f8fafc;
|
||||||
|
--mermaid-section-bg: #334155;
|
||||||
|
--mermaid-node-bg: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 基础Mermaid样式覆盖 */
|
||||||
|
.mermaid {
|
||||||
|
background-color: transparent !important;
|
||||||
|
transition: color 0.3s ease, fill 0.3s ease, stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mermaid文本颜色统一 */
|
||||||
|
.mermaid .label,
|
||||||
|
.mermaid text,
|
||||||
|
.mermaid span,
|
||||||
|
.mermaid .messageText,
|
||||||
|
.mermaid .loopText,
|
||||||
|
.mermaid .noteText,
|
||||||
|
.mermaid .taskText {
|
||||||
|
color: var(--mermaid-text-color) !important;
|
||||||
|
fill: var(--mermaid-text-color) !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
transition: color 0.3s ease, fill 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 节点样式设置 */
|
||||||
|
.mermaid .node rect,
|
||||||
|
.mermaid .node circle,
|
||||||
|
.mermaid .node ellipse,
|
||||||
|
.mermaid .node polygon,
|
||||||
|
.mermaid .node path {
|
||||||
|
fill: var(--mermaid-node-bg) !important;
|
||||||
|
stroke: var(--mermaid-border-color) !important;
|
||||||
|
stroke-width: 1px !important;
|
||||||
|
transition: fill 0.3s ease, stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 连接线样式 */
|
||||||
|
.mermaid .edgePath .path,
|
||||||
|
.mermaid .flowchart-link,
|
||||||
|
.mermaid line,
|
||||||
|
.mermaid .messageLine0,
|
||||||
|
.mermaid .messageLine1 {
|
||||||
|
stroke: var(--mermaid-line-color) !important;
|
||||||
|
stroke-width: 1px !important;
|
||||||
|
transition: stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 箭头填充 */
|
||||||
|
.mermaid .arrowheadPath,
|
||||||
|
.mermaid marker path {
|
||||||
|
fill: var(--mermaid-line-color) !important;
|
||||||
|
stroke: none !important;
|
||||||
|
transition: fill 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本标签背景 */
|
||||||
|
.mermaid .edgeLabel rect,
|
||||||
|
.mermaid .labelBox {
|
||||||
|
fill: var(--mermaid-edge-label-bg) !important;
|
||||||
|
background-color: var(--mermaid-edge-label-bg) !important;
|
||||||
|
transition: fill 0.3s ease, background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题样式 */
|
||||||
|
.mermaid .titleText,
|
||||||
|
.mermaid .classTitle,
|
||||||
|
.mermaid .cluster-label text {
|
||||||
|
fill: var(--mermaid-title-color) !important;
|
||||||
|
color: var(--mermaid-title-color) !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
transition: fill 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 集群/子图样式 */
|
||||||
|
.mermaid .cluster rect,
|
||||||
|
.mermaid .cluster polygon {
|
||||||
|
fill: var(--mermaid-section-bg) !important;
|
||||||
|
stroke: var(--mermaid-border-color) !important;
|
||||||
|
stroke-width: 1px !important;
|
||||||
|
opacity: 0.8 !important;
|
||||||
|
transition: fill 0.3s ease, stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 序列图特殊样式 */
|
||||||
|
.mermaid .actor {
|
||||||
|
fill: var(--mermaid-node-bg) !important;
|
||||||
|
stroke: var(--mermaid-border-color) !important;
|
||||||
|
stroke-width: 1px !important;
|
||||||
|
transition: fill 0.3s ease, stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid .note {
|
||||||
|
fill: var(--mermaid-secondary-color) !important;
|
||||||
|
opacity: 0.7 !important;
|
||||||
|
transition: fill 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图特殊样式 */
|
||||||
|
.mermaid .section0,
|
||||||
|
.mermaid .section2 {
|
||||||
|
fill: var(--mermaid-section-bg) !important;
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
transition: fill 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid .section1,
|
||||||
|
.mermaid .section3 {
|
||||||
|
fill: var(--mermaid-node-bg) !important;
|
||||||
|
opacity: 0.4 !important;
|
||||||
|
transition: fill 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid .task0,
|
||||||
|
.mermaid .task1,
|
||||||
|
.mermaid .task2,
|
||||||
|
.mermaid .task3 {
|
||||||
|
fill: var(--mermaid-primary-color) !important;
|
||||||
|
stroke: var(--mermaid-border-color) !important;
|
||||||
|
transition: fill 0.3s ease, stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 类图特殊样式 */
|
||||||
|
.mermaid .classLabel .label {
|
||||||
|
fill: var(--mermaid-text-color) !important;
|
||||||
|
color: var(--mermaid-text-color) !important;
|
||||||
|
transition: fill 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
@import "./table-styles.css";
|
@import "./articles-table.css";
|
||||||
@import "./code-blocks.css";
|
@import "./articles-mermaid.css";
|
||||||
@import "./mermaid-themes.css";
|
@import "./articles-code.css";
|
||||||
|
|
||||||
/* 增强列表样式 */
|
/* 增强列表样式 */
|
||||||
.prose ul {
|
.prose ul {
|
||||||
@ -42,41 +42,6 @@
|
|||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 行内代码样式 */
|
|
||||||
.prose :not(pre) > code {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
color: var(--color-primary-700);
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-radius: 0.375em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: "JetBrains Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
||||||
font-size: 0.875em;
|
|
||||||
white-space: normal;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
||||||
letter-spacing: -0.025em;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose :not(pre) > code:hover {
|
|
||||||
background-color: rgba(var(--color-primary-600-rgb), 0.08);
|
|
||||||
border-color: rgba(var(--color-primary-400-rgb), 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .prose :not(pre) > code {
|
|
||||||
background-color: rgba(255, 255, 255, 0.07);
|
|
||||||
color: var(--color-primary-300);
|
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .prose :not(pre) > code:hover {
|
|
||||||
background-color: rgba(var(--color-primary-500-rgb), 0.15);
|
|
||||||
border-color: rgba(var(--color-primary-300-rgb), 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题样式 */
|
/* 标题样式 */
|
||||||
.prose h1 {
|
.prose h1 {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
@ -294,8 +259,7 @@
|
|||||||
.prose details > p,
|
.prose details > p,
|
||||||
.prose details > ul,
|
.prose details > ul,
|
||||||
.prose details > ol,
|
.prose details > ol,
|
||||||
.prose details > div,
|
.prose details > div {
|
||||||
.prose details > pre {
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
@ -1,537 +0,0 @@
|
|||||||
/* 代码块容器 */
|
|
||||||
.code-block-container {
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
border: 1px solid rgba(0,0,0,0.1);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-block-container {
|
|
||||||
background-color: #282a36;
|
|
||||||
border-color: rgba(255,255,255,0.1);
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题栏 */
|
|
||||||
.code-block-title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
background-color: #e2e8f0;
|
|
||||||
border-bottom: 1px solid #cbd5e0;
|
|
||||||
font-family: 'JetBrains Mono', Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-block-title {
|
|
||||||
background-color: #343746;
|
|
||||||
border-bottom: 1px solid #44475a;
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题栏左侧 */
|
|
||||||
.code-title-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题栏右侧 */
|
|
||||||
.code-title-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 语言标识 */
|
|
||||||
.code-language {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #4a5568;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-language {
|
|
||||||
color: #bd93f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文件名 */
|
|
||||||
.code-filename {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #4a5568;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-filename {
|
|
||||||
color: #f1fa8c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 复制按钮 */
|
|
||||||
.copy-button {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #a0aec0;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #4a5568;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-button:hover {
|
|
||||||
background: #edf2f7;
|
|
||||||
color: #2d3748;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .copy-button {
|
|
||||||
border-color: #44475a;
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .copy-button:hover {
|
|
||||||
background: #44475a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代码内容区 */
|
|
||||||
.code-block-content {
|
|
||||||
padding: 1rem;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block-content pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
overflow: visible;
|
|
||||||
font-family: 'JetBrains Mono', Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block-content code {
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------- */
|
|
||||||
/* 终端样式 */
|
|
||||||
/* ----------------------- */
|
|
||||||
|
|
||||||
.terminal-container {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #adc2ff;
|
|
||||||
box-shadow: 0 8px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container {
|
|
||||||
background-color: #1a1e2a;
|
|
||||||
border-color: rgba(255,255,255,0.1);
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端标题栏 */
|
|
||||||
.terminal-container .code-block-title {
|
|
||||||
background-color: #f1f5f9;
|
|
||||||
border-bottom: 1px solid #adc2ff;
|
|
||||||
padding: 0.4rem 1rem;
|
|
||||||
color: #334155;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .code-block-title {
|
|
||||||
background-color: #111827;
|
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
||||||
color: #f8fafc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端控制按钮 */
|
|
||||||
.terminal-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-control {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: block;
|
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-close {
|
|
||||||
background-color: #f87171;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-minimize {
|
|
||||||
background-color: #fbbf24;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-maximize {
|
|
||||||
background-color: #34d399;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端内容区 */
|
|
||||||
.terminal-container .code-block-content {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
color: #334155;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .code-block-content {
|
|
||||||
background-color: #1a1e2a;
|
|
||||||
color: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-pre {
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-pre {
|
|
||||||
color: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 语言标签在终端样式中的颜色 */
|
|
||||||
.terminal-container .code-language {
|
|
||||||
color: #64748b;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .code-language {
|
|
||||||
color: #a0aec0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端中的复制按钮 */
|
|
||||||
.terminal-container .copy-button {
|
|
||||||
border-color: #adc2ff;
|
|
||||||
color: #4a5568;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-container .copy-button:hover {
|
|
||||||
background: #ebf0ff;
|
|
||||||
color: #4b6bff;
|
|
||||||
border-color: #4b6bff;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .copy-button {
|
|
||||||
border-color: rgba(255,255,255,0.3);
|
|
||||||
color: #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .copy-button:hover {
|
|
||||||
background: rgba(255,255,255,0.15);
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------- */
|
|
||||||
/* Mermaid 图表样式 */
|
|
||||||
/* ----------------------- */
|
|
||||||
|
|
||||||
.mermaid-figure {
|
|
||||||
margin: 2rem auto;
|
|
||||||
text-align: center;
|
|
||||||
max-width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-figure img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 亮色主题图表 */
|
|
||||||
.light-theme-diagram {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .light-theme-diagram {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗色主题图表 */
|
|
||||||
.dark-theme-diagram {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .dark-theme-diagram {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mermaid 错误样式 */
|
|
||||||
.mermaid-error {
|
|
||||||
border: 1px solid #e53e3e;
|
|
||||||
background-color: #fff5f5;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-error {
|
|
||||||
background-color: #3b1a1a;
|
|
||||||
border-color: #fc8181;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-error-message {
|
|
||||||
color: #c53030;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-error-message {
|
|
||||||
color: #fc8181;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------- */
|
|
||||||
/* Highlight.js 主题样式 */
|
|
||||||
/* ----------------------- */
|
|
||||||
|
|
||||||
/* 基本颜色方案 - 亮色 */
|
|
||||||
.hljs {
|
|
||||||
color: #1a202c;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs {
|
|
||||||
color: #f8f8f2;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 注释 */
|
|
||||||
.hljs-comment,
|
|
||||||
.hljs-quote {
|
|
||||||
color: #718096;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-comment,
|
|
||||||
[data-theme="dark"] .hljs-quote {
|
|
||||||
color: #6272a4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 关键字 */
|
|
||||||
.hljs-keyword,
|
|
||||||
.hljs-selector-tag,
|
|
||||||
.hljs-addition {
|
|
||||||
color: #805ad5;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-keyword,
|
|
||||||
[data-theme="dark"] .hljs-selector-tag,
|
|
||||||
[data-theme="dark"] .hljs-addition {
|
|
||||||
color: #ff79c6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 变量名和属性 */
|
|
||||||
.hljs-variable,
|
|
||||||
.hljs-template-variable,
|
|
||||||
.hljs-literal,
|
|
||||||
.hljs-attr,
|
|
||||||
.hljs-selector-attr,
|
|
||||||
.hljs-selector-pseudo,
|
|
||||||
.hljs-meta {
|
|
||||||
color: #dd6b20;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-variable,
|
|
||||||
[data-theme="dark"] .hljs-template-variable,
|
|
||||||
[data-theme="dark"] .hljs-literal,
|
|
||||||
[data-theme="dark"] .hljs-attr,
|
|
||||||
[data-theme="dark"] .hljs-selector-attr,
|
|
||||||
[data-theme="dark"] .hljs-selector-pseudo,
|
|
||||||
[data-theme="dark"] .hljs-meta {
|
|
||||||
color: #f1fa8c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 函数名 */
|
|
||||||
.hljs-title,
|
|
||||||
.hljs-function .hljs-title,
|
|
||||||
.hljs-section {
|
|
||||||
color: #3182ce;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-title,
|
|
||||||
[data-theme="dark"] .hljs-function .hljs-title,
|
|
||||||
[data-theme="dark"] .hljs-section {
|
|
||||||
color: #50fa7b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 类名和ID */
|
|
||||||
.hljs-title.class_,
|
|
||||||
.hljs-type,
|
|
||||||
.hljs-class .hljs-title,
|
|
||||||
.hljs-selector-id,
|
|
||||||
.hljs-selector-class {
|
|
||||||
color: #d53f8c;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-title.class_,
|
|
||||||
[data-theme="dark"] .hljs-type,
|
|
||||||
[data-theme="dark"] .hljs-class .hljs-title,
|
|
||||||
[data-theme="dark"] .hljs-selector-id,
|
|
||||||
[data-theme="dark"] .hljs-selector-class {
|
|
||||||
color: #8be9fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 字符串 */
|
|
||||||
.hljs-string,
|
|
||||||
.hljs-regexp,
|
|
||||||
.hljs-attribute,
|
|
||||||
.hljs-doctag {
|
|
||||||
color: #38a169;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-string,
|
|
||||||
[data-theme="dark"] .hljs-regexp,
|
|
||||||
[data-theme="dark"] .hljs-attribute,
|
|
||||||
[data-theme="dark"] .hljs-doctag {
|
|
||||||
color: #f1fa8c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 数字 */
|
|
||||||
.hljs-number,
|
|
||||||
.hljs-deletion,
|
|
||||||
.hljs-symbol,
|
|
||||||
.hljs-bullet,
|
|
||||||
.hljs-link,
|
|
||||||
.hljs-formula {
|
|
||||||
color: #e53e3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-number,
|
|
||||||
[data-theme="dark"] .hljs-deletion,
|
|
||||||
[data-theme="dark"] .hljs-symbol,
|
|
||||||
[data-theme="dark"] .hljs-bullet,
|
|
||||||
[data-theme="dark"] .hljs-link,
|
|
||||||
[data-theme="dark"] .hljs-formula {
|
|
||||||
color: #bd93f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HTML标签 */
|
|
||||||
.hljs-tag,
|
|
||||||
.hljs-name,
|
|
||||||
.hljs-selector-tag {
|
|
||||||
color: #2b6cb0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-tag,
|
|
||||||
[data-theme="dark"] .hljs-name,
|
|
||||||
[data-theme="dark"] .hljs-selector-tag {
|
|
||||||
color: #ff79c6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 内置对象 */
|
|
||||||
.hljs-built_in {
|
|
||||||
color: #c05621;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .hljs-built_in {
|
|
||||||
color: #8be9fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 高亮代码行 */
|
|
||||||
.hljs-emphasis {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端命令特殊着色 */
|
|
||||||
.terminal-container .hljs-built_in,
|
|
||||||
.terminal-container .hljs-keyword {
|
|
||||||
color: #4b6bff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .hljs-built_in,
|
|
||||||
[data-theme="dark"] .terminal-container .hljs-keyword {
|
|
||||||
color: #9ae6b4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端中的字符串 */
|
|
||||||
.terminal-container .hljs-string {
|
|
||||||
color: #ea580c;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .hljs-string {
|
|
||||||
color: #fbd38d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端中的变量 */
|
|
||||||
.terminal-container .hljs-variable {
|
|
||||||
color: #8b5cf6;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .hljs-variable {
|
|
||||||
color: #b794f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端中的参数 */
|
|
||||||
.terminal-container .hljs-params {
|
|
||||||
color: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .hljs-params {
|
|
||||||
color: #cbd5e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 终端中的注释 */
|
|
||||||
.terminal-container .hljs-comment {
|
|
||||||
color: #64748b;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .terminal-container .hljs-comment {
|
|
||||||
color: #a0aec0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动条样式 */
|
|
||||||
.code-block-content::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block-content::-webkit-scrollbar-track {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block-content::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-block-content::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-block-content::-webkit-scrollbar-track {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-block-content::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .code-block-content::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
/* Mermaid图表主题样式
|
|
||||||
* 支持亮色/暗色主题切换
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* 图表容器样式 */
|
|
||||||
.mermaid-figure {
|
|
||||||
margin: 2rem auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 80vh; /* 限制容器最大高度为视窗高度的80% */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 错误信息样式 */
|
|
||||||
.mermaid-error-message {
|
|
||||||
color: #e53e3e;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px dashed #e53e3e;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin: 1rem 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 基础样式 - 亮色主题 */
|
|
||||||
.mermaid-svg {
|
|
||||||
/* 全局图表容器样式 */
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto !important; /* 强制高度为自动,覆盖内联样式 */
|
|
||||||
max-height: 80vh; /* 限制最大高度为视窗高度的80% */
|
|
||||||
width: auto; /* 允许宽度自适应内容 */
|
|
||||||
overflow: visible;
|
|
||||||
/* 移除背景色,使用透明背景 */
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 节点样式 */
|
|
||||||
.mermaid-svg .node rect,
|
|
||||||
.mermaid-svg .node circle,
|
|
||||||
.mermaid-svg .node ellipse,
|
|
||||||
.mermaid-svg .node polygon,
|
|
||||||
.mermaid-svg .node path {
|
|
||||||
fill: #f8fafc;
|
|
||||||
stroke: #94a3b8;
|
|
||||||
stroke-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 基本填充和背景色 */
|
|
||||||
.mermaid-svg .basic {
|
|
||||||
fill: #f8fafc;
|
|
||||||
stroke: #94a3b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg .labelBkg {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 集群样式 */
|
|
||||||
.mermaid-svg .cluster rect {
|
|
||||||
fill: #f1f5f9;
|
|
||||||
stroke: #cbd5e1;
|
|
||||||
stroke-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 边标签样式 */
|
|
||||||
.mermaid-svg .edgeLabel rect {
|
|
||||||
fill: transparent;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文本样式 */
|
|
||||||
.mermaid-svg text {
|
|
||||||
fill: #334155;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg .label {
|
|
||||||
color: #334155;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg .nodeLabel {
|
|
||||||
color: #334155;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg .cluster text {
|
|
||||||
fill: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 连线样式 */
|
|
||||||
.mermaid-svg .edgePath .path {
|
|
||||||
stroke: #94a3b8;
|
|
||||||
stroke-width: 1.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg .flowchart-link {
|
|
||||||
stroke: #94a3b8;
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg marker {
|
|
||||||
fill: #94a3b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 箭头路径 */
|
|
||||||
.mermaid-svg .arrowMarkerPath {
|
|
||||||
fill: #94a3b8;
|
|
||||||
stroke: #94a3b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 特殊节点样式 */
|
|
||||||
.mermaid-svg .node.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-svg .node.clickable:hover rect,
|
|
||||||
.mermaid-svg .node.clickable:hover circle,
|
|
||||||
.mermaid-svg .node.clickable:hover ellipse,
|
|
||||||
.mermaid-svg .node.clickable:hover polygon {
|
|
||||||
stroke-width: 2px;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ======= 暗色主题样式 ======= */
|
|
||||||
[data-theme="dark"] .mermaid-svg {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .node rect,
|
|
||||||
[data-theme="dark"] .mermaid-svg .node circle,
|
|
||||||
[data-theme="dark"] .mermaid-svg .node ellipse,
|
|
||||||
[data-theme="dark"] .mermaid-svg .node polygon,
|
|
||||||
[data-theme="dark"] .mermaid-svg .node path {
|
|
||||||
fill: #1e293b;
|
|
||||||
stroke: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .basic {
|
|
||||||
fill: #1e293b;
|
|
||||||
stroke: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .labelBkg {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .cluster rect {
|
|
||||||
fill: #0f172a;
|
|
||||||
stroke: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .edgeLabel rect {
|
|
||||||
fill: transparent;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg text {
|
|
||||||
fill: #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .label {
|
|
||||||
color: #e2e8f0;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .nodeLabel {
|
|
||||||
color: #e2e8f0;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .cluster text {
|
|
||||||
fill: #cbd5e1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .edgePath .path {
|
|
||||||
stroke: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .flowchart-link {
|
|
||||||
stroke: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg marker {
|
|
||||||
fill: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .arrowMarkerPath {
|
|
||||||
fill: #64748b;
|
|
||||||
stroke: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 序列图特殊样式 */
|
|
||||||
[data-theme="dark"] .mermaid-svg .actor {
|
|
||||||
fill: #334155 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .messageLine0 {
|
|
||||||
stroke: #94a3b8 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .messageLine1 {
|
|
||||||
stroke: #94a3b8 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg #arrowhead path {
|
|
||||||
fill: #94a3b8 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 甘特图特殊样式 */
|
|
||||||
[data-theme="dark"] .mermaid-svg .section0 {
|
|
||||||
fill: rgba(255, 255, 255, 0.1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .section1,
|
|
||||||
[data-theme="dark"] .mermaid-svg .section2 {
|
|
||||||
fill: rgba(255, 255, 255, 0.07) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .mermaid-svg .task0,
|
|
||||||
[data-theme="dark"] .mermaid-svg .task1,
|
|
||||||
[data-theme="dark"] .mermaid-svg .task2,
|
|
||||||
[data-theme="dark"] .mermaid-svg .task3 {
|
|
||||||
fill: #1e3a8a !important;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user