<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>YXK&apos;s Blog</title><description>每一段旅行，都有终点。</description><link>https://blog.261770.xyz/</link><language>zh-CN</language><item><title>Alert 提示框组件演示</title><link>https://blog.261770.xyz/blog/alert-component-demo/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/alert-component-demo/</guid><description>展示 Alert 提示框组件的多种用法，包括不同类型、卡片风格、自定义标题和图标等</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Alert from &apos;../../components/post/Alert.astro&apos;;&lt;/p&gt;
&lt;h1&gt;Alert 提示框组件演示 📢&lt;/h1&gt;
&lt;p&gt;本文展示 &lt;strong&gt;Alert&lt;/strong&gt; 提示框组件的各种功能和用法。这个组件可以帮助你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;突出显示重要信息&lt;/li&gt;
&lt;li&gt;区分不同类型的提示（提醒、信息、问题、警告、错误）&lt;/li&gt;
&lt;li&gt;支持卡片风格和扁平风格&lt;/li&gt;
&lt;li&gt;自定义标题、图标和颜色&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;h3&gt;提醒（Tip）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;tip&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;提醒&lt;/strong&gt;类型的提示框，默认用于显示一般性的提示信息。&lt;/p&gt;
&lt;p&gt;支持 Markdown 语法，比如&lt;strong&gt;粗体&lt;/strong&gt;、&lt;em&gt;斜体&lt;/em&gt;、&lt;code&gt;行内代码&lt;/code&gt;等。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h3&gt;信息（Info）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;info&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;信息&lt;/strong&gt;类型的提示框，用于显示补充说明或背景信息。&lt;/p&gt;
&lt;p&gt;你可以在这里添加更多的解释性内容。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h3&gt;问题（Question）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;question&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;问题&lt;/strong&gt;类型的提示框，用于提出问题或引发思考。&lt;/p&gt;
&lt;p&gt;默认插槽的 &lt;a href=&quot;https://example.com&quot;&gt;超链接&lt;/a&gt; &lt;strong&gt;粗体&lt;/strong&gt; &lt;code&gt;Inline code&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h3&gt;警告（Warning）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;warning&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;警告&lt;/strong&gt;类型的提示框，用于提醒用户注意潜在的问题或风险。&lt;/p&gt;
&lt;p&gt;请仔细阅读以下内容，确保你了解相关风险。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h3&gt;错误（Error）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;error&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;错误&lt;/strong&gt;类型的提示框，用于显示错误信息或失败原因。&lt;/p&gt;
&lt;p&gt;请检查你的输入或联系管理员获取帮助。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;自定义标题&lt;/h2&gt;
&lt;p&gt;你可以通过 &lt;code&gt;title&lt;/code&gt; 属性自定义标题：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;info&quot; title=&quot;自定义标题&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这是带有自定义标题的提示框，标题会覆盖默认的类型标题。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;卡片风格&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;card&lt;/code&gt; 属性启用卡片风格（默认启用）：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;tip&quot; card={true}&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;卡片风格&lt;/strong&gt;的提示框，具有渐变背景和更好的视觉效果。&lt;/p&gt;
&lt;p&gt;卡片风格适合用于需要特别强调的内容。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;扁平风格&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;flat&lt;/code&gt; 属性禁用卡片风格：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;warning&quot; flat={true}&amp;gt;&lt;/p&gt;
&lt;p&gt;这是一个&lt;strong&gt;扁平风格&lt;/strong&gt;的提示框，背景更加简洁。&lt;/p&gt;
&lt;p&gt;扁平风格适合用于内容较多或需要节省空间的场景。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;自定义图标&lt;/h2&gt;
&lt;p&gt;你可以通过 &lt;code&gt;icon&lt;/code&gt; 属性指定自定义的 Iconify 图标：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;info&quot; icon=&quot;ri:lightbulb-fill&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;使用自定义图标的提示框，这里使用了 &lt;code&gt;ri:lightbulb-fill&lt;/code&gt; 图标。&lt;/p&gt;
&lt;p&gt;图标可以在 &lt;a href=&quot;https://iconify.design/&quot;&gt;Iconify&lt;/a&gt; 或 &lt;a href=&quot;https://yesicon.app/&quot;&gt;Yesicon&lt;/a&gt; 搜索获取。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;tip&quot; icon=&quot;carbon:favorite&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;另一个自定义图标的示例，使用 &lt;code&gt;carbon:favorite&lt;/code&gt; 图标。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;自定义颜色&lt;/h2&gt;
&lt;p&gt;你可以通过 &lt;code&gt;color&lt;/code&gt; 属性自定义主题色：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;info&quot; color=&quot;#8B5CF6&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;使用自定义颜色的提示框，这里使用了紫色 &lt;code&gt;#8B5CF6&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;颜色可以是任何有效的 CSS 颜色值。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;tip&quot; color=&quot;#EC4899&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;使用粉色的提示框，颜色值为 &lt;code&gt;#EC4899&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;组合使用&lt;/h2&gt;
&lt;p&gt;你可以同时使用多个属性来创建独特的提示框：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;question&quot; title=&quot;卡片风格 标题插槽的 超链接 粗体 Inline code&quot; icon=&quot;ph:question-fill&quot; color=&quot;#F59E0B&quot; card={true}&amp;gt;&lt;/p&gt;
&lt;p&gt;默认插槽的 &lt;a href=&quot;https://example.com&quot;&gt;超链接&lt;/a&gt; &lt;strong&gt;粗体&lt;/strong&gt; &lt;code&gt;Inline code&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这是一个组合了多种自定义属性的提示框，包括自定义标题、图标、颜色和卡片风格。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;error&quot; title=&quot;扁平风格 标题插槽的 超链接 粗体 Inline code&quot; icon=&quot;ph:x-circle-fill&quot; color=&quot;#EF4444&quot; flat={true}&amp;gt;&lt;/p&gt;
&lt;p&gt;默认插槽的 &lt;a href=&quot;https://example.com&quot;&gt;超链接&lt;/a&gt; &lt;strong&gt;粗体&lt;/strong&gt; &lt;code&gt;Inline code&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这是一个扁平风格的错误提示框，同样支持自定义标题、图标和颜色。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;仅标题，自定义图标和颜色&lt;/h2&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;tip&quot; title=&quot;仅标题，并且自定义图标和颜色&quot; icon=&quot;ri:star-smile-line&quot; color=&quot;#10B981&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;这个提示框只设置了标题、图标和颜色，内容区域为空。&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;组件使用方式&lt;/h2&gt;
&lt;p&gt;在 MDX 文件中导入并使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mdx&quot;&gt;import Alert from &apos;../../components/post/Alert.astro&apos;;

&amp;lt;Alert type=&quot;tip&quot;&amp;gt;
  这是一个提示框
&amp;lt;/Alert&amp;gt;

&amp;lt;Alert type=&quot;info&quot; title=&quot;自定义标题&quot; icon=&quot;ri:lightbulb-fill&quot; color=&quot;#8B5CF6&quot;&amp;gt;
  支持 Markdown 和 **富文本内容**
