优化样式,修复文章dom绑定问题
This commit is contained in:
parent
0d114ed070
commit
e859a1294e
@ -19,6 +19,8 @@ const {
|
||||
transitionDuration = 700, // 默认动画时间(毫秒)
|
||||
} = Astro.props;
|
||||
|
||||
// 导入外部CSS样式文件
|
||||
import "../styles/theme-toggle.css";
|
||||
---
|
||||
|
||||
<button
|
||||
@ -62,63 +64,6 @@ const {
|
||||
<span id="ripple-container" class="absolute inset-0 pointer-events-none z-0"></span>
|
||||
</button>
|
||||
|
||||
<style is:global>
|
||||
/* 波纹效果相关样式 */
|
||||
@keyframes ripple-effect {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0.8;
|
||||
}
|
||||
to {
|
||||
transform: scale(10);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-ripple {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--theme-ripple-color, 100, 100, 100), 0.15);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
pointer-events: none;
|
||||
transform-origin: center;
|
||||
animation: ripple-effect 800ms ease-out forwards;
|
||||
}
|
||||
|
||||
/* 暗色模式下使用不同颜色变量 */
|
||||
.dark .theme-ripple {
|
||||
background-color: rgba(var(--theme-ripple-color, 200, 200, 200), 0.15);
|
||||
}
|
||||
|
||||
/* View Transitions 样式控制 */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none !important;
|
||||
mix-blend-mode: normal !important;
|
||||
isolation: auto !important;
|
||||
}
|
||||
|
||||
/* 新增特殊模式样式 */
|
||||
html.theme-transition-active {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
::view-transition-old(root) {
|
||||
z-index: 999 !important;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
/* 设置主题容器在移动设备上的样式 */
|
||||
#theme-toggle-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
// 主题切换逻辑 - 自销毁模式
|
||||
(function() {
|
||||
@ -165,7 +110,7 @@ const {
|
||||
// 添加事件监听器并记录,方便后续统一清理
|
||||
function addListener(element, eventType, handler, options) {
|
||||
if (!element) {
|
||||
console.warn(`主题切换尝试为不存在的元素添加事件:`, eventType);
|
||||
console.error(`主题切换尝试为不存在的元素添加事件:`, eventType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -180,14 +125,8 @@ const {
|
||||
if (window._themeTransition && typeof window._themeTransition.skipTransition === 'function') {
|
||||
try {
|
||||
window._themeTransition.skipTransition();
|
||||
console.debug(`主题切换清理阶段取消transition成功`);
|
||||
} catch (err) {
|
||||
// 降级为debug日志
|
||||
if (err.name === 'AbortError') {
|
||||
console.debug(`主题切换清理阶段: 过渡已被跳过,这是正常现象`);
|
||||
} else {
|
||||
console.debug(`主题切换清理阶段取消transition出错:`, err);
|
||||
}
|
||||
// 忽略AbortError,这是正常现象
|
||||
} finally {
|
||||
window._themeTransition = null;
|
||||
}
|
||||
@ -336,8 +275,7 @@ const {
|
||||
window._themeTransition.skipTransition();
|
||||
}
|
||||
} catch (e) {
|
||||
// 降级为debug日志,不需要显示警告
|
||||
console.debug(`主题切换取消先前过渡失败:`, e);
|
||||
// 忽略取消先前过渡的错误
|
||||
} finally {
|
||||
// 无论成功与否,都清除引用
|
||||
window._themeTransition = null;
|
||||
@ -470,7 +408,7 @@ const {
|
||||
// 最后执行用户回调
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error(`主题切换 回调执行出错:`, err);
|
||||
console.error(`主题切换回调执行出错:`, err);
|
||||
}
|
||||
};
|
||||
|
||||
@ -504,20 +442,14 @@ const {
|
||||
// 等待过渡准备完成
|
||||
await readyPromise;
|
||||
} catch (err) {
|
||||
// 超时处理 - 只记录警告日志,继续执行动画
|
||||
if (err.message === 'Ready timeout') {
|
||||
console.debug(`主题切换过渡准备超时,使用备用策略`);
|
||||
// 收缩模式时此处超时更常见,立即使用备用方案
|
||||
if (animationType === TRANSITION_MODES.SHRINK) {
|
||||
// 超时处理或过渡中断,使用备用方案
|
||||
if (err.message === 'Ready timeout' && animationType === TRANSITION_MODES.SHRINK) {
|
||||
applyFallbackAnimation();
|
||||
return; // 跳过后续代码,使用备用方案
|
||||
}
|
||||
} else if (err.name === 'AbortError') {
|
||||
// 如果是AbortError,我们也立即使用备用方案
|
||||
applyFallbackAnimation();
|
||||
return; // 跳过后续代码,使用备用方案
|
||||
} else {
|
||||
console.debug(`主题切换过渡准备错误:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,18 +566,9 @@ const {
|
||||
}
|
||||
}, TOTAL_TRANSITION_TIME);
|
||||
} catch (error) {
|
||||
// AbortError是正常的,我们完全可以忽略它,但仍保留调试日志
|
||||
if (error.name === 'AbortError') {
|
||||
// 降级为debug级别日志,不要吓到用户
|
||||
console.debug(`主题切换过渡被跳过,这是正常现象`);
|
||||
// 使用备用动画方案
|
||||
applyFallbackAnimation();
|
||||
} else if (error.message === 'Ready timeout') {
|
||||
// 超时是我们主动抛出的,只需要降级处理
|
||||
console.debug(`主题切换过渡准备超时,继续执行动画`);
|
||||
|
||||
// 即使ready超时,我们仍然可以应用样式
|
||||
// 这里可以直接调用备用方案应用动画
|
||||
// 处理错误情况
|
||||
if (error.name === 'AbortError' || error.message === 'Ready timeout') {
|
||||
// AbortError或超时是正常的,使用备用动画方案
|
||||
applyFallbackAnimation();
|
||||
} else {
|
||||
// 只有真正意外的错误才输出error级别日志
|
||||
@ -766,7 +689,7 @@ const {
|
||||
}
|
||||
}, TOTAL_TRANSITION_TIME);
|
||||
} catch (err) {
|
||||
console.debug(`主题切换应用备用动画方案出错:`, err);
|
||||
console.error(`主题切换应用备用动画方案出错:`, err);
|
||||
}
|
||||
};
|
||||
|
||||
@ -783,9 +706,7 @@ const {
|
||||
window._themeTransition = null;
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.name === 'AbortError') {
|
||||
console.debug(`主题切换过渡被中断: 这是正常现象,可能是新的过渡开始或页面导航变化`);
|
||||
} else {
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error(`主题切换过渡动画错误:`, error);
|
||||
}
|
||||
// 确保标记类被移除
|
||||
@ -810,7 +731,7 @@ const {
|
||||
const themeToggleButtons = document.querySelectorAll("#theme-toggle-button");
|
||||
|
||||
if (!themeToggleButtons.length) {
|
||||
console.warn(`主题切换未找到主题切换按钮`);
|
||||
console.error(`主题切换未找到主题切换按钮`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -838,8 +759,6 @@ const {
|
||||
ripple.parentNode.removeChild(ripple);
|
||||
}
|
||||
});
|
||||
|
||||
console.debug('主题切换: 状态已重置');
|
||||
};
|
||||
|
||||
// 监听Astro的页面切换事件
|
||||
@ -869,7 +788,6 @@ const {
|
||||
if (window._themeTransitioning &&
|
||||
(e.target.closest('#theme-toggle-button') ||
|
||||
e.target.closest('#theme-toggle-container'))) {
|
||||
console.debug('全局拦截: 主题切换正在进行中,忽略额外点击');
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
@ -930,25 +848,19 @@ const {
|
||||
|
||||
// 第1层保护:全局事件防抖
|
||||
if (isThrottled()) {
|
||||
console.debug('主题切换: 防抖拦截,忽略点击');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第2层保护:transitioning状态检查
|
||||
if (window._themeTransitioning) {
|
||||
console.debug('主题切换: 已有过渡进行中,忽略点击');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第3层保护:视图过渡检查
|
||||
if (window._themeTransition) {
|
||||
console.debug('主题切换: 视图过渡已存在,忽略点击');
|
||||
return;
|
||||
}
|
||||
|
||||
// 所有检查通过后,开始处理主题切换
|
||||
console.debug('主题切换: 开始处理');
|
||||
|
||||
// 立即设置transitioning状态,阻止后续点击
|
||||
window._themeTransitioning = true;
|
||||
|
||||
@ -1022,7 +934,6 @@ const {
|
||||
// 过渡完成后恢复状态
|
||||
setTimeout(() => {
|
||||
window._themeTransitioning = false;
|
||||
console.debug('主题切换: 过渡完成,恢复状态');
|
||||
}, ANIMATION_BUFFER); // 添加缓冲时间
|
||||
}).catch(error => {
|
||||
// 出现错误时强制执行主题切换以确保功能可用
|
||||
@ -1030,20 +941,16 @@ const {
|
||||
console.error(`主题切换过渡动画错误:`, error);
|
||||
// 如果不是因为过渡被跳过而是真正的错误,直接执行主题切换
|
||||
safeThemeChangeCallback();
|
||||
} else {
|
||||
console.debug(`主题切换过渡被跳过,这是正常现象`);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
window._themeTransitioning = false;
|
||||
console.debug('主题切换: 过渡被取消,恢复状态');
|
||||
}, ANIMATION_BUFFER);
|
||||
});
|
||||
|
||||
// 设置防抖保底 - 防止transition.finished不触发导致状态卡死
|
||||
transitionTimeout = setTimeout(() => {
|
||||
window._themeTransitioning = false;
|
||||
console.debug('主题切换: 过渡超时,强制恢复状态');
|
||||
}, TOTAL_TRANSITION_TIME + 200); // 总过渡时间加额外缓冲
|
||||
} catch (err) {
|
||||
// 即使发生错误,也要确保主题能切换
|
||||
@ -1070,7 +977,6 @@ const {
|
||||
|
||||
// 确保状态被重置
|
||||
window._themeTransitioning = false;
|
||||
console.debug('主题切换: 发生错误,恢复状态');
|
||||
}
|
||||
};
|
||||
|
||||
@ -1154,7 +1060,6 @@ const {
|
||||
document._themeToggleCleanedUp = true;
|
||||
// 执行清理
|
||||
selfDestruct();
|
||||
console.debug('主题切换: 自动清理已执行');
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
@ -96,7 +96,10 @@ let relatedArticles = allArticles
|
||||
|
||||
return hasCommonTags;
|
||||
})
|
||||
.sort((a: ArticleEntry, b: ArticleEntry) => b.data.date.getTime() - a.data.date.getTime())
|
||||
.sort(
|
||||
(a: ArticleEntry, b: ArticleEntry) =>
|
||||
b.data.date.getTime() - a.data.date.getTime(),
|
||||
)
|
||||
.slice(0, 3);
|
||||
|
||||
// 跟踪相关文章的匹配方式: "tag", "directory", "latest"
|
||||
@ -119,7 +122,10 @@ if (relatedArticles.length < 3) {
|
||||
a.id.startsWith(currentPath + "/") &&
|
||||
!relatedArticles.some((r: ArticleEntry) => r.id === a.id),
|
||||
)
|
||||
.sort((a: ArticleEntry, b: ArticleEntry) => b.data.date.getTime() - a.data.date.getTime())
|
||||
.sort(
|
||||
(a: ArticleEntry, b: ArticleEntry) =>
|
||||
b.data.date.getTime() - a.data.date.getTime(),
|
||||
)
|
||||
.slice(0, 3 - relatedArticles.length);
|
||||
|
||||
if (dirRelatedArticles.length > 0) {
|
||||
@ -136,9 +142,14 @@ if (relatedArticles.length < 3) {
|
||||
if (relatedArticles.length < 3) {
|
||||
const latestArticles = allArticles
|
||||
.filter(
|
||||
(a: ArticleEntry) => a.id !== article.id && !relatedArticles.some((r: ArticleEntry) => r.id === a.id),
|
||||
(a: ArticleEntry) =>
|
||||
a.id !== article.id &&
|
||||
!relatedArticles.some((r: ArticleEntry) => r.id === a.id),
|
||||
)
|
||||
.sort(
|
||||
(a: ArticleEntry, b: ArticleEntry) =>
|
||||
b.data.date.getTime() - a.data.date.getTime(),
|
||||
)
|
||||
.sort((a: ArticleEntry, b: ArticleEntry) => b.data.date.getTime() - a.data.date.getTime())
|
||||
.slice(0, 3 - relatedArticles.length);
|
||||
|
||||
if (latestArticles.length > 0) {
|
||||
@ -167,7 +178,7 @@ function generateTableOfContents(headings: Heading[]) {
|
||||
}
|
||||
|
||||
// 查找最低级别的标题(数值最小)
|
||||
const minDepth = Math.min(...headings.map(h => h.depth));
|
||||
const minDepth = Math.min(...headings.map((h) => h.depth));
|
||||
|
||||
let tocHtml = '<ul class="space-y-2">';
|
||||
|
||||
@ -192,7 +203,7 @@ function generateTableOfContents(headings: Heading[]) {
|
||||
</li>`;
|
||||
});
|
||||
|
||||
tocHtml += '</ul>';
|
||||
tocHtml += "</ul>";
|
||||
return tocHtml;
|
||||
}
|
||||
|
||||
@ -376,9 +387,22 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
<div
|
||||
class="border-b border-secondary-100 dark:border-gray-700 p-4 pb-3 sticky top-0 bg-white dark:bg-gray-800 bg-opacity-95 dark:bg-opacity-95 backdrop-filter backdrop-blur-sm z-10 rounded-t-xl"
|
||||
>
|
||||
<h3 class="font-bold text-primary-700 dark:text-primary-400 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
<h3
|
||||
class="font-bold text-primary-700 dark:text-primary-400 flex items-center gap-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
></path>
|
||||
</svg>
|
||||
文章目录
|
||||
</h3>
|
||||
@ -413,8 +437,19 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
>
|
||||
<div class="article-card-content">
|
||||
<div class="article-card-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="article-card-body">
|
||||
@ -430,11 +465,14 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
</p>
|
||||
)}
|
||||
<div class="article-card-footer">
|
||||
<time datetime={relatedArticle.data.date.toISOString()} class="article-card-date">
|
||||
<time
|
||||
datetime={relatedArticle.data.date.toISOString()}
|
||||
class="article-card-date"
|
||||
>
|
||||
{relatedArticle.data.date.toLocaleDateString("zh-CN", {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</time>
|
||||
<span class="article-card-read-more">阅读全文</span>
|
||||
@ -469,20 +507,10 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<!-- 文章页面脚本 -->
|
||||
<script is:inline>
|
||||
<!-- 文章页面脚本 -->
|
||||
<script is:inline>
|
||||
// 文章页面交互脚本 - 自销毁模式
|
||||
(function() {
|
||||
// 如果不是文章页面,立即退出,不执行任何代码
|
||||
if (!document.querySelector("article")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scriptInstanceId = Date.now();
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 检测到文章页面,开始初始化`);
|
||||
|
||||
(function () {
|
||||
// 集中管理所有事件监听器
|
||||
const allListeners = [];
|
||||
|
||||
@ -495,11 +523,9 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
// 添加事件监听器并记录,方便后续统一清理
|
||||
function addListener(element, eventType, handler, options) {
|
||||
if (!element) {
|
||||
console.warn(`[文章脚本:${scriptInstanceId}] 尝试为不存在的元素添加事件:`, eventType);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 添加事件监听器: ${eventType} 到`, element.tagName || "Window/Document");
|
||||
element.addEventListener(eventType, handler, options);
|
||||
allListeners.push({ element, eventType, handler, options });
|
||||
return handler;
|
||||
@ -507,16 +533,12 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 统一的清理函数,执行完整清理并自销毁
|
||||
function selfDestruct() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 执行自销毁流程`);
|
||||
|
||||
// 1. 先移除普通事件监听器
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 移除常规监听器,数量:`, allListeners.length);
|
||||
allListeners.forEach(({ element, eventType, handler, options }) => {
|
||||
try {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 移除事件监听器: ${eventType} 从`, element.tagName || "Window/Document");
|
||||
element.removeEventListener(eventType, handler, options);
|
||||
} catch (err) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 移除事件监听器出错:`, err);
|
||||
console.error("移除事件监听器出错:", err);
|
||||
}
|
||||
});
|
||||
|
||||
@ -524,12 +546,11 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
allListeners.length = 0;
|
||||
|
||||
// 2. 执行特殊清理任务
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 执行特殊清理任务,数量:`, customCleanupTasks.length);
|
||||
customCleanupTasks.forEach(task => {
|
||||
customCleanupTasks.forEach((task) => {
|
||||
try {
|
||||
task();
|
||||
} catch (err) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 执行特殊清理任务出错:`, err);
|
||||
console.error("执行特殊清理任务出错:", err);
|
||||
}
|
||||
});
|
||||
|
||||
@ -537,70 +558,71 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
customCleanupTasks.length = 0;
|
||||
|
||||
// 3. 最后移除清理事件监听器自身
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 移除清理监听器,数量:`, cleanupListeners.length);
|
||||
cleanupListeners.forEach(({ element, eventType, handler, options }) => {
|
||||
try {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 移除清理监听器: ${eventType} 从`, element.tagName || "Window/Document");
|
||||
element.removeEventListener(eventType, handler, options);
|
||||
} catch (err) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 移除清理监听器出错:`, err);
|
||||
console.error("移除清理监听器出错:", err);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 完全销毁完成`);
|
||||
}
|
||||
|
||||
// 注册清理事件,并保存引用
|
||||
function registerCleanupEvents() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 注册清理事件`);
|
||||
|
||||
// 创建一次性事件处理函数
|
||||
const beforeSwapHandler = () => {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] astro:before-swap 触发,执行自销毁`);
|
||||
selfDestruct();
|
||||
};
|
||||
|
||||
const beforeUnloadHandler = () => {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] beforeunload 触发,执行自销毁`);
|
||||
selfDestruct();
|
||||
};
|
||||
|
||||
// 添加清理事件监听器并保存引用
|
||||
document.addEventListener("astro:before-swap", beforeSwapHandler, { once: true });
|
||||
window.addEventListener("beforeunload", beforeUnloadHandler, { once: true });
|
||||
document.addEventListener("astro:before-swap", beforeSwapHandler, {
|
||||
once: true,
|
||||
});
|
||||
window.addEventListener("beforeunload", beforeUnloadHandler, {
|
||||
once: true,
|
||||
});
|
||||
|
||||
// 保存清理事件引用,用于完全销毁
|
||||
cleanupListeners.push(
|
||||
{ element: document, eventType: "astro:before-swap", handler: beforeSwapHandler, options: { once: true } },
|
||||
{ element: window, eventType: "beforeunload", handler: beforeUnloadHandler, options: { once: true } }
|
||||
{
|
||||
element: document,
|
||||
eventType: "astro:before-swap",
|
||||
handler: beforeSwapHandler,
|
||||
options: { once: true },
|
||||
},
|
||||
{
|
||||
element: window,
|
||||
eventType: "beforeunload",
|
||||
handler: beforeUnloadHandler,
|
||||
options: { once: true },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 初始化所有功能
|
||||
function initializeFeatures() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 开始初始化各功能`);
|
||||
|
||||
// 1. 代码块复制功能
|
||||
function setupCodeCopy() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 初始化代码复制功能`);
|
||||
const copyButtons = document.querySelectorAll('.code-block-copy');
|
||||
const copyButtons = document.querySelectorAll(".code-block-copy");
|
||||
if (copyButtons.length === 0) {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 未找到代码复制按钮`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 找到代码复制按钮数量:`, copyButtons.length);
|
||||
copyButtons.forEach(button => {
|
||||
addListener(button, 'click', async () => {
|
||||
copyButtons.forEach((button) => {
|
||||
addListener(button, "click", async () => {
|
||||
try {
|
||||
const encodedCode = button.getAttribute('data-code');
|
||||
const encodedCode = button.getAttribute("data-code");
|
||||
if (!encodedCode) return;
|
||||
|
||||
const code = atob(encodedCode);
|
||||
await navigator.clipboard.writeText(code);
|
||||
|
||||
const originalHTML = button.innerHTML;
|
||||
button.classList.add('copied');
|
||||
button.classList.add("copied");
|
||||
button.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>
|
||||
@ -609,11 +631,11 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
button.classList.remove('copied');
|
||||
button.classList.remove("copied");
|
||||
button.innerHTML = originalHTML;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 复制失败:`, err);
|
||||
console.error("复制失败:", err);
|
||||
button.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>
|
||||
@ -638,18 +660,19 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 2. 阅读进度条
|
||||
function setupProgressBar() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 初始化阅读进度条`);
|
||||
const progressBar = document.getElementById("progress-bar");
|
||||
const backToTopButton = document.getElementById("back-to-top");
|
||||
|
||||
if (!progressBar) {
|
||||
console.warn(`[文章脚本:${scriptInstanceId}] 未找到进度条元素`);
|
||||
return;
|
||||
}
|
||||
|
||||
function updateReadingProgress() {
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
const scrollTop =
|
||||
window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollHeight =
|
||||
document.documentElement.scrollHeight -
|
||||
document.documentElement.clientHeight;
|
||||
const progress = (scrollTop / scrollHeight) * 100;
|
||||
|
||||
progressBar.style.width = `${progress}%`;
|
||||
@ -659,23 +682,23 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
backToTopButton.classList.add(
|
||||
"opacity-100",
|
||||
"visible",
|
||||
"translate-y-0"
|
||||
"translate-y-0",
|
||||
);
|
||||
backToTopButton.classList.remove(
|
||||
"opacity-0",
|
||||
"invisible",
|
||||
"translate-y-5"
|
||||
"translate-y-5",
|
||||
);
|
||||
} else {
|
||||
backToTopButton.classList.add(
|
||||
"opacity-0",
|
||||
"invisible",
|
||||
"translate-y-5"
|
||||
"translate-y-5",
|
||||
);
|
||||
backToTopButton.classList.remove(
|
||||
"opacity-100",
|
||||
"visible",
|
||||
"translate-y-0"
|
||||
"translate-y-0",
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -698,12 +721,10 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 3. 目录交互
|
||||
function setupTableOfContents() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 初始化目录交互`);
|
||||
const tocContent = document.getElementById("toc-content");
|
||||
const tocPanel = document.querySelector("#toc-panel");
|
||||
|
||||
if (!tocPanel || !tocContent) {
|
||||
console.warn(`[文章脚本:${scriptInstanceId}] 未找到目录面板或内容`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -723,9 +744,8 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 处理目录链接点击跳转
|
||||
const tocLinks = tocContent.querySelectorAll("a");
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 找到目录链接数量:`, tocLinks.length);
|
||||
|
||||
tocLinks.forEach(link => {
|
||||
tocLinks.forEach((link) => {
|
||||
addListener(link, "click", (e) => {
|
||||
e.preventDefault();
|
||||
const targetId = link.getAttribute("href")?.substring(1);
|
||||
@ -734,16 +754,25 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
const targetElement = document.getElementById(targetId);
|
||||
if (targetElement) {
|
||||
const offset = 100;
|
||||
const targetPosition = targetElement.getBoundingClientRect().top + window.scrollY - offset;
|
||||
const targetPosition =
|
||||
targetElement.getBoundingClientRect().top +
|
||||
window.scrollY -
|
||||
offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: "smooth",
|
||||
});
|
||||
|
||||
targetElement.classList.add("bg-primary-50", "dark:bg-primary-900/20");
|
||||
targetElement.classList.add(
|
||||
"bg-primary-50",
|
||||
"dark:bg-primary-900/20",
|
||||
);
|
||||
setTimeout(() => {
|
||||
targetElement.classList.remove("bg-primary-50", "dark:bg-primary-900/20");
|
||||
targetElement.classList.remove(
|
||||
"bg-primary-50",
|
||||
"dark:bg-primary-900/20",
|
||||
);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
@ -752,19 +781,24 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
// 监听滚动以更新当前活动的目录项
|
||||
const article = document.querySelector("article");
|
||||
if (!article) {
|
||||
console.warn(`[文章脚本:${scriptInstanceId}] 未找到文章内容元素`);
|
||||
return;
|
||||
}
|
||||
|
||||
let ticking = false;
|
||||
|
||||
function updateActiveHeading() {
|
||||
const headings = Array.from(article.querySelectorAll("h1, h2, h3, h4, h5, h6"));
|
||||
const headings = Array.from(
|
||||
article.querySelectorAll("h1, h2, h3, h4, h5, h6"),
|
||||
);
|
||||
const tocLinks = Array.from(tocContent.querySelectorAll("a"));
|
||||
|
||||
// 清除所有活动状态
|
||||
tocLinks.forEach((link) => {
|
||||
link.classList.remove("text-primary-600", "dark:text-primary-400", "font-medium");
|
||||
link.classList.remove(
|
||||
"text-primary-600",
|
||||
"dark:text-primary-400",
|
||||
"font-medium",
|
||||
);
|
||||
});
|
||||
|
||||
// 找出当前可见的标题
|
||||
@ -772,7 +806,8 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
let currentHeading = null;
|
||||
|
||||
for (const heading of headings) {
|
||||
const headingTop = heading.getBoundingClientRect().top + window.scrollY;
|
||||
const headingTop =
|
||||
heading.getBoundingClientRect().top + window.scrollY;
|
||||
if (headingTop <= scrollPosition) {
|
||||
currentHeading = heading;
|
||||
} else {
|
||||
@ -782,26 +817,33 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 高亮当前标题对应的目录项
|
||||
if (currentHeading) {
|
||||
const id = currentHeading.getAttribute('id');
|
||||
const id = currentHeading.getAttribute("id");
|
||||
if (id) {
|
||||
const activeLink = tocLinks.find(
|
||||
(link) => link.getAttribute("href") === `#${id}`
|
||||
(link) => link.getAttribute("href") === `#${id}`,
|
||||
);
|
||||
if (activeLink) {
|
||||
// 高亮当前目录项
|
||||
activeLink.classList.add("text-primary-600", "dark:text-primary-400", "font-medium");
|
||||
activeLink.classList.add(
|
||||
"text-primary-600",
|
||||
"dark:text-primary-400",
|
||||
"font-medium",
|
||||
);
|
||||
|
||||
// 可选: 确保当前激活的目录项在可视区域内
|
||||
const tocContainer = tocContent.querySelector('ul');
|
||||
const tocContainer = tocContent.querySelector("ul");
|
||||
if (tocContainer) {
|
||||
const linkOffsetTop = activeLink.offsetTop;
|
||||
const containerScrollTop = tocContainer.scrollTop;
|
||||
const containerHeight = tocContainer.clientHeight;
|
||||
|
||||
// 如果当前项不在视口内,滚动目录
|
||||
if (linkOffsetTop < containerScrollTop ||
|
||||
linkOffsetTop > containerScrollTop + containerHeight) {
|
||||
tocContainer.scrollTop = linkOffsetTop - containerHeight / 2;
|
||||
if (
|
||||
linkOffsetTop < containerScrollTop ||
|
||||
linkOffsetTop > containerScrollTop + containerHeight
|
||||
) {
|
||||
tocContainer.scrollTop =
|
||||
linkOffsetTop - containerHeight / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -824,70 +866,74 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 4. Mermaid图表渲染
|
||||
function setupMermaid() {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 检查Mermaid图表`);
|
||||
// 查找所有mermaid代码块
|
||||
const mermaidBlocks = document.querySelectorAll(
|
||||
'pre.language-mermaid, pre > code.language-mermaid, .mermaid'
|
||||
"pre.language-mermaid, pre > code.language-mermaid, .mermaid",
|
||||
);
|
||||
|
||||
if (mermaidBlocks.length === 0) {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 未找到Mermaid图表`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 找到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(`[文章脚本:${scriptInstanceId}] Mermaid库加载成功`);
|
||||
const script = document.createElement("script");
|
||||
script.src =
|
||||
"https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
|
||||
|
||||
script.onload = function () {
|
||||
if (!window.mermaid) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] Mermaid库加载后window.mermaid不存在`);
|
||||
console.error("Mermaid库加载后window.mermaid不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化mermaid配置
|
||||
window.mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: 'default',
|
||||
securityLevel: 'loose'
|
||||
theme: "default",
|
||||
securityLevel: "loose",
|
||||
});
|
||||
|
||||
// 将所有mermaid代码块转换为可渲染的格式
|
||||
mermaidBlocks.forEach((block, index) => {
|
||||
// 获取mermaid代码
|
||||
let code = '';
|
||||
let code = "";
|
||||
|
||||
// 检查元素类型并相应处理
|
||||
if (block.tagName === 'CODE' && block.classList.contains('language-mermaid')) {
|
||||
if (
|
||||
block.tagName === "CODE" &&
|
||||
block.classList.contains("language-mermaid")
|
||||
) {
|
||||
// 处理 code.language-mermaid 元素
|
||||
code = block.textContent || '';
|
||||
const pre = block.closest('pre');
|
||||
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;
|
||||
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')) {
|
||||
} 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;
|
||||
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') {
|
||||
} 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;
|
||||
code = block.textContent || "";
|
||||
const div = document.createElement("div");
|
||||
div.className = "mermaid";
|
||||
div.id = "mermaid-diagram-" + index;
|
||||
div.textContent = code;
|
||||
block.parentNode.replaceChild(div, block);
|
||||
}
|
||||
@ -895,22 +941,22 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 初始化渲染
|
||||
try {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 开始渲染Mermaid图表`);
|
||||
window.mermaid.run().catch(err => {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] Mermaid渲染出错:`, err);
|
||||
window.mermaid.run().catch((err) => {
|
||||
console.error("Mermaid渲染出错:", err);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 初始化Mermaid渲染失败:`, error);
|
||||
console.error("初始化Mermaid渲染失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
script.onerror = function() {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 加载Mermaid库失败`);
|
||||
script.onerror = function () {
|
||||
console.error("加载Mermaid库失败");
|
||||
// 显示错误信息
|
||||
mermaidBlocks.forEach(block => {
|
||||
if (block.tagName === 'CODE') block = block.closest('pre');
|
||||
mermaidBlocks.forEach((block) => {
|
||||
if (block.tagName === "CODE") block = block.closest("pre");
|
||||
if (block) {
|
||||
block.innerHTML = '<div class="mermaid-error-message">无法加载Mermaid图表库</div>';
|
||||
block.innerHTML =
|
||||
'<div class="mermaid-error-message">无法加载Mermaid图表库</div>';
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -919,8 +965,6 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 添加Mermaid清理任务
|
||||
customCleanupTasks.push(() => {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 执行Mermaid特殊清理`);
|
||||
|
||||
// 移除脚本标签
|
||||
if (script.parentNode) {
|
||||
script.parentNode.removeChild(script);
|
||||
@ -928,15 +972,14 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
|
||||
// 清除全局mermaid对象
|
||||
if (window.mermaid) {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 清除window.mermaid对象`);
|
||||
try {
|
||||
// 尝试清理mermaid内部状态
|
||||
if (typeof window.mermaid.destroy === 'function') {
|
||||
if (typeof window.mermaid.destroy === "function") {
|
||||
window.mermaid.destroy();
|
||||
}
|
||||
window.mermaid = undefined;
|
||||
} catch (e) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 清理mermaid对象出错:`, e);
|
||||
console.error("清理mermaid对象出错:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -944,20 +987,21 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
try {
|
||||
// 移除所有可能的mermaid样式和元素
|
||||
const mermaidElements = [
|
||||
'#mermaid-style',
|
||||
'#mermaid-cloned-styles',
|
||||
'.mermaid-svg-reference',
|
||||
'style[id^="mermaid-"]'
|
||||
"#mermaid-style",
|
||||
"#mermaid-cloned-styles",
|
||||
".mermaid-svg-reference",
|
||||
'style[id^="mermaid-"]',
|
||||
];
|
||||
|
||||
document.querySelectorAll(mermaidElements.join(', ')).forEach(el => {
|
||||
document
|
||||
.querySelectorAll(mermaidElements.join(", "))
|
||||
.forEach((el) => {
|
||||
if (el && el.parentNode) {
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 移除Mermaid元素:`, el.id || el.className);
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`[文章脚本:${scriptInstanceId}] 清理Mermaid元素时出错:`, e);
|
||||
console.error("清理Mermaid元素时出错:", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -967,12 +1011,11 @@ const tableOfContents = generateTableOfContents(headings);
|
||||
setupProgressBar();
|
||||
setupTableOfContents();
|
||||
setupMermaid();
|
||||
|
||||
console.log(`[文章脚本:${scriptInstanceId}] 所有功能初始化完成`);
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
registerCleanupEvents();
|
||||
initializeFeatures();
|
||||
})();
|
||||
</script>
|
||||
</script>
|
||||
</Layout>
|
||||
|
@ -1,34 +1,6 @@
|
||||
: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文本颜色统一 */
|
||||
@ -39,10 +11,9 @@
|
||||
.mermaid .loopText,
|
||||
.mermaid .noteText,
|
||||
.mermaid .taskText {
|
||||
color: var(--mermaid-text-color) !important;
|
||||
fill: var(--mermaid-text-color) !important;
|
||||
color: var(--color-secondary-800) !important;
|
||||
fill: var(--color-secondary-800) !important;
|
||||
font-family: inherit !important;
|
||||
transition: color 0.3s ease, fill 0.3s ease;
|
||||
}
|
||||
|
||||
/* 节点样式设置 */
|
||||
@ -51,10 +22,9 @@
|
||||
.mermaid .node ellipse,
|
||||
.mermaid .node polygon,
|
||||
.mermaid .node path {
|
||||
fill: var(--mermaid-node-bg) !important;
|
||||
stroke: var(--mermaid-border-color) !important;
|
||||
fill: var(--color-gray-100) !important;
|
||||
stroke: var(--color-gray-400) !important;
|
||||
stroke-width: 1px !important;
|
||||
transition: fill 0.3s ease, stroke 0.3s ease;
|
||||
}
|
||||
|
||||
/* 连接线样式 */
|
||||
@ -63,88 +33,156 @@
|
||||
.mermaid line,
|
||||
.mermaid .messageLine0,
|
||||
.mermaid .messageLine1 {
|
||||
stroke: var(--mermaid-line-color) !important;
|
||||
stroke: var(--color-gray-600) !important;
|
||||
stroke-width: 1px !important;
|
||||
transition: stroke 0.3s ease;
|
||||
fill: none !important; /* 确保线条没有填充 */
|
||||
}
|
||||
|
||||
/* 箭头填充 */
|
||||
.mermaid .arrowheadPath,
|
||||
.mermaid marker path {
|
||||
fill: var(--mermaid-line-color) !important;
|
||||
fill: var(--color-gray-600) !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;
|
||||
fill: white !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.mermaid .titleText,
|
||||
.mermaid .classTitle,
|
||||
.mermaid .cluster-label text {
|
||||
fill: var(--mermaid-title-color) !important;
|
||||
color: var(--mermaid-title-color) !important;
|
||||
fill: var(--color-secondary-900) !important;
|
||||
color: var(--color-secondary-900) !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;
|
||||
fill: var(--color-gray-50) !important;
|
||||
stroke: var(--color-gray-400) !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;
|
||||
fill: var(--color-gray-100) !important;
|
||||
stroke: var(--color-gray-400) !important;
|
||||
stroke-width: 1px !important;
|
||||
transition: fill 0.3s ease, stroke 0.3s ease;
|
||||
}
|
||||
|
||||
.mermaid .note {
|
||||
fill: var(--mermaid-secondary-color) !important;
|
||||
fill: var(--color-primary-500) !important;
|
||||
opacity: 0.7 !important;
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
|
||||
/* 甘特图特殊样式 */
|
||||
.mermaid .section0,
|
||||
.mermaid .section2 {
|
||||
fill: var(--mermaid-section-bg) !important;
|
||||
fill: var(--color-gray-50) !important;
|
||||
opacity: 0.5 !important;
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
|
||||
.mermaid .section1,
|
||||
.mermaid .section3 {
|
||||
fill: var(--mermaid-node-bg) !important;
|
||||
fill: var(--color-gray-100) !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;
|
||||
fill: var(--color-primary-600) !important;
|
||||
stroke: var(--color-gray-400) !important;
|
||||
}
|
||||
|
||||
/* 类图特殊样式 */
|
||||
.mermaid .classLabel .label {
|
||||
fill: var(--mermaid-text-color) !important;
|
||||
color: var(--mermaid-text-color) !important;
|
||||
transition: fill 0.3s ease, color 0.3s ease;
|
||||
fill: var(--color-secondary-800) !important;
|
||||
color: var(--color-secondary-800) !important;
|
||||
}
|
||||
|
||||
/* 暗色模式样式 */
|
||||
[data-theme='dark'] .mermaid .label,
|
||||
[data-theme='dark'] .mermaid text,
|
||||
[data-theme='dark'] .mermaid span,
|
||||
[data-theme='dark'] .mermaid .messageText,
|
||||
[data-theme='dark'] .mermaid .loopText,
|
||||
[data-theme='dark'] .mermaid .noteText,
|
||||
[data-theme='dark'] .mermaid .taskText,
|
||||
[data-theme='dark'] .mermaid .classLabel .label {
|
||||
color: var(--color-secondary-300) !important;
|
||||
fill: var(--color-secondary-300) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .node rect,
|
||||
[data-theme='dark'] .mermaid .node circle,
|
||||
[data-theme='dark'] .mermaid .node ellipse,
|
||||
[data-theme='dark'] .mermaid .node polygon,
|
||||
[data-theme='dark'] .mermaid .node path,
|
||||
[data-theme='dark'] .mermaid .actor {
|
||||
fill: var(--color-dark-hover) !important;
|
||||
stroke: var(--color-gray-600) !important;
|
||||
}
|
||||
|
||||
/* 暗色模式连接线样式 - 分开处理 */
|
||||
[data-theme='dark'] .mermaid .edgePath .path,
|
||||
[data-theme='dark'] .mermaid .flowchart-link,
|
||||
[data-theme='dark'] .mermaid line,
|
||||
[data-theme='dark'] .mermaid .messageLine0,
|
||||
[data-theme='dark'] .mermaid .messageLine1 {
|
||||
stroke: var(--color-gray-400) !important;
|
||||
fill: none !important; /* 确保线条没有填充 */
|
||||
}
|
||||
|
||||
/* 暗色模式箭头样式 - 分开处理 */
|
||||
[data-theme='dark'] .mermaid .arrowheadPath,
|
||||
[data-theme='dark'] .mermaid marker path {
|
||||
fill: var(--color-gray-400) !important;
|
||||
stroke: none !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .edgeLabel rect,
|
||||
[data-theme='dark'] .mermaid .labelBox {
|
||||
fill: var(--color-dark-surface) !important;
|
||||
background-color: var(--color-dark-surface) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .titleText,
|
||||
[data-theme='dark'] .mermaid .classTitle,
|
||||
[data-theme='dark'] .mermaid .cluster-label text {
|
||||
fill: var(--color-secondary-100) !important;
|
||||
color: var(--color-secondary-100) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .cluster rect,
|
||||
[data-theme='dark'] .mermaid .cluster polygon,
|
||||
[data-theme='dark'] .mermaid .section0,
|
||||
[data-theme='dark'] .mermaid .section2 {
|
||||
fill: var(--color-dark-card) !important;
|
||||
stroke: var(--color-gray-600) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .section1,
|
||||
[data-theme='dark'] .mermaid .section3 {
|
||||
fill: var(--color-dark-hover) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .note {
|
||||
fill: var(--color-primary-300) !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .mermaid .task0,
|
||||
[data-theme='dark'] .mermaid .task1,
|
||||
[data-theme='dark'] .mermaid .task2,
|
||||
[data-theme='dark'] .mermaid .task3 {
|
||||
fill: var(--color-primary-400) !important;
|
||||
stroke: var(--color-gray-600) !important;
|
||||
}
|
@ -1,293 +1,100 @@
|
||||
/* 表格基础样式 */
|
||||
/* 表格样式 - 与全局主题配色协调 */
|
||||
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;
|
||||
margin-bottom: 1.5rem;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
/* 特定表格类型样式 */
|
||||
table.feature-comparison td,
|
||||
.feature-table td,
|
||||
.function-table td,
|
||||
.comparison-table td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.feature-comparison td:first-child,
|
||||
.feature-table td:first-child,
|
||||
.function-table td:first-child,
|
||||
.comparison-table td:first-child {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 勾号和叉号样式 */
|
||||
.tick, .yes, .true, .check-mark, .checkmark, .check, .✓ {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.cross, .no, .false, .cross-mark, .crossmark, .cross-symbol, .✗ {
|
||||
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: "✗";
|
||||
}
|
||||
|
||||
/* 状态标识符号样式 */
|
||||
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;
|
||||
}
|
||||
|
||||
/* 全局状态颜色 */
|
||||
.text-success,
|
||||
.text-green,
|
||||
.success-mark {
|
||||
color: #059669 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-danger,
|
||||
.text-red,
|
||||
.danger-mark {
|
||||
color: #dc2626 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Prose 内表格样式 */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 表格内行内样式覆盖 */
|
||||
table[style] th,
|
||||
table[style] td {
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
}
|
||||
|
||||
table[style] td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 响应式表格样式 */
|
||||
@media (max-width: 640px) {
|
||||
/* 表格容器 */
|
||||
table {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* 表格包装容器 - 注意:需要在HTML中添加div.table-wrapper包围表格 */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保标题和内容宽度一致 */
|
||||
table th,
|
||||
table td {
|
||||
min-width: 8rem;
|
||||
padding: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
thead {
|
||||
background-color: var(--color-primary-700);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 暗色模式 */
|
||||
[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;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-theme="dark"] table,
|
||||
[data-theme="dark"] th,
|
||||
[data-theme="dark"] td {
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
[data-theme="dark"] table[style] th,
|
||||
[data-theme="dark"] table[style] td,
|
||||
[data-theme="dark"] .prose table th,
|
||||
[data-theme="dark"] .prose table td {
|
||||
border-color: #475569 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] thead {
|
||||
background-color: #1e293b;
|
||||
border-bottom: 2px solid #4b6bff;
|
||||
}
|
||||
|
||||
[data-theme="dark"] th {
|
||||
border-bottom-color: #4b6bff;
|
||||
color: #adc2ff;
|
||||
background-color: transparent;
|
||||
thead th {
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
text-align: left;
|
||||
border-bottom: 2px solid var(--color-primary-800);
|
||||
border-right: 1px solid var(--color-primary-600);
|
||||
}
|
||||
|
||||
[data-theme="dark"] th:not(:last-child) {
|
||||
border-right-color: #374151;
|
||||
}
|
||||
thead th:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] td {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
tbody tr {
|
||||
background-color: white;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
[data-theme="dark"] td:not(:last-child) {
|
||||
border-right-color: #374151;
|
||||
}
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
[data-theme="dark"] tbody tr:nth-child(even) {
|
||||
background-color: #1e293b;
|
||||
}
|
||||
tbody tr:hover {
|
||||
background-color: var(--color-primary-50);
|
||||
}
|
||||
|
||||
[data-theme="dark"] tbody tr:nth-child(odd) {
|
||||
background-color: #111827;
|
||||
}
|
||||
tbody td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
border-right: 1px solid var(--color-gray-200);
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
[data-theme="dark"] tbody tr:hover {
|
||||
background-color: #272f45;
|
||||
box-shadow: inset 0 0 0 2px rgba(75, 107, 255, 0.3);
|
||||
}
|
||||
tbody td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] tbody tr:last-child td {
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色模式下的勾叉符号 */
|
||||
[data-theme="dark"] .tick,
|
||||
[data-theme="dark"] .yes,
|
||||
[data-theme="dark"] .true,
|
||||
[data-theme="dark"] .check-mark,
|
||||
[data-theme="dark"] .checkmark,
|
||||
[data-theme="dark"] .check,
|
||||
[data-theme="dark"] .✓,
|
||||
[data-theme="dark"] .text-success,
|
||||
[data-theme="dark"] .text-green,
|
||||
[data-theme="dark"] .success-mark {
|
||||
color: #10b981 !important;
|
||||
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .cross,
|
||||
[data-theme="dark"] .no,
|
||||
[data-theme="dark"] .false,
|
||||
[data-theme="dark"] .cross-mark,
|
||||
[data-theme="dark"] .crossmark,
|
||||
[data-theme="dark"] .cross-symbol,
|
||||
[data-theme="dark"] .✗,
|
||||
[data-theme="dark"] .text-danger,
|
||||
[data-theme="dark"] .text-red,
|
||||
[data-theme="dark"] .danger-mark {
|
||||
color: #ef4444 !important;
|
||||
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
[data-theme='dark'] thead {
|
||||
background-color: var(--color-primary-900);
|
||||
}
|
||||
|
||||
[data-theme='dark'] thead th {
|
||||
border-bottom: 2px solid var(--color-primary-800);
|
||||
border-right: 1px solid var(--color-primary-800);
|
||||
}
|
||||
|
||||
[data-theme='dark'] thead th:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-theme='dark'] tbody tr {
|
||||
background-color: var(--color-gray-800);
|
||||
color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
[data-theme='dark'] tbody tr:nth-child(odd) {
|
||||
background-color: var(--color-gray-900);
|
||||
}
|
||||
|
||||
[data-theme='dark'] tbody tr:hover {
|
||||
background-color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
[data-theme='dark'] tbody td {
|
||||
border-bottom: 1px solid var(--color-gray-700);
|
||||
border-right: 1px solid var(--color-gray-700);
|
||||
}
|
||||
|
||||
[data-theme='dark'] tbody td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
[data-theme='dark'] td:empty::before {
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
|
54
src/styles/theme-toggle.css
Normal file
54
src/styles/theme-toggle.css
Normal file
@ -0,0 +1,54 @@
|
||||
/* 波纹效果相关样式 */
|
||||
@keyframes ripple-effect {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0.8;
|
||||
}
|
||||
to {
|
||||
transform: scale(10);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-ripple {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--theme-ripple-color, 100, 100, 100), 0.15);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
pointer-events: none;
|
||||
transform-origin: center;
|
||||
animation: ripple-effect 800ms ease-out forwards;
|
||||
}
|
||||
|
||||
/* 暗色模式下使用不同颜色变量 */
|
||||
.dark .theme-ripple {
|
||||
background-color: rgba(var(--theme-ripple-color, 200, 200, 200), 0.15);
|
||||
}
|
||||
|
||||
/* View Transitions 样式控制 */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none !important;
|
||||
mix-blend-mode: normal !important;
|
||||
isolation: auto !important;
|
||||
}
|
||||
|
||||
/* 新增特殊模式样式 */
|
||||
html.theme-transition-active {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
::view-transition-old(root) {
|
||||
z-index: 999 !important;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
/* 设置主题容器在移动设备上的样式 */
|
||||
#theme-toggle-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
Loading…
Reference in New Issue
Block a user