预览
现在教程开始!
修改文件前请注意备份,防止修改失败无法回退
新建src/components/misc/AISummary.astro文件
--- export interface Props { content: string; } const { content } = Astro.props; // 如果没有内容,不渲染组件 if (!content || content.trim() === '') { return null; } --- {content && ( <div class="ai-summary"> <div class="ai-title"> <div class="ai-title-left"> <i>🤖</i> <span class="ai-title-text">AI 摘要</span> </div> <div class="ai-tag">fishcpy AI</div> </div> <div class="ai-explanation" data-content={content}></div> </div> )} <script> // 检查当前页面路径是否包含 "posts" function isPostsPage() { return window.location.pathname.includes('/posts/'); } // 全局函数,用于初始化AI打字效果 function initAITyping() { // 只在包含 "posts" 的页面才执行AI总结功能 if (!isPostsPage()) { return; } // 查找所有AI摘要容器 const aiSummaryContainers = document.querySelectorAll('.ai-summary'); aiSummaryContainers.forEach(container => { const textElement = container.querySelector('.ai-explanation'); if (!textElement) { return; } // 检查是否已经初始化过 if (textElement.hasAttribute('data-initialized')) { return; } const content = textElement.getAttribute('data-content'); if (!content) { return; } // 标记为已初始化 textElement.setAttribute('data-initialized', 'true'); // 清空文本内容,准备打字效果 textElement.textContent = ''; textElement.classList.remove('typing-complete'); let index = 0; const typeSpeed = 30; // 打字速度(毫秒) function typeWriter() { if (index < content.length) { textElement.textContent += content.charAt(index); index++; setTimeout(typeWriter, typeSpeed); } else { // 打字完成后隐藏光标(通过CSS控制) textElement.classList.add('typing-complete'); } } // 延迟开始打字效果 setTimeout(typeWriter, 800); }); } // 页面加载完成时初始化 function handlePageLoad() { setTimeout(initAITyping, 100); } // 监听页面导航事件(适用于Astro的客户端路由) function setupNavigationListeners() { // DOMContentLoaded事件 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', handlePageLoad); } else { handlePageLoad(); } // 监听Astro的页面导航事件 document.addEventListener('astro:page-load', handlePageLoad); // 监听浏览器的popstate事件(后退/前进按钮) window.addEventListener('popstate', handlePageLoad); // 监听pushstate和replacestate事件 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function() { originalPushState.apply(history, arguments); setTimeout(handlePageLoad, 100); }; history.replaceState = function() { originalReplaceState.apply(history, arguments); setTimeout(handlePageLoad, 100); }; } // 立即设置监听器 setupNavigationListeners(); </script>
在src/content/config.ts插入下方代码,13行下左右,注意+号要删除
tags: z.array(z.string()).optional().default([]), category: z.string().optional().nullable().default(""), lang: z.string().optional().default(""), + ai: z.string().optional().default(""), /* For internal use */ prevTitle: z.string().default(""),
在src/pages/posts/...slug.astro插入下方代码,注意+号要删除
import { profileConfig, siteConfig } from "../../config"; import { formatDateToYYYYMMDD } from "../../utils/date-utils"; import Comment from "@components/comment/index.astro"; + import AISummary from "@components/misc/AISummary.astro"; export async function getStaticPaths() { const blogEntries = await getSortedPosts(); @@ -84,6 +85,9 @@ const jsonLd = { </div> </div> + <!-- AI Summary --> + {entry.data.description && <AISummary content={entry.data.description} class="onload-animation" />} <!-- metadata --> <div class="onload-animation"> <PostMetadata
在src/styles/main.css底部添加下方代码
/* =================== */ /* 📘 AI 摘要模块样式 */ /* =================== */ .ai-summary { background: var(--card-bg); border: 1px solid var(--line-divider); border-radius: 12px; padding: 8px 8px 12px 8px; line-height: 1.3; flex-direction: column; margin-bottom: 16px; display: flex; gap: 5px; position: relative; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); transition: all 0.3s; } .ai-summary:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: translateY(-1px); } .ai-summary .ai-explanation { z-index: 10; padding: 8px 12px; font-size: 15px; line-height: 1.4; @apply text-90; text-align: justify; } /* ✅ 打字机光标动画 */ .ai-summary .ai-explanation::after { content: ''; display: inline-block; width: 8px; height: 2px; margin-left: 2px; @apply bg-black/90 dark:bg-white/90; vertical-align: bottom; animation: blink-underline 1s ease-in-out infinite; transition: all 0.3s; position: relative; bottom: 3px; } /* 打字完成后隐藏光标 */ .ai-summary .ai-explanation.typing-complete::after { display: none; } .ai-summary .ai-title { z-index: 10; font-size: 14px; display: flex; border-radius: 8px; align-items: center; position: relative; padding: 0 12px; cursor: default; user-select: none; } .ai-summary .ai-title .ai-title-left { display: flex; align-items: center; color: var(--primary); } .ai-summary .ai-title .ai-title-left i { margin-right: 3px; display: flex; color: var(--primary); border-radius: 20px; justify-content: center; align-items: center; } .ai-summary .ai-title .ai-title-left .ai-title-text { font-weight: 500; } .ai-summary .ai-title .ai-tag { color: var(--btn-content); font-weight: 300; margin-left: auto; display: flex; align-items: center; justify-content: center; transition: 0.3s; } /* ✅ 打字机光标闪烁动画 */ @keyframes blink-underline { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
最后在src/styles/variables.styl 大约19行后面添加下方代码
--page-bg: oklch(0.95 0.01 var(--hue)) oklch(0.16 0.014 var(--hue)) --card-bg: white oklch(0.23 0.015 var(--hue)) + // AI Summary 相关变量 + --liushen-title-font-color: #0883b7 #0883b7 + --liushen-maskbg: rgba(255, 255, 255, 0.85) rgba(0, 0, 0, 0.85) + --liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%, #d6b300 0%, #42A2FF 54%, #d6b300 100%) conic-gradient(from 1.5708rad at 50% 50%, rgba(214, 178, 0, 0.46) 0%, rgba(66, 161, 255, 0.53) 54%, rgba(214, 178, 0, 0.49) 100%) + --liushen-card-secondbg: #f1f3f8 #3e3f41 + --liushen-text: #4c4948 #ffffffb3 + --liushen-secondtext: #3c3c43cc #a1a2b8 --btn-content: oklch(0.55 0.12 var(--hue)) oklch(0.75 0.1 var(--hue)) --btn-regular-bg: oklch(0.95 0.025 var(--hue)) oklch(0.33 0.035 var(--hue))
样式参考
清羽飞扬
本地实现HEXO文章AI摘要
评论区
评论加载中...