&amp;lt;/Alert&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;支持的类型&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;默认图标&lt;/th&gt;
&lt;th&gt;默认颜色&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tip&lt;/td&gt;
&lt;td&gt;提醒&lt;/td&gt;
&lt;td&gt;ph:notepad-bold&lt;/td&gt;
&lt;td&gt;#3A7（绿色）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;info&lt;/td&gt;
&lt;td&gt;信息&lt;/td&gt;
&lt;td&gt;ph:info-bold&lt;/td&gt;
&lt;td&gt;var(--c-primary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;question&lt;/td&gt;
&lt;td&gt;问题&lt;/td&gt;
&lt;td&gt;ph:question-bold&lt;/td&gt;
&lt;td&gt;#3AF（青色）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;warning&lt;/td&gt;
&lt;td&gt;警告&lt;/td&gt;
&lt;td&gt;ph:warning-bold&lt;/td&gt;
&lt;td&gt;#F80（橙色）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;error&lt;/td&gt;
&lt;td&gt;错误&lt;/td&gt;
&lt;td&gt;ph:x-circle-bold&lt;/td&gt;
&lt;td&gt;#F33（红色）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Props 属性说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;tip&apos; | &apos;info&apos; | &apos;question&apos; | &apos;warning&apos; | &apos;error&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;tip&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;提示框类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;card&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用卡片风格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flat&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用扁平风格（与 card 相反）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;icon&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;自定义 Iconify 图标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;自定义主题颜色&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;title&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;自定义标题&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;图标资源&lt;/h2&gt;
&lt;p&gt;图标可以从以下网站获取：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://iconify.design/&quot;&gt;Iconify&lt;/a&gt; - 超过 200,000 个开源图标&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yesicon.app/&quot;&gt;Yesicon&lt;/a&gt; - 图标搜索和浏览工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图标格式为 &lt;code&gt;集合名：图标名&lt;/code&gt;，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ri:lightbulb-fill&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ph:question-bold&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;carbon:favorite&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;Alert 组件让博客中的重要信息更加醒目，通过不同的类型和样式帮助读者快速识别内容的性质。&lt;/p&gt;
&lt;p&gt;你可以根据需要选择合适的类型和风格，让博客内容更加丰富多彩！🎉&lt;/p&gt;
</content:encoded></item><item><title>Badge 徽章组件演示</title><link>https://blog.261770.xyz/blog/badge-component-demo/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/badge-component-demo/</guid><description>展示 Badge 徽章组件的多种用法，包括带链接、自定义图标、圆形方形样式等</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Alert from &apos;../../components/post/Alert.astro&apos;;
import Badge from &apos;../../components/post/Badge.astro&apos;;&lt;/p&gt;
&lt;h1&gt;Badge 徽章组件演示 ️&lt;/h1&gt;
&lt;p&gt;本文展示 &lt;strong&gt;Badge&lt;/strong&gt; 徽章组件的各种功能和用法。这个组件可以帮助你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建美观的链接徽章&lt;/li&gt;
&lt;li&gt;自动获取外部网站的图标&lt;/li&gt;
&lt;li&gt;GitHub 链接自动显示用户头像&lt;/li&gt;
&lt;li&gt;支持圆形和方形两种样式&lt;/li&gt;
&lt;li&gt;自定义图标和文本&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;h3&gt;普通带链接&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;普通带链接&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;纯文本指定圆形&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge text=&quot;纯文本指定圆形&quot; round={true} /&amp;gt;&lt;/p&gt;
&lt;h3&gt;纯文本指定方形&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge text=&quot;纯文本指定方形&quot; square={true} /&amp;gt;&lt;/p&gt;
&lt;h3&gt;带个图&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge img=&quot;https://github.com/github.png&quot; text=&quot;带个图&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;外部域名自动获取站点图标&lt;/h2&gt;
&lt;p&gt;Badge 组件可以自动根据外部链接获取对应的站点图标：&lt;/p&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://www.zhihu.com&quot; text=&quot;纸鹿&quot; /&amp;gt;，
&amp;lt;Badge link=&quot;https://blog.example.com&quot; text=&quot;古怪杂记本&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;GitHub 链接自动识别头像&lt;/h2&gt;
&lt;p&gt;GitHub 链接会自动获取用户的头像：&lt;/p&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://github.com/KazariEX&quot; text=&quot;KazariEX&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;也可以指定为方形：&lt;/p&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://github.com/isYangs/GioPic&quot; text=&quot;isYangs/GioPic&quot; square={true} /&amp;gt;&lt;/p&gt;
&lt;h2&gt;在其他组件中使用&lt;/h2&gt;
&lt;p&gt;你可以在其他组件（如 Alert）中嵌套使用 Badge 组件：&lt;/p&gt;
&lt;p&gt;&amp;lt;Alert type=&quot;tip&quot; title=&quot;在其他组件中使用&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;带链接&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Badge img=&quot;https://github.com/github.png&quot; text=&quot;指定圆形&quot; round={true} /&amp;gt;
背景色 &amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;可以&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;动态变化&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;使用&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/Alert&amp;gt;&lt;/p&gt;
&lt;h2&gt;更多示例&lt;/h2&gt;
&lt;h3&gt;社交媒体&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://github.com&quot; text=&quot;GitHub&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://www.bilibili.com&quot; text=&quot;Bilibili&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://www.zhihu.com&quot; text=&quot;知乎&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://weibo.com&quot; text=&quot;微博&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://twitter.com&quot; text=&quot;Twitter&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;开发者平台&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://stackoverflow.com&quot; text=&quot;Stack Overflow&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://www.npmjs.com&quot; text=&quot;npm&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://vercel.com&quot; text=&quot;Vercel&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://netlify.com&quot; text=&quot;Netlify&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;文档资源&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge link=&quot;https://developer.mozilla.org&quot; text=&quot;MDN&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://docs.microsoft.com&quot; text=&quot;Microsoft Docs&quot; /&amp;gt;
&amp;lt;Badge link=&quot;https://astro.build&quot; text=&quot;Astro&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;自定义图标&lt;/h3&gt;
&lt;p&gt;&amp;lt;Badge img=&quot;https://astro.build/favicon.svg&quot; text=&quot;Astro&quot; round={true} /&amp;gt;
&amp;lt;Badge img=&quot;https://vuejs.org/logo.svg&quot; text=&quot;Vue.js&quot; round={true} /&amp;gt;
&amp;lt;Badge img=&quot;https://react.dev/favicon.ico&quot; text=&quot;React&quot; round={true} /&amp;gt;&lt;/p&gt;
&lt;h2&gt;组件使用方式&lt;/h2&gt;
&lt;p&gt;在 MDX 文件中导入并使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mdx&quot;&gt;import Badge from &apos;../../components/post/Badge.astro&apos;;

&amp;lt;!-- 基本用法 --&amp;gt;
&amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;徽章文本&quot; /&amp;gt;

&amp;lt;!-- 圆形样式 --&amp;gt;
&amp;lt;Badge text=&quot;圆形&quot; round={true} /&amp;gt;

&amp;lt;!-- 方形样式 --&amp;gt;
&amp;lt;Badge text=&quot;方形&quot; square={true} /&amp;gt;

&amp;lt;!-- 自定义图标 --&amp;gt;
&amp;lt;Badge img=&quot;https://example.com/icon.png&quot; text=&quot;带图标&quot; /&amp;gt;

&amp;lt;!-- 在 Alert 中使用 --&amp;gt;
&amp;lt;Alert type=&quot;tip&quot;&amp;gt;
  &amp;lt;Badge link=&quot;https://example.com&quot; text=&quot;链接徽章&quot; /&amp;gt;
&amp;lt;/Alert&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Props 属性说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;img&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;自定义图标 URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;徽章文本内容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;link&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;链接地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;round&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用圆形样式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;square&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用方形样式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;样式说明&lt;/h2&gt;
&lt;h3&gt;自动样式规则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有图标时&lt;/strong&gt;：默认为圆形，除非指定 &lt;code&gt;square={true}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无图标时&lt;/strong&gt;：默认为方形，除非指定 &lt;code&gt;round={true}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;视觉效果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;边框和背景色会根据当前文本颜色自动调整&lt;/li&gt;
&lt;li&gt;悬停时颜色会加深&lt;/li&gt;
&lt;li&gt;外部链接会自动在新标签页打开&lt;/li&gt;
&lt;li&gt;鼠标悬停会显示域名提示&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;图标获取规则&lt;/h2&gt;
&lt;p&gt;Badge 组件会智能地获取图标：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;自定义图标优先&lt;/strong&gt;：如果提供了 &lt;code&gt;img&lt;/code&gt; 属性，直接使用自定义图标&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub 头像&lt;/strong&gt;：如果是 GitHub 链接，自动获取用户头像&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;站点图标&lt;/strong&gt;：如果是其他外部链接，使用 Google 服务获取站点 favicon&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无图标&lt;/strong&gt;：如果没有链接或内部链接，不显示图标&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;暗色模式适配&lt;/h2&gt;
&lt;p&gt;Badge 组件完全适配暗色模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;边框颜色自动调整&lt;/li&gt;
&lt;li&gt;背景色透明度优化&lt;/li&gt;
&lt;li&gt;悬停效果保持一致&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;Badge 组件让博客中的链接更加美观和实用，通过自动获取图标和智能样式，让读者一眼就能识别链接的类型和来源。&lt;/p&gt;
&lt;p&gt;你可以在文章中使用 Badge 组件来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;标注引用的来源&lt;/li&gt;
&lt;li&gt;展示相关的 GitHub 项目&lt;/li&gt;
&lt;li&gt;链接到社交媒体&lt;/li&gt;
&lt;li&gt;美化外部链接&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;让博客内容更加丰富多彩！🎉&lt;/p&gt;
</content:encoded></item><item><title>BlogHeader 博客头部组件演示</title><link>https://blog.261770.xyz/blog/blogheader-component-demo/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/blogheader-component-demo/</guid><description>展示 BlogHeader 博客头部组件的多种用法，包括自定义标题、副标题、Logo 和 Emoji 动画效果</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Alert from &apos;../../components/post/Alert.astro&apos;;
import Badge from &apos;../../components/post/Badge.astro&apos;;
import BlogHeader from &apos;../../components/post/BlogHeader.astro&apos;;&lt;/p&gt;
&lt;h1&gt;BlogHeader 博客头部组件演示 🏠&lt;/h1&gt;
&lt;p&gt;本文展示 &lt;strong&gt;BlogHeader&lt;/strong&gt; 博客头部组件的各种功能和用法。这个组件可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显示博客标题和副标题&lt;/li&gt;
&lt;li&gt;自定义 Logo 图标&lt;/li&gt;
&lt;li&gt;鼠标悬浮时显示 Emoji 动画效果&lt;/li&gt;
&lt;li&gt;标题文字动态粗细变化效果&lt;/li&gt;
&lt;li&gt;支持自定义配置&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;h3&gt;默认样式&lt;/h3&gt;
&lt;p&gt;&amp;lt;BlogHeader /&amp;gt;&lt;/p&gt;
&lt;h2&gt;自定义标题和副标题&lt;/h2&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;我的博客&quot;
subtitle=&quot;记录生活，分享技术&quot;
logo=&quot;https://api.iconify.design/noto:cat-face.svg&quot;
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;自定义 Emoji 动画&lt;/h2&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;技术空间&quot;
subtitle=&quot;探索编程的世界&quot;
logo=&quot;https://api.iconify.design/noto:robot-face.svg&quot;
emojiTail={[&apos;💻&apos;, &apos;️&apos;, &apos;🖱️&apos;, &apos;💾&apos;, &apos;&apos;]}
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;隐藏标题&lt;/h2&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;隐藏标题&quot;
subtitle=&quot;只显示 Logo&quot;
logo=&quot;https://api.iconify.design/noto:sparkles.svg&quot;
showTitle={false}
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;不同风格的 Logo&lt;/h2&gt;
&lt;h3&gt;使用 Iconify 图标&lt;/h3&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;Iconify 风格&quot;
subtitle=&quot;使用 Iconify 图标作为 Logo&quot;
logo=&quot;https://api.iconify.design/noto:book.svg&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;使用自定义图片&lt;/h3&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;自定义图片&quot;
subtitle=&quot;使用自己的图片作为 Logo&quot;
logo=&quot;https://github.com/github.png&quot;
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;在不同的场景中使用&lt;/h2&gt;
&lt;h3&gt;在文章开头&lt;/h3&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;文章标题&quot;
subtitle=&quot;这是文章的副标题&quot;
logo=&quot;https://api.iconify.design/noto:pen.svg&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;在页面顶部&lt;/h3&gt;
&lt;p&gt;&amp;lt;BlogHeader
title=&quot;欢迎来到我的博客&quot;
subtitle=&quot;分享技术，记录生活&quot;
logo=&quot;https://api.iconify.design/noto:house.svg&quot;
emojiTail={[&apos;&apos;, &apos;📝&apos;, &apos;💡&apos;, &apos;🎨&apos;, &apos;&apos;]}
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;组件使用方式&lt;/h2&gt;
&lt;p&gt;在 MDX 文件中导入并使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mdx&quot;&gt;import BlogHeader from &apos;../../components/post/BlogHeader.astro&apos;;

&amp;lt;!-- 基本用法 --&amp;gt;
&amp;lt;BlogHeader /&amp;gt;

&amp;lt;!-- 自定义配置 --&amp;gt;
&amp;lt;BlogHeader 
  title=&quot;我的博客&quot; 
  subtitle=&quot;记录生活，分享技术&quot;
  logo=&quot;https://example.com/logo.png&quot;
  emojiTail={[&apos;📝&apos;, &apos;💻&apos;, &apos;🚀&apos;]}
  showTitle={true}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Props 属性说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tag&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;div&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;标题的 HTML 标签&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;title&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;纸鹿摸鱼处&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;博客标题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;subtitle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;纸鹿至麓不知路，支炉制露不止漉&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;博客副标题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;logo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Iconify 图标&lt;/td&gt;
&lt;td&gt;Logo 图片 URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;emojiTail&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&apos;📄&apos;, &apos;🦌&apos;, &apos;&apos;, &apos;🐟&apos;, &apos;&apos;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;悬浮时显示的 Emoji 数组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;showTitle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否显示标题文字&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;动画效果说明&lt;/h2&gt;
&lt;h3&gt;标题文字动画&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字体粗细变化&lt;/strong&gt;：使用字体变体功能，在 300-900 之间循环变化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;斜角效果&lt;/strong&gt;：字体斜角属性动态变化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟效果&lt;/strong&gt;：每个字符有不同的动画延迟，形成波浪效果&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Emoji 悬浮动画&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;悬浮效果&lt;/strong&gt;：Emoji 会轻微浮动和缩放&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模糊效果&lt;/strong&gt;：悬浮时带有模糊效果，增加层次感&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;透明度变化&lt;/strong&gt;：鼠标悬浮时透明度从 0.2 增加到 0.5&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟效果&lt;/strong&gt;：每个 Emoji 有不同的动画延迟&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;字体配置&lt;/h2&gt;
&lt;p&gt;BlogHeader 组件使用了 &lt;strong&gt;阿里妈妈方圆体&lt;/strong&gt; 字体，该字体支持字体变体功能，可以实现动态的粗细和斜角效果。&lt;/p&gt;
&lt;h3&gt;字体子集生成&lt;/h3&gt;
&lt;p&gt;如果需要自定义字体子集，可以使用以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装工具
pip install fonttools brotli

# 生成字体子集
pyftsubset ./AlimamaFangYuanTi.ttf --text=Header 文本 --flavor=woff2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;自定义样式&lt;/h2&gt;
&lt;p&gt;你可以通过 CSS 覆盖来自定义样式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* 修改 Logo 大小 */
.blog-logo {
  height: 4em; /* 默认 3em */
}

/* 修改标题字体大小 */
.header-title {
  font-size: 2em; /* 默认 1.5em */
}

/* 修改副标题透明度 */
.header-subtitle {
  opacity: 0.7; /* 默认 0.5 */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;暗色模式适配&lt;/h2&gt;
&lt;p&gt;BlogHeader 组件完全适配暗色模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文字颜色自动调整&lt;/li&gt;
&lt;li&gt;Logo 阴影效果优化&lt;/li&gt;
&lt;li&gt;Emoji 悬浮效果保持一致&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;性能优化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;懒加载&lt;/strong&gt;：Logo 图片使用 &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; 延迟加载&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动画优化&lt;/strong&gt;：使用 CSS 动画，GPU 加速&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;悬浮暂停&lt;/strong&gt;：鼠标离开时动画暂停，节省资源&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;BlogHeader 组件让博客的头部更加生动有趣，通过 Emoji 动画和文字动态效果，为访客留下深刻的第一印象。&lt;/p&gt;
&lt;p&gt;你可以在以下场景使用 BlogHeader 组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;博客首页顶部&lt;/li&gt;
&lt;li&gt;文章页面开头&lt;/li&gt;
&lt;li&gt;个人主页头部&lt;/li&gt;
&lt;li&gt;任何需要展示博客标识的地方&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;让你的博客更加个性化！🎉&lt;/p&gt;
</content:encoded></item><item><title>Chat 聊天消息组件演示</title><link>https://blog.261770.xyz/blog/chat-component-demo/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/chat-component-demo/</guid><description>展示 Chat 聊天消息组件的多种用法，包括系统消息、用户消息、时间戳等</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Chat from &apos;../../components/post/Chat.astro&apos;;&lt;/p&gt;
&lt;h1&gt;Chat 聊天消息组件演示 💬&lt;/h1&gt;
&lt;p&gt;本文展示 &lt;strong&gt;Chat&lt;/strong&gt; 聊天消息组件的各种功能和用法。这个组件可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显示聊天对话样式&lt;/li&gt;
&lt;li&gt;支持系统消息&lt;/li&gt;
&lt;li&gt;支持自定义用户消息&lt;/li&gt;
&lt;li&gt;显示时间戳&lt;/li&gt;
&lt;li&gt;支持撤回消息提示&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本用法&lt;/h2&gt;
&lt;h3&gt;时间戳&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat timestamp=&quot;2024-11-09 23:39:30&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;系统消息&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat system&amp;gt;
纸鹿撤回了一条消息
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h3&gt;普通消息&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat&amp;gt;
有趣
我学到了。
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h2&gt;用户消息&lt;/h2&gt;
&lt;h3&gt;右侧消息（自己）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;纸鹿&quot; myself&amp;gt;
也许
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;纸鹿&quot; myself&amp;gt;
我们可以聊聊天
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h3&gt;带名字的消息&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;纸鹿&quot; myself&amp;gt;
我还可以有名字
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h3&gt;左侧消息（他人）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;用户 1&quot;&amp;gt;
有趣
我学到了。
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h2&gt;完整对话示例&lt;/h2&gt;
&lt;p&gt;&amp;lt;Chat timestamp=&quot;2024-11-09 23:39:30&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;纸鹿&quot; myself&amp;gt;
也许
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;纸鹿&quot; myself&amp;gt;
我们可以聊聊天
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;纸鹿&quot; myself&amp;gt;
我还可以有名字
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat system&amp;gt;
纸鹿撤回了一条消息
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;用户 1&quot;&amp;gt;
有趣
我学到了。
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h2&gt;更多场景&lt;/h2&gt;
&lt;h3&gt;多人对话&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat timestamp=&quot;2024-11-10 10:00:00&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;Alice&quot;&amp;gt;
大家好！
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;Bob&quot;&amp;gt;
你好，Alice！
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;Charlie&quot;&amp;gt;
欢迎加入！
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h3&gt;系统通知&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat system&amp;gt;
Alice 加入了聊天
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;Alice&quot;&amp;gt;
很高兴加入大家！
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat system&amp;gt;
Bob 邀请 Charlie 加入聊天
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;Charlie&quot;&amp;gt;
谢谢邀请！
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h3&gt;撤回消息&lt;/h3&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;小明&quot;&amp;gt;
这是一条消息
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat system&amp;gt;
小明撤回了一条消息
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Chat user=&quot;小明&quot;&amp;gt;
发错了，重来
&amp;lt;/Chat&amp;gt;&lt;/p&gt;
&lt;h2&gt;组件使用方式&lt;/h2&gt;
&lt;p&gt;在 MDX 文件中导入并使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mdx&quot;&gt;import Chat from &apos;../../components/post/Chat.astro&apos;;

&amp;lt;!-- 时间戳 --&amp;gt;
&amp;lt;Chat timestamp=&quot;2024-11-09 23:39:30&quot; /&amp;gt;

&amp;lt;!-- 系统消息 --&amp;gt;
&amp;lt;Chat system&amp;gt;
  系统通知内容
&amp;lt;/Chat&amp;gt;

&amp;lt;!-- 自己的消息（右侧） --&amp;gt;
&amp;lt;Chat user=&quot;用户名&quot; myself&amp;gt;
  消息内容
&amp;lt;/Chat&amp;gt;

&amp;lt;!-- 他人的消息（左侧） --&amp;gt;
&amp;lt;Chat user=&quot;用户名&quot;&amp;gt;
  消息内容
&amp;lt;/Chat&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Props 属性说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;发送消息的用户名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;myself&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为自己的消息，设置为 &lt;code&gt;true&lt;/code&gt; 时显示在右侧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;system&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为系统消息，居中显示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;时间戳，显示在消息上方&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;样式说明&lt;/h2&gt;
&lt;h3&gt;消息类型&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;系统消息&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;居中显示&lt;/li&gt;
&lt;li&gt;半透明效果&lt;/li&gt;
&lt;li&gt;用于显示撤回、加入等系统通知&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用户消息（右侧）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显示在右侧&lt;/li&gt;
&lt;li&gt;使用主题色背景&lt;/li&gt;
&lt;li&gt;显示用户名&lt;/li&gt;
&lt;li&gt;圆角在右上角&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;普通消息（左侧）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显示在左侧&lt;/li&gt;
&lt;li&gt;使用普通背景色&lt;/li&gt;
&lt;li&gt;可选显示用户名&lt;/li&gt;
&lt;li&gt;圆角在左上角&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;视觉效果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;消息气泡采用圆角设计&lt;/li&gt;
&lt;li&gt;用户名半透明显示&lt;/li&gt;
&lt;li&gt;时间戳使用等宽字体&lt;/li&gt;
&lt;li&gt;最大宽度限制为 90%&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;暗色模式适配&lt;/h2&gt;
&lt;p&gt;Chat 组件完全适配暗色模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;消息气泡颜色自动调整&lt;/li&gt;
&lt;li&gt;用户名和时间戳透明度保持一致&lt;/li&gt;
&lt;li&gt;主题色背景在暗色模式下更柔和&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;使用场景&lt;/h2&gt;
&lt;p&gt;你可以在以下场景使用 Chat 组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;教程文章&lt;/strong&gt;：模拟对话形式讲解知识点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;案例分析&lt;/strong&gt;：展示真实的聊天记录&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;产品演示&lt;/strong&gt;：展示聊天功能效果&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;故事叙述&lt;/strong&gt;：用对话形式讲述故事&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FAQ&lt;/strong&gt;：以问答形式呈现常见问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;Chat 组件让博客中的对话内容更加生动直观，通过模拟真实的聊天界面，让读者更容易理解和代入。&lt;/p&gt;
&lt;p&gt;你可以在文章中使用 Chat 组件来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;展示对话内容&lt;/li&gt;
&lt;li&gt;模拟客服聊天&lt;/li&gt;
&lt;li&gt;呈现用户反馈&lt;/li&gt;
&lt;li&gt;演示产品功能&lt;/li&gt;
&lt;li&gt;讲述故事情节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;让博客内容更加丰富多彩！🎉&lt;/p&gt;
</content:encoded></item><item><title>ProseA 链接组件演示</title><link>https://blog.261770.xyz/blog/prosea-component-demo/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/prosea-component-demo/</guid><description>展示 ProseA 增强链接组件的功能，包括自动图标匹配、外部链接处理等特性</description><pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import ProseA from &apos;../../components/post/ProseA.astro&apos;;&lt;/p&gt;
&lt;h1&gt;ProseA 链接组件演示 🔗&lt;/h1&gt;
&lt;p&gt;本文展示 &lt;strong&gt;ProseA&lt;/strong&gt; 增强链接组件的各种功能。这个组件可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动识别外部链接并在新标签页打开&lt;/li&gt;
&lt;li&gt;根据域名自动匹配对应图标&lt;/li&gt;
&lt;li&gt;支持自定义图标&lt;/li&gt;
&lt;li&gt;鼠标悬停显示域名提示&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;内部链接&lt;/h2&gt;
&lt;p&gt;这是&amp;lt;ProseA href=&quot;/blog/welcome-to-my-blog/&quot;&amp;gt;内部链接示例&amp;lt;/ProseA&amp;gt;，点击后会在当前页面跳转。&lt;/p&gt;
&lt;h2&gt;外部链接与自动图标&lt;/h2&gt;
&lt;h3&gt;开发者平台&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://github.com/L33Z22L11/blog-v3&quot;&amp;gt;GitHub 仓库&amp;lt;/ProseA&amp;gt; - 自动显示 GitHub 图标&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://gitlab.com&quot;&amp;gt;GitLab&amp;lt;/ProseA&amp;gt; - 自动显示 GitLab 图标&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://stackoverflow.com&quot;&amp;gt;Stack Overflow&amp;lt;/ProseA&amp;gt; - 开发者问答社区&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.npmjs.com&quot;&amp;gt;npm 包管理&amp;lt;/ProseA&amp;gt; - Node.js 包管理器&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://vercel.com&quot;&amp;gt;Vercel 部署平台&amp;lt;/ProseA&amp;gt; - 前端部署服务&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.cloudflare.com&quot;&amp;gt;Cloudflare&amp;lt;/ProseA&amp;gt; - CDN 和云服务&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;社交媒体&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.bilibili.com&quot;&amp;gt;哔哩哔哩&amp;lt;/ProseA&amp;gt; - 自动显示 B站 图标&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.zhihu.com&quot;&amp;gt;知乎&amp;lt;/ProseA&amp;gt; - 中文问答社区&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://weibo.com&quot;&amp;gt;微博&amp;lt;/ProseA&amp;gt; - 社交媒体平台&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://twitter.com&quot;&amp;gt;Twitter/X&amp;lt;/ProseA&amp;gt; - 国际社交平台&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.youtube.com&quot;&amp;gt;YouTube&amp;lt;/ProseA&amp;gt; - 视频分享平台&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文档与学习&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://developer.mozilla.org&quot;&amp;gt;MDN Web Docs&amp;lt;/ProseA&amp;gt; - Web 开发文档&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://docs.microsoft.com&quot;&amp;gt;微软文档&amp;lt;/ProseA&amp;gt; - Microsoft 官方文档&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.w3schools.com&quot;&amp;gt;W3Schools&amp;lt;/ProseA&amp;gt; - Web 技术教程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;国内服务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.baidu.com&quot;&amp;gt;百度&amp;lt;/ProseA&amp;gt; - 搜索引擎&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.taobao.com&quot;&amp;gt;淘宝&amp;lt;/ProseA&amp;gt; - 电商平台&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.jd.com&quot;&amp;gt;京东&amp;lt;/ProseA&amp;gt; - 电商平台&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://cloud.tencent.com&quot;&amp;gt;腾讯云&amp;lt;/ProseA&amp;gt; - 云服务商&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://www.aliyun.com&quot;&amp;gt;阿里云&amp;lt;/ProseA&amp;gt; - 云服务商&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自定义图标&lt;/h2&gt;
&lt;p&gt;你可以通过 &lt;code&gt;icon&lt;/code&gt; 属性指定自定义的 Iconify 图标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://astro.build&quot; icon=&quot;simple-icons:astro&quot;&amp;gt;Astro 官网&amp;lt;/ProseA&amp;gt; - 使用 Astro 图标&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://vuejs.org&quot; icon=&quot;uim:vuejs&quot;&amp;gt;Vue.js 官网&amp;lt;/ProseA&amp;gt; - 使用 Vue 图标&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://react.dev&quot; icon=&quot;simple-icons:react&quot;&amp;gt;React 官网&amp;lt;/ProseA&amp;gt; - 使用 React 图标&lt;/li&gt;
&lt;li&gt;&amp;lt;ProseA href=&quot;https://svelte.dev&quot; icon=&quot;simple-icons:svelte&quot;&amp;gt;Svelte 官网&amp;lt;/ProseA&amp;gt; - 使用 Svelte 图标&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图标可以在 &lt;a href=&quot;https://iconify.design/&quot;&gt;Iconify&lt;/a&gt; 或 &lt;a href=&quot;https://yesicon.app/&quot;&gt;Yesicon&lt;/a&gt; 搜索获取。&lt;/p&gt;
&lt;h2&gt;组件使用方式&lt;/h2&gt;
&lt;p&gt;在 MDX 文件中导入并使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mdx&quot;&gt;import ProseA from &apos;../../components/post/ProseA.astro&apos;;

&amp;lt;ProseA href=&quot;https://github.com&quot;&amp;gt;GitHub&amp;lt;/ProseA&amp;gt;
&amp;lt;ProseA href=&quot;https://example.com&quot; icon=&quot;ri:link&quot;&amp;gt;自定义图标&amp;lt;/ProseA&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;支持的域名图标&lt;/h2&gt;
&lt;p&gt;目前支持自动匹配图标的域名包括：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;域名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;社交媒体&lt;/td&gt;
&lt;td&gt;bilibili.com, qq.com, weibo.com, zhihu.com, twitter.com, x.com, facebook.com, instagram.com, youtube.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;开发者平台&lt;/td&gt;
&lt;td&gt;github.com, gitlab.com, stackoverflow.com, npmjs.com, vercel.com, netlify.com, cloudflare.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;云服务&lt;/td&gt;
&lt;td&gt;aws.amazon.com, azure.com, google.com, firebase.google.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;文档&lt;/td&gt;
&lt;td&gt;microsoft.com, mozilla.org, developer.mozilla.org, w3schools.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;国内服务&lt;/td&gt;
&lt;td&gt;baidu.com, aliyun.com, tencent.com, taobao.com, tmall.com, jd.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;设计&lt;/td&gt;
&lt;td&gt;figma.com, dribbble.com, behance.net&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通讯&lt;/td&gt;
&lt;td&gt;slack.com, discord.com, telegram.org&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;为更多站点匹配图标&lt;/h2&gt;
&lt;p&gt;你可以在 &lt;code&gt;src/utils/icon.ts&lt;/code&gt; 中分别为主域名或专门域名（优先级高）添加匹配规则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 主域名图标映射
const mainDomainIcons: Record&amp;lt;string, string&amp;gt; = {
  &apos;example.com&apos;: &apos;ri:example-fill&apos;,
  // ...
};

// 专门域名图标映射（优先级更高）
export const domainIcons: Record&amp;lt;string, string&amp;gt; = {
  &apos;docs.example.com&apos;: &apos;ri:example-docs-fill&apos;,
  // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;ProseA 组件让博客中的链接更加美观和实用，读者可以一眼看出链接指向的平台类型，外部链接也会安全地在新标签页打开。&lt;/p&gt;
&lt;p&gt;如果你有想要添加的域名图标，欢迎修改 &lt;code&gt;src/utils/icon.ts&lt;/code&gt; 文件来扩展支持！&lt;/p&gt;
</content:encoded></item><item><title>博客新文章通知与历史修订功能实现</title><link>https://blog.261770.xyz/blog/blog-update-notification-system/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/blog-update-notification-system/</guid><description>基于 Astro + IndexedDB + diff 库实现的文章更新通知系统，支持 RSS 差异比较、文章内变更高亮显示</description><pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在维护博客的过程中，经常需要更新已发布的文章。如何让读者知道文章有更新？如何展示具体变更内容？本文将介绍我基于 Astro 框架实现的新文章通知与历史修订功能。&lt;/p&gt;
&lt;h2&gt;功能概述&lt;/h2&gt;
&lt;p&gt;实现的功能包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;新文章通知&lt;/strong&gt; - 右下角通知面板，显示新发布的文章和更新的文章&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文章内差异高亮&lt;/strong&gt; - 在文章页面显示具体变更（新增/删除）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳转到更新处&lt;/strong&gt; - 一键跳转到文章变更位置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文章时效性提示&lt;/strong&gt; - 显示文章发布/更新时间&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt; - 静态站点生成器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IndexedDB&lt;/strong&gt; - 浏览器本地数据库存储 RSS 数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;diff&lt;/strong&gt; - JavaScript 差异比较库&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS&lt;/strong&gt; - 文章数据源&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;架构设计&lt;/h2&gt;
&lt;h3&gt;数据流向&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;RSS 数据
    ↓
NewPostNotification 获取并存储到 IndexedDB
    ↓
比较新旧 RSS 差异（diff）
    ↓
存储差异数据到 localStorage
    ↓
PostDiffInArticle 读取并渲染高亮
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组件分工&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NewPostNotification&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;全局通知组件，RSS 获取、差异计算、IndexedDB 存储&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PostDiffInArticle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;文章内差异展示，DOM 操作渲染高亮&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PostContentHighlighter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;更新提示 Toast，跳转功能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PostUpdateNotice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;文章时效性提示&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;核心实现&lt;/h2&gt;
&lt;h3&gt;1. RSS 存储与差异比较&lt;/h3&gt;
&lt;p&gt;使用 IndexedDB 存储 RSS 数据，避免重复获取：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const DB_NAME = &apos;blog-rss-store-v2&apos;;
const STORE_OLD = &apos;posts&apos;;
const STORE_NEW = &apos;posts_new&apos;;

function openDB() {
    return new Promise((resolve, reject) =&amp;gt; {
        const request = indexedDB.open(DB_NAME, 1);
        request.onupgradeneeded = (event) =&amp;gt; {
            const db = event.target.result;
            db.createObjectStore(STORE_OLD, { keyPath: &apos;id&apos; });
            db.createObjectStore(STORE_NEW, { keyPath: &apos;id&apos; });
        };
        // ...
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;diff&lt;/code&gt; 库比较文章内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as Diff from &apos;diff&apos;;

function computeDiff(oldText, newText) {
    const diffs = Diff.diffLines(oldText, newText);
    return diffs.filter(part =&amp;gt; part.added || part.removed);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 文章内差异高亮&lt;/h3&gt;
&lt;p&gt;差异数据格式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;{
    title: &quot;文章标题&quot;,
    isUpdated: true,
    diff: {
        content: [
            { value: &quot;旧内容&quot;, removed: true },
            { value: &quot;新内容&quot;, added: true }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;渲染逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 找到匹配的 DOM 元素并添加样式
const target = findBlockByText(container, addRow.text);
if (target) {
    target.classList.add(&quot;post-inline-diff-add-target&quot;);
    target.style.textDecorationLine = &quot;underline&quot;;
    target.style.textDecorationColor = &quot;#4ade80&quot;;
} else {
    // 创建新元素显示新增内容
    const node = createAdditionNode(text);
    container.appendChild(node);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 样式设计&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* 新增内容 - 绿色下划线 */
.post-inline-diff-add-target {
    text-decoration-line: underline !important;
    text-decoration-thickness: 2px !important;
    text-decoration-color: #4ade80 !important;
}

/* 删除内容 - 红色删除线 */
.post-inline-diff-del-target {
    color: #f87171 !important;
    text-decoration-line: line-through !important;
    text-decoration-color: #f87171 !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 触发条件&lt;/h3&gt;
&lt;p&gt;Diff 样式在以下情况自动显示：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从网站内部页面点击文章（通过 &lt;code&gt;document.referrer&lt;/code&gt; 检测）&lt;/li&gt;
&lt;li&gt;URL 带 &lt;code&gt;?diff=1&lt;/code&gt; 参数&lt;/li&gt;
&lt;li&gt;URL 带 &lt;code&gt;#post-diff&lt;/code&gt; 锚点&lt;/li&gt;
&lt;li&gt;localStorage 中有通知状态（从通知面板点击）&lt;/li&gt;
&lt;li&gt;调试模式 &lt;code&gt;?__diff_debug=1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const isFromInternal = document.referrer?.startsWith(window.location.origin);
const hasNotificationState = !!localStorage.getItem(NOTIFICATION_STATE_KEY);
const shouldAutoShow = matched &amp;amp;&amp;amp; (isFromInternal || hasNotificationState);
const shouldApply = isDebug || diffParam || hasDiffHash || shouldAutoShow;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用方式&lt;/h2&gt;
&lt;h3&gt;对于读者&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;查看通知&lt;/strong&gt; - 访问博客时，右下角通知面板自动显示新文章和更新文章&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查看差异&lt;/strong&gt; - 点击更新文章，自动显示变更高亮（红色删除/绿色新增）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳转定位&lt;/strong&gt; - 点击&quot;跳转到更新处&quot;按钮，快速定位到变更位置&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;对于开发者&lt;/h3&gt;
&lt;p&gt;调试模式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:4321/blog/article-slug?__diff_debug=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;强制显示差异：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:4321/blog/article-slug?diff=1#post-diff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;清除 IndexedDB（调试用途）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:4321/?__diff_debug_cleanup=1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实现细节&lt;/h2&gt;
&lt;h3&gt;IndexedDB 版本管理&lt;/h3&gt;
&lt;p&gt;当数据库结构变化时，需要升级版本。为避免版本冲突，生产环境使用了新的数据库名称：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const DB_NAME = &apos;blog-rss-store-v2&apos;; // 使用 v2 避免与旧版本冲突
const DB_VERSION = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;路径匹配&lt;/h3&gt;
&lt;p&gt;处理不同部署路径的情况：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function stripBasePath(pathname) {
    const base = normalizePathname(BASE_URL);
    const p = normalizePathname(pathname);
    if (p.startsWith(`${base}/`)) return p.slice(base.length) || &apos;/&apos;;
    return p;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;RSS 路径适配&lt;/h3&gt;
&lt;p&gt;生产环境可能使用不同的 basePath，需要尝试多个 RSS 路径：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const possiblePaths = [
    basePath ? `${basePath}/rss.xml` : &apos;/rss.xml&apos;,
    &apos;/rss.xml&apos;,
    &apos;./rss.xml&apos;,
    &apos;rss.xml&apos;
];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;图片变更处理&lt;/h3&gt;
&lt;p&gt;图片变更通过 &lt;code&gt;outline&lt;/code&gt; 样式标记：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.post-inline-diff-add-target-img {
    outline: 2px solid #4ade80;
    outline-offset: 2px;
}

.post-inline-diff-del-target-img {
    outline: 2px solid #f87171;
    opacity: 0.7;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;遇到的问题与解决方案&lt;/h2&gt;
&lt;h3&gt;1. 子模块部署问题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：Cloudflare Pages 部署时无法克隆 git 子模块&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：移除 &lt;code&gt;.gitmodules&lt;/code&gt; 文件，将 &lt;code&gt;Blogs_worker&lt;/code&gt; 和 &lt;code&gt;fuwari&lt;/code&gt; 作为普通目录保留在本地&lt;/p&gt;
&lt;h3&gt;2. IndexedDB 版本冲突&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：生产环境数据库版本与本地不一致，导致 &lt;code&gt;VersionError&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：修改数据库名称为 &lt;code&gt;blog-rss-store-v2&lt;/code&gt;，重新开始&lt;/p&gt;
&lt;h3&gt;3. 变量重复定义&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：&lt;code&gt;NOTIFICATION_STATE_KEY&lt;/code&gt; 在组件顶部和函数内重复定义，导致构建后变量提升问题&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：移除函数内的重复定义&lt;/p&gt;
&lt;h3&gt;4. 日期字段不匹配&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：友链朋友圈 API 返回的日期字段是 &lt;code&gt;created&lt;/code&gt; 而不是 &lt;code&gt;date&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：统一使用 &lt;code&gt;created&lt;/code&gt; 字段&lt;/p&gt;
&lt;h3&gt;5. Referrer 不可靠&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：生产环境 &lt;code&gt;document.referrer&lt;/code&gt; 可能为空&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：同时检查 localStorage 中是否有通知状态&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;通过 RSS + IndexedDB + diff 的组合，实现了完整的新文章通知和历史修订功能。读者可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;及时了解博客更新&lt;/li&gt;
&lt;li&gt;直观查看文章变更&lt;/li&gt;
&lt;li&gt;快速定位更新位置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套方案不依赖后端，完全在浏览器端实现，适合静态博客部署。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kpdecker/jsdiff&quot;&gt;diff 库文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API&quot;&gt;IndexedDB API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><updated>2026-03-13T00:00:00.000Z</updated></item><item><title>如何为 Astro 博客添加 AI 摘要功能</title><link>https://blog.261770.xyz/blog/how-to-add-ai-summary/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/how-to-add-ai-summary/</guid><description>详细介绍如何为 Astro 静态博客添加 AI 摘要功能，包括脚本编写、组件开发和样式设计</description><pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;如何为 Astro 博客添加 AI 摘要功能&lt;/h1&gt;
&lt;p&gt;在这篇文章中，我将详细介绍如何为 Astro 静态博客添加 AI 摘要功能，让读者在打开文章时能够快速了解文章的核心内容。&lt;/p&gt;
&lt;h2&gt;功能效果&lt;/h2&gt;
&lt;p&gt;添加 AI 摘要后，文章页面会显示一个精美的摘要卡片：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🤖 机器人图标 + &quot;AI 摘要&quot; 标题&lt;/li&gt;
&lt;li&gt;✨ 打字机动画效果展示摘要&lt;/li&gt;
&lt;li&gt;🏷️ &quot;AI Generated&quot; 标签&lt;/li&gt;
&lt;li&gt;⚠️ 免责声明&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实现原理&lt;/h2&gt;
&lt;p&gt;整个方案分为三个部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;摘要生成脚本&lt;/strong&gt; - 调用 AI API 为文章生成摘要&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;摘要展示组件&lt;/strong&gt; - 在文章页面渲染摘要卡片&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;样式设计&lt;/strong&gt; - 美观的卡片样式和动画效果&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第一步：创建摘要生成脚本&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;scripts/generateSummary.ts&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import fs from &apos;node:fs&apos;
import path from &apos;node:path&apos;
import dotenv from &apos;dotenv&apos;

dotenv.config()

const ROOT = process.cwd()
const BLOG_DIR = path.join(ROOT, &apos;src&apos;, &apos;content&apos;, &apos;blog&apos;)
const SUMMARY_MAX_LEN = 150

// 扫描所有文章
function findMarkdownEntries(dir: string): string[] {
  const results: string[] = []
  const items = fs.readdirSync(dir, { withFileTypes: true })
  for (const item of items) {
    const fp = path.join(dir, item.name)
    if (item.isDirectory()) {
      results.push(...findMarkdownEntries(fp))
    } else if (item.isFile() &amp;amp;&amp;amp; /\.mdx?$/.test(item.name)) {
      results.push(fp)
    }
  }
  return results
}

// 提取 frontmatter 和正文
function splitFrontmatterAndBody(content: string) {
  content = content.replace(/^\uFEFF?/, &apos;&apos;).trimStart()
  const re = /^---\r?\n[\s\S]*?\r?\n---(?=\r?\n|$)/
  const match = re.exec(content)
  if (!match) return { frontmatter: &apos;&apos;, body: content }
  const fm = match[0]
  const body = content.slice(match.index + fm.length).replace(/^\r?\n*/, &apos;&apos;)
  return { frontmatter: fm, body }
}

// 清洗正文内容
function sanitizeBodyForAPI(body: string): string {
  return body
    .replace(/```[\s\S]*?```/g, &apos;&apos;)
    .replace(/`[^`]*`/g, &apos;&apos;)
    .replace(/!\[[^\]]*\]\([^\)]+\)/g, &apos;&apos;)
    .replace(/\[([^\]]+)\]\([^\)]+\)/g, &apos;$1&apos;)
    .replace(/^[ \t]*#{1,6}[^\n]*\n/gm, &apos;&apos;)
    .replace(/\r?\n+/g, &apos; &apos;)
    .replace(/\s{2,}/g, &apos; &apos;)
    .trim()
}

// 调用 AI API 生成摘要
async function callSummaryAPI(title: string, body: string): Promise&amp;lt;string | null&amp;gt; {
  const api = process.env.AI_SUMMARY_API
  const key = process.env.AI_SUMMARY_KEY
  const model = process.env.AI_SUMMARY_MODEL || &apos;lite&apos;

  if (!api) return null

  const prompt = `你是一个文章摘要生成助手。请用一句话（不超过120字）简洁总结给定文章的核心内容，使用陈述句，结尾必须用句号。只返回摘要内容，不要有任何前缀或解释。

标题：${title}

正文：${body.slice(0, 3000)}`

  try {
    const res = await fetch(api, {
      method: &apos;POST&apos;,
      headers: {
        &apos;Content-Type&apos;: &apos;application/json&apos;,
        &apos;Authorization&apos;: key ? `Bearer ${key}` : &apos;&apos;
      },
      body: JSON.stringify({ model, content: prompt })
    })
    if (!res.ok) throw new Error(&apos;HTTP &apos; + res.status)
    const data = await res.json()
    let summary = data.choices?.[0]?.message?.content || data.summary || data.content
    if (typeof summary !== &apos;string&apos;) return null
    summary = summary.trim()
    // 清理可能的 markdown 代码块
    summary = summary.replace(/^```[\s\S]*?\n/, &apos;&apos;).replace(/```$/, &apos;&apos;).trim()
    // 确保以句号结尾
    if (!summary.endsWith(&apos;。&apos;) &amp;amp;&amp;amp; !summary.endsWith(&apos;.&apos;)) {
      summary = summary + &apos;。&apos;
    }
    // 限制长度
    if (summary.length &amp;gt; SUMMARY_MAX_LEN) {
      const lastPeriod = summary.lastIndexOf(&apos;。&apos;, SUMMARY_MAX_LEN)
      if (lastPeriod &amp;gt; 50) {
        summary = summary.slice(0, lastPeriod + 1)
      } else {
        summary = summary.slice(0, SUMMARY_MAX_LEN - 1) + &apos;。&apos;
      }
    }
    return summary
  } catch (err) {
    console.error(&apos;API 调用失败:&apos;, err)
    return null
  }
}

// 在 frontmatter 中写入 summary
function upsertSummaryInFrontmatter(frontmatter: string, summary: string): string {
  if (!frontmatter) {
    return `---\nsummary: &quot;${summary.replace(/&quot;/g, &apos;\\&quot;&apos;)}&quot;\n---\n`
  }
  const lines = frontmatter.split(&apos;\n&apos;)
  let endIdx = lines.findIndex((l, i) =&amp;gt; i &amp;gt; 0 &amp;amp;&amp;amp; l.trim() === &apos;---&apos;)
  if (endIdx === -1) endIdx = lines.length
  const bodyLines = lines
    .slice(1, endIdx)
    .filter(l =&amp;gt; !/^\s*summary\s*:/i.test(l))
  const rebuilt = [&apos;---&apos;, ...bodyLines, `summary: &quot;${summary.replace(/&quot;/g, &apos;\\&quot;&apos;)}&quot;`, &apos;---&apos;]
  return rebuilt.join(&apos;\n&apos;) + &apos;\n&apos;
}

// 主函数
async function run() {
  const files = findMarkdownEntries(BLOG_DIR)
  console.log(`找到 ${files.length} 篇文章`)

  for (const file of files) {
    const content = fs.readFileSync(file, &apos;utf8&apos;)
    const { frontmatter, body } = splitFrontmatterAndBody(content)
    
    // 检查是否已有摘要
    if (/summary\s*:/i.test(frontmatter)) {
      console.log(`跳过已有摘要: ${path.basename(file)}`)
      continue
    }

    const title = frontmatter.match(/title:\s*[&apos;&quot;]?([^&apos;&quot;\n]+)[&apos;&quot;]?/)?.[1] || &apos;&apos;
    const cleanBody = sanitizeBodyForAPI(body)
    
    console.log(`生成摘要: ${path.basename(file)}`)
    const summary = await callSummaryAPI(title, cleanBody)
    
    if (summary) {
      const newFrontmatter = upsertSummaryInFrontmatter(frontmatter, summary)
      const newContent = newFrontmatter + body
      fs.writeFileSync(file, newContent, &apos;utf8&apos;)
      console.log(`✓ 已写入摘要: ${summary.slice(0, 50)}...`)
    } else {
      console.log(`✗ 生成失败: ${path.basename(file)}`)
    }
  }
}

run().catch(console.error)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第二步：配置环境变量&lt;/h2&gt;
&lt;p&gt;创建或修改 &lt;code&gt;.env&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# AI 摘要生成配置
AI_SUMMARY_API=https://your-api-endpoint.com/v1/chat/completions
AI_SUMMARY_KEY=your-api-key
AI_SUMMARY_MODEL=gpt-3.5-turbo
AISUMMARY_CONCURRENCY=2
AISUMMARY_COVER_ALL=false
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第三步：创建摘要展示组件&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/components/AISummary.astro&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;---
interface Props {
  summary: string;
}

const { summary } = Astro.props;
---

&amp;lt;div class=&quot;aisummary-container&quot;&amp;gt;
  &amp;lt;div class=&quot;aisummary-title&quot;&amp;gt;
    &amp;lt;div class=&quot;aisummary-title-icon&quot;&amp;gt;
      &amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 48 48&quot;&amp;gt;
        &amp;lt;path d=&quot;M34.7...&quot; fill=&quot;currentColor&quot;/&amp;gt;
      &amp;lt;/svg&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;aisummary-title-text&quot;&amp;gt;AI 摘要&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;aisummary-tag&quot;&amp;gt;AI Generated&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;aisummary-explanation&quot; data-ai-summary={summary}&amp;gt;{summary}&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;aisummary-disclaimer&quot;&amp;gt;本摘要由 AI 生成，仅供参考，内容准确性请以原文为准。&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  // 打字机动画
  let isRunning = false;
  
  function typeWriter(element: HTMLElement, text: string) {
    if (isRunning) return;
    isRunning = true;
    
    const prefersReducedMotion = window.matchMedia(&apos;(prefers-reduced-motion: reduce)&apos;).matches;
    if (prefersReducedMotion) {
      element.textContent = text;
      return;
    }
    
    element.innerHTML = &apos;&amp;lt;span class=&quot;text-zinc-400&quot;&amp;gt;生成中...&amp;lt;/span&amp;gt;&amp;lt;span class=&quot;blinking-cursor&quot;&amp;gt;&amp;lt;/span&amp;gt;&apos;;
    
    let i = 0;
    const type = () =&amp;gt; {
      if (i &amp;lt; text.length) {
        element.innerHTML = text.slice(0, i + 1) + &apos;&amp;lt;span class=&quot;blinking-cursor&quot;&amp;gt;&amp;lt;/span&amp;gt;&apos;;
        i++;
        setTimeout(type, 20);
      } else {
        element.textContent = text;
        isRunning = false;
      }
    };
    
    // 使用 IntersectionObserver 在可见时开始动画
    const observer = new IntersectionObserver((entries) =&amp;gt; {
      if (entries[0].isIntersecting) {
        setTimeout(type, 300);
        observer.disconnect();
      }
    }, { threshold: 0.1 });
    
    observer.observe(element.closest(&apos;.aisummary-container&apos;)!);
  }
  
  function init() {
    const el = document.querySelector(&apos;.aisummary-explanation&apos;);
    if (!el) return;
    const summary = el.getAttribute(&apos;data-ai-summary&apos;);
    if (summary) typeWriter(el as HTMLElement, summary);
  }
  
  document.addEventListener(&apos;DOMContentLoaded&apos;, init);
  document.addEventListener(&apos;astro:page-load&apos;, init);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第四步：添加样式&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/styles/global.css&lt;/code&gt; 中添加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.aisummary-container {
  @apply rounded-xl border border-zinc-200/80 bg-zinc-50/80 p-4 my-5;
  @apply dark:border-zinc-700/80 dark:bg-zinc-900/60;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}

.aisummary-title {
  @apply flex items-center gap-2 text-zinc-600 dark:text-zinc-400;
}

.aisummary-title-text {
  @apply font-semibold text-sm;
}

.aisummary-tag {
  @apply text-xs font-semibold px-2 py-1 rounded ml-auto bg-blue-500 text-white;
}

.aisummary-explanation {
  @apply mt-3 px-4 py-3 rounded-lg border border-zinc-200/60 bg-white/90;
  @apply dark:border-zinc-700/60 dark:bg-zinc-800/60;
  @apply text-sm leading-relaxed text-zinc-700 dark:text-zinc-300;
  position: relative;
}

.aisummary-explanation::before {
  content: &quot;&quot;;
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 3px;
  @apply bg-blue-500 rounded-l-lg;
}

.aisummary-disclaimer {
  @apply text-xs text-zinc-400 dark:text-zinc-500 mt-2 px-1;
}

.blinking-cursor {
  @apply inline-block w-2 h-4 align-middle ml-1 rounded-sm bg-blue-500;
  animation: blinking-cursor 0.6s infinite;
}

@keyframes blinking-cursor {
  0%, 40% { opacity: 1; }
  50%, 90% { opacity: 0; }
  100% { opacity: 1; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第五步：修改文章布局&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/content/layouts/BlogPost.astro&lt;/code&gt; 中添加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;---
import AISummary from &apos;../../components/AISummary.astro&apos;;
// ... 其他导入

type Props = {
  // ... 其他属性
  summary?: string;
};

const { summary, /* ... */ } = Astro.props;
---

&amp;lt;!-- 在文章正文前显示摘要 --&amp;gt;
{summary &amp;amp;&amp;amp; &amp;lt;AISummary summary={summary} /&amp;gt;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第六步：更新内容配置&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/content.config.ts&lt;/code&gt; 中添加 summary 字段：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const blog = defineCollection({
  loader: glob({ base: &apos;./src/content/blog&apos;, pattern: &apos;**/*.{md,mdx}&apos; }),
  schema: ({ image }) =&amp;gt;
    z.object({
      // ... 其他字段
      summary: z.string().optional(),
    }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第七步：生成摘要&lt;/h2&gt;
&lt;p&gt;运行脚本为所有文章生成摘要：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx tsx scripts/generateSummary.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;强制重新生成所有摘要：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# PowerShell
$env:AISUMMARY_COVER_ALL=&quot;true&quot;; npx tsx scripts/generateSummary.ts

# Bash
AISUMMARY_COVER_ALL=true npx tsx scripts/generateSummary.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;环境变量&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AI_SUMMARY_API&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI API 端点&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AI_SUMMARY_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API 密钥&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AI_SUMMARY_MODEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型名称&lt;/td&gt;
&lt;td&gt;lite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AISUMMARY_CONCURRENCY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;并发数&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AISUMMARY_COVER_ALL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否覆盖已有摘要&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API 兼容性&lt;/strong&gt;：脚本使用 &lt;code&gt;content&lt;/code&gt; 字段发送请求，如果你的 API 使用 &lt;code&gt;messages&lt;/code&gt; 格式，需要修改 &lt;code&gt;callSummaryAPI&lt;/code&gt; 函数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字数控制&lt;/strong&gt;：摘要限制在 150 字以内，确保简洁明了&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本地兜底&lt;/strong&gt;：当 API 不可用时，脚本会使用本地规则生成简单摘要&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无障碍支持&lt;/strong&gt;：打字机动画会检测 &lt;code&gt;prefers-reduced-motion&lt;/code&gt; 设置，尊重用户的动画偏好&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;通过以上步骤，你就可以为 Astro 博客添加一个美观实用的 AI 摘要功能。读者打开文章时，会看到一个带有打字机动画的摘要卡片，快速了解文章核心内容。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;Astro Content Collections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://platform.openai.com/docs/api-reference&quot;&gt;OpenAI API 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>如何实现博客评论邮件通知功能</title><link>https://blog.261770.xyz/blog/how-to-implement-comment-notifications/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/how-to-implement-comment-notifications/</guid><description>详细介绍如何在 Astro + Cloudflare Workers 博客中实现评论邮件通知功能，支持 QQ 邮箱 SMTP</description><pubDate>Wed, 11 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;如何实现博客评论邮件通知功能&lt;/h1&gt;
&lt;p&gt;在这篇文章中，我将详细介绍如何为 Astro + Cloudflare Workers 博客添加评论邮件通知功能，让你在新评论到来时第一时间收到邮件提醒。&lt;/p&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;前端&lt;/strong&gt;: Astro + React&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后端&lt;/strong&gt;: Cloudflare Workers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;邮件服务&lt;/strong&gt;: QQ 邮箱 SMTP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库&lt;/strong&gt;: Cloudflare D1&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;整体架构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;用户发表评论
    ↓
前端调用 API → Cloudflare Worker
    ↓
保存到 D1 数据库
    ↓
异步发送邮件通知 (ctx.waitUntil)
    ↓
QQ 邮箱 SMTP 发送邮件
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;核心实现步骤&lt;/h2&gt;
&lt;h3&gt;1. 后端邮件服务&lt;/h3&gt;
&lt;p&gt;在 Cloudflare Worker 中创建 SMTP 客户端：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// src/email/smtp.ts
import { connect } from &apos;cloudflare:sockets&apos;;

export interface SMTPConfig {
  host: string;
  port: number;
  secure: boolean;
  auth: {
    user: string;
    pass: string;
  };
}

export class SMTPClient {
  private socket: ReturnType&amp;lt;typeof connect&amp;gt; | null = null;
  private reader: ReadableStreamDefaultReader&amp;lt;Uint8Array&amp;gt; | null = null;
  private writer: WritableStreamDefaultWriter&amp;lt;Uint8Array&amp;gt; | null = null;
  private encoder = new TextEncoder();
  private decoder = new TextDecoder();

  constructor(private config: SMTPConfig) {}

  async send(message: SMTPMessage): Promise&amp;lt;void&amp;gt; {
    try {
      await this.connect();
      await this.ehlo();
      await this.auth();
      await this.mailFrom(message.from);
      
      for (const to of message.to) {
        await this.rcptTo(to);
      }
      
      await this.data(message);
      await this.quit();
    } finally {
      await this.close();
    }
  }
  
  // ... 其他方法
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. QQ 邮箱配置&lt;/h3&gt;
&lt;p&gt;QQ 邮箱需要使用 &lt;strong&gt;端口 465 + 直接 TLS&lt;/strong&gt;，而不是 587 + STARTTLS：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export function createQQMailConfig(auth: { user: string; pass: string }): SMTPConfig {
  return {
    host: &apos;smtp.qq.com&apos;,
    port: 465,        // 使用直接 TLS
    secure: true,     // 启用 TLS
    auth,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意&lt;/strong&gt;: QQ 邮箱的密码不是登录密码，而是需要在邮箱设置中生成的 &lt;strong&gt;授权码&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3. 邮件模板&lt;/h3&gt;
&lt;p&gt;创建美观的 HTML 邮件模板：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;private buildEmailTemplate(data: CommentNotificationData): string {
  return `&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
  &amp;lt;style&amp;gt;
    body { 
      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
      max-width: 600px; 
      margin: 0 auto; 
      padding: 20px;
      background: #f5f5f5;
    }
    .container { 
      background: white; 
      border-radius: 8px; 
      padding: 30px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .button { 
      display: inline-block; 
      background: #2563eb; 
      color: white; 
      padding: 12px 24px; 
      text-decoration: none; 
      border-radius: 6px;
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;h1&amp;gt;📝 新评论通知&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;你的博客收到了一条新评论&amp;lt;/p&amp;gt;
    &amp;lt;a href=&quot;${postUrl}#comment-${data.commentId}&quot; class=&quot;button&quot;&amp;gt;
      查看评论
    &amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;`;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 异步发送邮件&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;ctx.waitUntil()&lt;/code&gt; 确保邮件发送不会阻塞 HTTP 响应：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;async function handleCommentsPost(request: Request, env: Env, ctx: ExecutionContext) {
  // 1. 保存评论到数据库
  const comment = await saveComment(data, env);
  
  // 2. 异步发送邮件通知
  ctx.waitUntil(
    emailService.sendCommentNotification({
      postSlug: data.post_id,
      commentId: comment.id,
      commentContent: data.content,
      authorName: user.name,
      authorUsername: user.username,
      createdAt: Date.now(),
    }).catch((err) =&amp;gt; {
      console.error(&apos;Failed to send comment notification:&apos;, err);
    })
  );
  
  return new Response(JSON.stringify(comment), { status: 201 });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 环境变量配置&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;wrangler.toml&lt;/code&gt; 中配置 SMTP：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[vars]
SMTP_PROVIDER = &quot;qq&quot;
SMTP_USER = &quot;your-email@qq.com&quot;
EMAIL_TO = &quot;your-email@foxmail.com&quot;
EMAIL_FROM_NAME = &quot;博客评论通知&quot;
NOTIFY_ON_COMMENT = &quot;true&quot;

# 敏感信息使用 secrets
# wrangler secret put SMTP_PASS
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;踩坑记录&lt;/h2&gt;
&lt;h3&gt;1. STARTTLS 问题&lt;/h3&gt;
&lt;p&gt;最初使用端口 587 + STARTTLS 时遇到 &lt;code&gt;WritableStream has been closed&lt;/code&gt; 错误。解决方案是改用端口 465 + 直接 TLS。&lt;/p&gt;
&lt;h3&gt;2. 异步任务执行&lt;/h3&gt;
&lt;p&gt;Worker 会在 HTTP 响应返回后终止，需要使用 &lt;code&gt;ctx.waitUntil()&lt;/code&gt; 来确保邮件发送任务完成。&lt;/p&gt;
&lt;h3&gt;3. 邮件链接路径&lt;/h3&gt;
&lt;p&gt;确保邮件中的文章链接路径正确（如 &lt;code&gt;/blog/&lt;/code&gt; 而不是 &lt;code&gt;/posts/&lt;/code&gt;）。&lt;/p&gt;
&lt;h2&gt;最终效果&lt;/h2&gt;
&lt;p&gt;当有新评论时，你会收到如下邮件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标题&lt;/strong&gt;: 📝 新评论：{作者名} 评论了「{文章标题}」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内容&lt;/strong&gt;: 包含评论者信息、评论内容和查看链接&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回复通知&lt;/strong&gt;: 如果评论是回复，会通知被回复者&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;通过 Cloudflare Workers 的 TCP Sockets API，我们可以直接连接 SMTP 服务器发送邮件，无需部署额外的邮件服务。这种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 无需额外部署&lt;/li&gt;
&lt;li&gt;✅ 免费使用&lt;/li&gt;
&lt;li&gt;✅ 响应快速&lt;/li&gt;
&lt;li&gt;✅ 支持主流邮箱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;希望这篇教程对你有帮助！如有问题欢迎在评论区留言。&lt;/p&gt;
</content:encoded></item><item><title>欢迎来到异飨客的博客</title><link>https://blog.261770.xyz/blog/welcome-to-my-blog/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/welcome-to-my-blog/</guid><description>展示博客对 Markdown 的各种渲染效果，包括标题、列表、代码块、表格等</description><pubDate>Wed, 11 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;欢迎来到异飨客的博客 👋&lt;/h1&gt;
&lt;p&gt;这是一篇展示博客 &lt;strong&gt;Markdown 渲染效果&lt;/strong&gt; 的示例文章。让我们一起探索各种 Markdown 语法在这个博客中的呈现效果吧！&lt;/p&gt;
&lt;h2&gt;文本格式&lt;/h2&gt;
&lt;p&gt;Markdown 支持多种文本格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;粗体文字&lt;/strong&gt; - 使用双星号包裹&lt;/li&gt;
&lt;li&gt;&lt;em&gt;斜体文字&lt;/em&gt; - 使用单星号包裹&lt;/li&gt;
&lt;li&gt;&lt;s&gt;删除线&lt;/s&gt; - 使用波浪线包裹&lt;/li&gt;
&lt;li&gt;&lt;code&gt;行内代码&lt;/code&gt; - 使用反引号包裹&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.261770.xyz&quot;&gt;链接文本&lt;/a&gt; - 使用方括号和圆括号&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;列表&lt;/h2&gt;
&lt;h3&gt;无序列表&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;第一项&lt;/li&gt;
&lt;li&gt;第二项
&lt;ul&gt;
&lt;li&gt;嵌套项 1&lt;/li&gt;
&lt;li&gt;嵌套项 2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第三项&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;有序列表&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;第一步&lt;/li&gt;
&lt;li&gt;第二步&lt;/li&gt;
&lt;li&gt;第三步
&lt;ol&gt;
&lt;li&gt;子步骤 A&lt;/li&gt;
&lt;li&gt;子步骤 B&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;任务列表&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[x] 已完成的项目&lt;/li&gt;
&lt;li&gt;[x] 另一个已完成的项目&lt;/li&gt;
&lt;li&gt;[ ] 待完成的项目&lt;/li&gt;
&lt;li&gt;[ ] 另一个待完成的项目&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;代码块&lt;/h2&gt;
&lt;h3&gt;行内代码&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;console.log(&apos;Hello World&apos;)&lt;/code&gt; 可以快速展示代码片段。&lt;/p&gt;
&lt;h3&gt;代码块&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// JavaScript 示例
function greet(name) {
  return `Hello, ${name}! Welcome to my blog.`;
}

console.log(greet(&apos;访客&apos;));
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Python 示例
def fibonacci(n):
    if n &amp;lt;= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(f&quot;斐波那契数列第 10 项: {fibonacci(10)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* CSS 示例 */
.blog-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
  line-height: 1.8;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;表格&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;支持情况&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;标题&lt;/td&gt;
&lt;td&gt;六级标题支持&lt;/td&gt;
&lt;td&gt;✅ 完整支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;列表&lt;/td&gt;
&lt;td&gt;有序/无序/任务列表&lt;/td&gt;
&lt;td&gt;✅ 完整支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代码&lt;/td&gt;
&lt;td&gt;语法高亮&lt;/td&gt;
&lt;td&gt;✅ 完整支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;表格&lt;/td&gt;
&lt;td&gt;对齐和格式化&lt;/td&gt;
&lt;td&gt;✅ 完整支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数学公式&lt;/td&gt;
&lt;td&gt;LaTeX 支持&lt;/td&gt;
&lt;td&gt;✅ 完整支持&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;引用&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;知识就是力量。&quot;
—— 弗朗西斯·培根&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;提示&lt;/strong&gt;
这是一个提示块，可以用来强调重要信息。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;分隔线&lt;/h2&gt;
&lt;p&gt;使用三个或更多的星号、减号或下划线可以创建分隔线：&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;图片&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/fallback.jpg&quot; alt=&quot;博客占位图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;数学公式&lt;/h2&gt;
&lt;p&gt;行内公式：$E = mc^2$&lt;/p&gt;
&lt;p&gt;块级公式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-math&quot;&gt;\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;折叠内容&lt;/h2&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;点击展开更多内容&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;这是折叠起来的内容。可以用来隐藏一些补充信息，保持文章整洁。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隐藏的细节 1&lt;/li&gt;
&lt;li&gt;隐藏的细节 2&lt;/li&gt;
&lt;li&gt;隐藏的细节 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;感谢你阅读这篇示例文章！这个博客支持丰富的 Markdown 语法，让你可以专注于内容创作。&lt;/p&gt;
&lt;p&gt;如果你有任何问题或建议，欢迎在评论区留言交流！🎉&lt;/p&gt;
</content:encoded></item><item><title>组件展示</title><link>https://blog.261770.xyz/blog/component-show/</link><guid isPermaLink="true">https://blog.261770.xyz/blog/component-show/</guid><description>博客组件示例展示</description><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import EmojiClock from &apos;../../components/post/EmojiClock.astro&apos;;
import Key from &apos;../../components/post/Key.astro&apos;;
import LinkBanner from &apos;../../components/post/LinkBanner.astro&apos;;
import Pic from &apos;../../components/post/Pic.astro&apos;;
import Poetry from &apos;../../components/post/Poetry.astro&apos;;
import Quote from &apos;../../components/post/Quote.astro&apos;;
import Tab from &apos;../../components/post/Tab.astro&apos;;
import Timeline from &apos;../../components/post/Timeline.astro&apos;;
import Tip from &apos;../../components/post/Tip.astro&apos;;
import VideoEmbed from &apos;../../components/post/VideoEmbed.astro&apos;;&lt;/p&gt;
&lt;h2&gt;EmojiClock&lt;/h2&gt;
&lt;p&gt;现在几点了？&lt;/p&gt;
&lt;h3&gt;静态模式（每半小时）&lt;/h3&gt;
&lt;p&gt;&amp;lt;EmojiClock datetime=&quot;2024-01-01T12:00:00&quot; /&amp;gt; (12:00)
&amp;lt;EmojiClock datetime=&quot;2024-01-01T12:30:00&quot; /&amp;gt; (12:30)
&amp;lt;EmojiClock datetime=&quot;2024-01-01T13:00:00&quot; /&amp;gt; (13:00)&lt;/p&gt;
&lt;h3&gt;旋转模式（每分钟）&lt;/h3&gt;
&lt;p&gt;&amp;lt;EmojiClock rotate datetime=&quot;2024-01-01T12:00:00&quot; /&amp;gt; (12:00)
&amp;lt;EmojiClock rotate datetime=&quot;2024-01-01T12:05:00&quot; /&amp;gt; (12:05)
&amp;lt;EmojiClock rotate datetime=&quot;2024-01-01T12:30:00&quot; /&amp;gt; (12:30)&lt;/p&gt;
&lt;h3&gt;当前时间&lt;/h3&gt;
&lt;p&gt;&amp;lt;EmojiClock /&amp;gt; (当前时间)
&amp;lt;EmojiClock rotate /&amp;gt; (旋转模式)&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Key&lt;/h2&gt;
&lt;p&gt;按下键时会亮，可以通过 &lt;code&gt;@press&lt;/code&gt; 配置触发事件，鼠标点击也会触发事件，博客全站搜索框的按键提示使用了这个组件。&lt;/p&gt;
&lt;h3&gt;纯 Code&lt;/h3&gt;
&lt;p&gt;&amp;lt;Key code=&quot;Escape&quot; /&amp;gt; &amp;lt;Key code=&quot;F2&quot; /&amp;gt; &amp;lt;Key code=&quot;Control&quot; /&amp;gt; &amp;lt;Key code=&quot;A&quot; /&amp;gt; &amp;lt;Key code=&quot; &quot; /&amp;gt; &amp;lt;Key code=&quot;Tab&quot; /&amp;gt; &amp;lt;Key code=&quot;Enter&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;指定修饰符、图标、文本（macOS 自动使用图标）&lt;/h3&gt;
&lt;p&gt;&amp;lt;Key icon code=&quot;ArrowUp&quot; /&amp;gt; &amp;lt;Key icon code=&quot;Alt&quot; /&amp;gt; &amp;lt;Key icon code=&quot;Shift&quot; /&amp;gt; &amp;lt;Key text=&quot;空格&quot; /&amp;gt; &amp;lt;Key icon code=&quot;Tab&quot; /&amp;gt; &amp;lt;Key icon code=&quot;Enter&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;组合键&lt;/h3&gt;
&lt;p&gt;&amp;lt;Key ctrl shift code=&quot;A&quot; /&amp;gt; &amp;lt;Key shift alt /&amp;gt; &amp;lt;Key cmd code=&quot;K&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;热血组合技&lt;/h3&gt;
&lt;p&gt;上上下下左右左右BA &amp;lt;Key icon code=&quot;ArrowUp&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowUp&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowDown&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowDown&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowLeft&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowRight&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowLeft&quot; /&amp;gt; &amp;lt;Key icon code=&quot;ArrowRight&quot; /&amp;gt; &amp;lt;Key code=&quot;B&quot; /&amp;gt; &amp;lt;Key code=&quot;A&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;LinkBanner&lt;/h2&gt;
&lt;p&gt;带背景图的链接卡片，无描述时自动显示域名。&lt;/p&gt;
&lt;h3&gt;完整配置&lt;/h3&gt;
&lt;p&gt;&amp;lt;LinkBanner
banner=&quot;https://picsum.photos/800/400&quot;
title=&quot;示例网站&quot;
description=&quot;这是一行描述，如果不提供描述会展示域名&quot;
link=&quot;https://example.com&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;自动提取域名&lt;/h3&gt;
&lt;p&gt;&amp;lt;LinkBanner
banner=&quot;https://picsum.photos/800/400?random=2&quot;
title=&quot;GitHub&quot;
link=&quot;https://github.com&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;无背景图&lt;/h3&gt;
&lt;p&gt;&amp;lt;LinkBanner
title=&quot;纯文字链接卡片&quot;
description=&quot;没有背景图的简洁样式&quot;
link=&quot;https://astro.build&quot;
/&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Pic&lt;/h2&gt;
&lt;p&gt;用于展示图片，支持说明文字、点击后打开灯箱缩放。&lt;/p&gt;
&lt;h3&gt;基础用法&lt;/h3&gt;
&lt;p&gt;&amp;lt;Pic
src=&quot;https://picsum.photos/800/400?random=10&quot;
caption=&quot;说明文字，还支持通过 width 或 height 属性指定尺寸&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;指定尺寸&lt;/h3&gt;
&lt;p&gt;&amp;lt;Pic
src=&quot;https://picsum.photos/400/300?random=11&quot;
caption=&quot;宽度 300px&quot;
width=&quot;300&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;禁用缩放&lt;/h3&gt;
&lt;p&gt;&amp;lt;Pic
src=&quot;https://picsum.photos/600/300?random=12&quot;
caption=&quot;禁用点击缩放功能&quot;
zoom={false}
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;仅使用 alt&lt;/h3&gt;
&lt;p&gt;&amp;lt;Pic
src=&quot;https://picsum.photos/700/350?random=13&quot;
alt=&quot;这是一张美丽的风景图片&quot;
/&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Poetry&lt;/h2&gt;
&lt;p&gt;用于展示诗歌内容，支持标题、作者、落款，内容居中显示并保留换行格式。&lt;/p&gt;
&lt;h3&gt;完整配置&lt;/h3&gt;
&lt;p&gt;&amp;lt;Poetry
title=&quot;诗有诗的标题&quot;
author=&quot;一名作者&quot;
footer=&quot;可选的落款&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;如你所见，
我，
是一首——
诗。
&amp;lt;/Poetry&amp;gt;&lt;/p&gt;
&lt;h3&gt;仅标题和内容&lt;/h3&gt;
&lt;p&gt;&amp;lt;Poetry title=&quot;静夜思&quot;&amp;gt;
床前明月光，
疑是地上霜。
举头望明月，
低头思故乡。
&amp;lt;/Poetry&amp;gt;&lt;/p&gt;
&lt;h3&gt;无标题&lt;/h3&gt;
&lt;p&gt;&amp;lt;Poetry&amp;gt;
白日依山尽，
黄河入海流。
欲穷千里目，
更上一层楼。
&amp;lt;/Poetry&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Quote&lt;/h2&gt;
&lt;p&gt;用于展示引用内容，支持自定义图标（使用 Iconify 图标库），也支持通过 &lt;code&gt;iconHtml&lt;/code&gt; 使用 Emoji 或颜文字作为图标。&lt;/p&gt;
&lt;h3&gt;默认图标&lt;/h3&gt;
&lt;p&gt;&amp;lt;Quote&amp;gt;
有时候，有些话，有点意思。
&amp;lt;/Quote&amp;gt;&lt;/p&gt;
&lt;h3&gt;自定义 Iconify 图标&lt;/h3&gt;
&lt;p&gt;&amp;lt;Quote icon=&quot;ph:files-duotone&quot;&amp;gt;
令图标有所指，引用亦有中心。
&amp;lt;/Quote&amp;gt;&lt;/p&gt;
&lt;h3&gt;使用 Emoji/颜文字作为图标&lt;/h3&gt;
&lt;p&gt;&amp;lt;Quote iconHtml=&quot;ヾ(•ω•`)o&quot;&amp;gt;
图标也可以是 Emoji 或颜文字，或者英文装饰。
&amp;lt;/Quote&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Quote iconHtml=&quot;💡&quot;&amp;gt;
灵感就像闪电，稍纵即逝，需要及时记录。
&amp;lt;/Quote&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Quote iconHtml=&quot;❝&quot;&amp;gt;
To be or not to be, that is the question.
&amp;lt;/Quote&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Tab&lt;/h2&gt;
&lt;p&gt;标签页组件，支持切换不同内容。将内容按顺序放入组件内，第一个子元素对应第一个标签，以此类推。&lt;/p&gt;
&lt;h3&gt;基础用法&lt;/h3&gt;
&lt;p&gt;&amp;lt;Tab tabs={[&apos;组件&apos;, &apos;语法&apos;]}&amp;gt;
&amp;lt;div&amp;gt;
这是组件标签页的内容。
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
这是语法标签页的内容。
&amp;lt;/div&amp;gt;
&amp;lt;/Tab&amp;gt;&lt;/p&gt;
&lt;h3&gt;自定义默认展示标签&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;active&lt;/code&gt; 属性设置默认展示的标签页（从1开始计数）。&lt;/p&gt;
&lt;p&gt;&amp;lt;Tab tabs={[&apos;当当当&apos;, &apos;高级交互！&apos;, &apos;就是藏得有点深&apos;]} active={3}&amp;gt;
&amp;lt;div&amp;gt;
第一个标签页的内容。
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
第二个标签页的内容。
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
你找到我了吗？（默认展示第三个标签页）
&amp;lt;/div&amp;gt;
&amp;lt;/Tab&amp;gt;&lt;/p&gt;
&lt;h3&gt;居中显示&lt;/h3&gt;
&lt;p&gt;&amp;lt;Tab tabs={[&apos;居中&apos;, &apos;标签页&apos;]} center&amp;gt;
&amp;lt;div&amp;gt;
标签页整体居中显示。
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
内容也会跟随居中。
&amp;lt;/div&amp;gt;
&amp;lt;/Tab&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Timeline&lt;/h2&gt;
&lt;p&gt;时间线组件，用于展示时间顺序的内容。&lt;/p&gt;
&lt;h3&gt;基础用法&lt;/h3&gt;
&lt;p&gt;&amp;lt;Timeline
items={[
{ caption: &apos;前天&apos;, content: &apos;看到了小兔&apos; },
{ caption: &apos;昨天&apos;, content: &apos;是小鹿&apos; },
{ caption: &apos;今天&apos;, content: &apos;是你。&apos; },
]}
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;更多内容&lt;/h3&gt;
&lt;p&gt;&amp;lt;Timeline
items={[
{ caption: &apos;今日无事&apos; },
{ caption: &apos;今日依旧无事&apos; },
{ caption: &apos;然后——&apos; },
{ content: &apos;一件事&amp;lt;br&amp;gt;两件事。&apos; },
{ content: &apos;&amp;lt;em&amp;gt;再添一笔。&amp;lt;/em&amp;gt;&apos; },
]}
/&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Tip&lt;/h2&gt;
&lt;p&gt;提示组件，显示带下划线的文本，支持鼠标悬停显示提示框和点击复制功能。&lt;/p&gt;
&lt;h3&gt;基础用法（带提示框）&lt;/h3&gt;
&lt;p&gt;我是一条小提示，&amp;lt;Tip text=&quot;我没有图标&quot; tip=&quot;提示的内容是提示&quot; /&amp;gt;，鼠标悬停查看提示。&lt;/p&gt;
&lt;h3&gt;带图标和提示&lt;/h3&gt;
&lt;p&gt;&amp;lt;Tip text=&quot;带图标的提示&quot; icon tip=&quot;或许也可以没有内容&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;复制功能&lt;/h3&gt;
&lt;p&gt;&amp;lt;Tip copy&amp;gt;+v 点击就能复制，太方便了！&amp;lt;/Tip&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;VideoEmbed&lt;/h2&gt;
&lt;p&gt;视频嵌入组件，支持多种视频平台（原始视频、Bilibili 等），自动适配不同平台的宽高比。&lt;/p&gt;
&lt;h3&gt;原始视频&lt;/h3&gt;
&lt;p&gt;支持自定义海报图，适合展示本地或直链视频。&lt;/p&gt;
&lt;p&gt;&amp;lt;VideoEmbed
type=&quot;raw&quot;
id=&quot;https://sf-atsx-tob.larksuite.com/obj/static-atsx-online-sg-ee-tob-mycis/02c7da694d343896877c09de9db4fc42/8ede49e0a92f53cdafbbf49339194986d9d900fb2abe242b9a8b4e338bf18b05.mp4&quot;
poster=&quot;https://sf-atsx-tob.larksuite.com/obj/static-atsx-online-sg-ee-tob-mycis/02c7da694d343896877c09de9db4fc42/e23074879c61a4d61e905ccef5771a36a2d19689c1204c2b32caa53711ac83ad.png&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Bilibili 视频&lt;/h3&gt;
&lt;p&gt;&amp;lt;VideoEmbed
type=&quot;bilibili&quot;
id=&quot;BV1Yr421p7rW&quot;
/&amp;gt;&lt;/p&gt;
&lt;h3&gt;自定义宽高比&lt;/h3&gt;
&lt;p&gt;&amp;lt;VideoEmbed
type=&quot;bilibili&quot;
id=&quot;BV1Yr421p7rW&quot;
ratio=&quot;4/3&quot;
/&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;
&lt;p&gt;更多组件示例...&lt;/p&gt;
</content:encoded></item></channel></rss>