<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>拾星漫记 - AISummary</title>
<link>https://blog.zhifouli.top/index.php/tag/AISummary/</link>
<atom:link href="https://blog.zhifouli.top/index.php/feed/tag/AISummary/" rel="self" type="application/rss+xml" />
<language>zh-CN</language>
<description></description>
<lastBuildDate>Fri, 25 Jul 2025 01:00:00 +0800</lastBuildDate>
<pubDate>Fri, 25 Jul 2025 01:00:00 +0800</pubDate>
<item>
<title>解决 AISummary插件自定义样式未遍历多容器导致内容不显示的问题</title>
<link>https://blog.zhifouli.top/index.php/archives/22/</link>
<guid>https://blog.zhifouli.top/index.php/archives/22/</guid>
<pubDate>Fri, 25 Jul 2025 01:00:00 +0800</pubDate>
<dc:creator>知否离</dc:creator>
<description><![CDATA[文章介绍了在使用AISummary插件时，因多容器导致摘要内容不显示的问题，并提供了遍历所有容器的增强代码及样式调整方案。]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<div class="aisummary"><p class="ai-header"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"         stroke-width="2" stroke-linecap="round" stroke-linejoin="round">         <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>         <path d="M3 10a4 4 0 0 1 4 -4h10a4 4 0 0 1 4 4v6a4 4 0 0 1 -4 4h-10a4 4 0 0 1 -4 -4v-6z"></path>         <path d="M8 3l2 3"></path>         <path d="M16 3l-2 3"></path>         <path d="M9 13v-2"></path>         <path d="M15 11v2"></path>     </svg>AI摘要 </p> <div class="ai-text-container">     <div class="ai-hidden-text">文章介绍了在使用AISummary插件时，因多容器导致摘要内容不显示的问题，并提供了遍历所有容器的增强代码及样式调整方案。</div>     <div class="ai-typewriter-text"></div> <span class="ai-cursor"></span> </div> <p class="ai-footer"> 此内容根据文章生成，仅用于文章内容的解释与总结 </p></div><h2>引言</h2><p>在为博客文章实现AI摘要工具时，我选择了 大佬--旋的来AISummary插件增强功能（非常好用）。然而，在按大佬的教程配置完毕后，我发现摘要正文并没有输出，经研究后得知，因为阅读模式的存在，出现了两个摘要容器，而大佬的自定义样式不会处理多个摘要容器，导致第二个摘要容器正文无法正常显示（当时不知道(⊙︿⊙)）。本文将详细记录这一问题的排查与解决方案，并在文末给出增强后的完整自定义样式代码。</p><p>插件文章链接：<a href="https://blog.ybyq.wang/archives/335.html">Typecho添加文章AI摘要功能（Handsome等全主题适配）By xuan</a></p><h2>问题描述</h2><h3>使用环境</h3><ul><li>博客平台: Typecho</li><li>博客主题: bearsimple</li></ul><h3>具体问题现象</h3><p>摘要正文未能正常加载<br><img src="https://upyuncdn.zhifouli.top/weblog/uploads/2025/07/2676412141.png" alt="" title=""></p><h2>排查过程</h2><h3>分析文章页的HTML文档</h3><p>可以看到，同时存在两个摘要容器，分别位于阅读模式页面处和文章页面处，并且阅读模式的容器为1号位，且1号位容器内容正常加载，而2号位为空值。故产生的现象为文章处摘要不加载，而阅读模式不会自动被使用，本人对该主题也是刚接触使用，不甚熟悉，故对本人的排查造成了不小的困扰。<br><img src="https://upyuncdn.zhifouli.top/weblog/uploads/2025/07/1375778050.png" alt="" title=""><br><img src="https://upyuncdn.zhifouli.top/weblog/uploads/2025/07/2960688968.png" alt="" title=""></p><h3>分析源代码</h3><p>主要的逻辑在executeAiSummaryTyping()中，其只处理了第一个容器</p><pre><code class="lang-javascript">function executeAiSummaryTyping() {
    // 【未增强版本】
    // 使用 document.querySelector() 只会获取页面上第一个匹配 &#039;.ai-typewriter-text&#039; 的元素。
    // 这意味着如果页面上有多个AI摘要容器，只有第一个摘要会执行打字机效果。
    const typewriterElement = document.querySelector(&#039;.ai-typewriter-text&#039;);
    // 获取隐藏的源文本元素。
    const sourceTextElement = document.querySelector(&#039;.ai-hidden-text&#039;);
    // 定义打字速度。
    const typingSpeed = 50; 
    
    // 如果没有找到对应的元素，则直接返回。
    if (!typewriterElement || !sourceTextElement) return;
    
    // 【未增强版本】
    // 使用全局变量 aiSummaryTypingTimeoutId 来管理定时器。
    // 当页面上有多个摘要时，如果前一个摘要的打字机效果还在进行中，新的打字机效果可能会清除掉旧的定时器，
    // 导致效果异常或只显示一个摘要的打字机效果。
    if (aiSummaryTypingTimeoutId) {
        clearTimeout(aiSummaryTypingTimeoutId);
    }

    // 获取并处理源文本，添加首行缩进。
    let textToType = sourceTextElement.textContent.trim();
    
    if (textToType.length &gt; 0) {
        textToType = &#039;  &#039; + textToType; // 首行缩进
    }

    // 清空打字机文本内容。
    typewriterElement.textContent = &#039;&#039;;
    // 初始化字符索引。
    let charIndex = 0;

    // 定义打字机效果的内部函数。
    function typeNextCharacter() {
        // 如果还有字符未输出。
        if (charIndex &lt; textToType.length) {
            // 逐个添加字符。
            typewriterElement.textContent += textToType.charAt(charIndex);
            charIndex++;
            // 设置定时器，继续输出下一个字符。
            // 【未增强版本】这里仍然使用全局变量来存储定时器ID。
            aiSummaryTypingTimeoutId = setTimeout(typeNextCharacter, typingSpeed);
        } else {
            // 所有字符输出完毕，清除全局定时器ID。
            aiSummaryTypingTimeoutId = null;
        }
    }
    // 启动打字机效果。
    typeNextCharacter();
}</code></pre><h2>解决方案</h2><h3>对函数做多容器增强</h3><pre><code class="lang-javascript">function executeAiSummaryTyping() {
    // 获取页面上所有带有 &#039;aisummary&#039; 类的元素，这些元素代表了不同的AI摘要容器。
    const aiSummaryContainers = document.querySelectorAll(&#039;.aisummary&#039;);

    // 遍历每一个找到的AI摘要容器。
    aiSummaryContainers.forEach(container =&gt; {
        // 在当前容器内部，查找带有 &#039;ai-typewriter-text&#039; 类的元素，这是显示打字机效果的文本区域。
        const typewriterElement = container.querySelector(&#039;.ai-typewriter-text&#039;);
        // 在当前容器内部，查找带有 &#039;ai-hidden-text&#039; 类的元素，这是存储原始摘要文本的隐藏区域。
        const sourceTextElement = container.querySelector(&#039;.ai-hidden-text&#039;);
        // 定义打字速度，数值越小，打字速度越快。
        const typingSpeed = 50; 

        // 如果在当前容器中没有找到打字机文本元素或源文本元素，则跳过当前容器，不执行打字机效果。
        if (!typewriterElement || !sourceTextElement) return;

        // 清除可能存在的旧定时器。
        // 这里将定时器ID存储在 &#039;typewriterElement&#039; 自身的属性 &#039;typingTimeoutId&#039; 上，
        // 这样每个打字机元素都有独立的定时器，避免相互干扰，支持多摘要同时运行。
        if (typewriterElement.typingTimeoutId) {
            clearTimeout(typewriterElement.typingTimeoutId);
        }

        // 获取源文本内容并去除首尾空格。
        let textToType = sourceTextElement.textContent.trim();
        
        // 如果源文本不为空，则在文本开头添加两个空格，实现首行缩进效果。
        if (textToType.length &gt; 0) {
            textToType = &#039;  &#039; + textToType; 
        }

        // 清空打字机文本元素的当前内容，准备开始打字。
        typewriterElement.textContent = &#039;&#039;;
        // 初始化字符索引，用于追踪当前打字到哪个字符。
        let charIndex = 0;

        // 定义核心的打字机效果函数。
        const typeNextCharacter = () =&gt; {
            // 如果还有未输出的字符。
            if (charIndex &lt; textToType.length) {
                // 将当前字符添加到打字机文本元素中。
                typewriterElement.textContent += textToType.charAt(charIndex);
                // 字符索引递增。
                charIndex++;
                // 设置一个定时器，在指定延迟后再次调用 &#039;typeNextCharacter&#039; 函数，实现逐字输出。
                typewriterElement.typingTimeoutId = setTimeout(typeNextCharacter, typingSpeed);
            } else {
                // 所有字符都已输出完毕，清除该打字机元素的定时器ID。
                typewriterElement.typingTimeoutId = null;
            }
        };
        // 启动打字机效果，开始输出第一个字符。
        typeNextCharacter();
    });
}</code></pre><p>并用伪元素实现字符缩进</p><pre><code class="lang-javascript">.ai-typewriter-text {
    display: inline;
    word-wrap: break-word;
    white-space: pre-wrap;
}

/* 缩进样式 */
.ai-typewriter-text::before {
    content: &#039;&#039;;
    width: 2em; /* 调整宽度以匹配所需的缩进 */
    height: 1em; /* 高度与字体大小相匹配 */
    float: left;
    margin-top: -0.5em; /* 调整这个值以对齐文本基线 */
}</code></pre><h3>验证修复效果</h3><p>可以看到，两个容器都能正常加载数据，且缩进会更为合适<br>文章链接：<a href="https://blog.zhifouli.top/index.php/archives/13/">效果展示</a></p><p><img src="https://upyuncdn.zhifouli.top/weblog/uploads/2025/07/3553312173.png" alt="" title=""><br><img src="https://upyuncdn.zhifouli.top/weblog/uploads/2025/07/1018512033.png" alt="" title=""></p><h2>总结</h2><p>通过这次经历，我记住了在排查前端页面的渲染加载问题时，可以直接从HTML文档出发，从元素本身出发，这样方便我们由果溯因，从而少走弯路，节省时间和精力。再次感谢大佬，不但开发了如此好用的插件（有类似插件付费提供商），还会积极讨论解决问题，希望我的经验能够帮助到其他遇到类似问题的朋友。</p><h2>完整自定义样式</h2><pre><code class="lang-javascript">&lt;!-- AI摘要样式 - 阅读模式多摘要容器增强版 --&gt;
&lt;style&gt;
/* 摘要容器样式 */
.aisummary {
    background: #f7f7f9;
    border-radius: 12px;
    padding: 12px;
    box-shadow: 0 8px 16px -4px rgba(44, 45, 48, 0.047);
    border: 1px solid #e3e8f7;
    margin: 25px 0 30px;
    color: #333;
    position: relative;
    overflow: hidden;
}

/* 标题样式 */
.ai-header {
    margin-bottom: 10px !important;
    color: #465CEB !important;
    text-align: left !important;
    display: flex !important;
    align-items: center !important;
    text-indent: 0 !important;
    font-weight: bold !important;
    font-size: 17px !important;
}

.ai-header svg {
    margin-right: 8px;
    width: 24px;
    height: 24px;
    stroke: currentColor;
}

/* 文本容器样式 */
.ai-text-container {
    background: #fff;
    border-radius: 8px;
    padding: 12px 15px;
    border: 1px solid #e3e8f7;
    margin-bottom: 10px;
    font-size: 15px;
    line-height: 1.7;
    color: #333;
}

.ai-hidden-text {
    display: none;
}

.ai-typewriter-text {
    display: inline;
    word-wrap: break-word;
    white-space: pre-wrap;
}

/* 缩进样式 */
.ai-typewriter-text::before {
    content: &#039;&#039;;
    width: 2em; /* 调整宽度以匹配所需的缩进 */
    height: 1em; /* 高度与字体大小相匹配 */
    float: left;
    margin-top: -0.5em; /* 调整这个值以对齐文本基线 */
}

/* 光标样式及动画 */
.ai-cursor {
    display: inline-block;
    width: 2px;
    height: 1em;
    background-color: #465CEB;
    margin-left: 3px;
    animation: ai-blink 0.7s infinite;
    vertical-align: middle;
}

@keyframes ai-blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}

/* 页脚样式 */
.ai-footer {
    font-size: 13px !important;
    color: rgba(60, 60, 67, 0.65) !important;
    font-style: italic !important;
    margin-bottom: 0 !important;
    padding: 0 5px !important;
    text-align: left !important;
    text-indent: 0 !important;
    margin-top: 10px !important;
}

/* 响应式调整 */
@media (max-width: 768px) {
    .aisummary {
        padding: 10px;
        margin: 20px 0 25px;
    }
    .ai-header {
        font-size: 16px !important;
    }
    .ai-header svg {
        width: 22px;
        height: 22px;
        margin-right: 6px;
    }
    .ai-text-container {
        font-size: 14px;
        padding: 10px 12px;
        line-height: 1.65;
    }
    .ai-footer {
        font-size: 12px !important;
        margin-top: 8px !important;
    }
}

/* 暗色模式适配 */
[data-night=&quot;night&quot;] .aisummary,
.dark-mode .aisummary,
body.dark .aisummary,
body.night .aisummary,
.night .aisummary,
.night-mode .aisummary,
html.night .aisummary,
.theme-dark .aisummary {
    background: #2c2c2e;
    border-color: #38383a;
    color: #d1d1d1;
    box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.15);
}

[data-night=&quot;night&quot;] .ai-text-container,
.dark-mode .ai-text-container,
body.dark .ai-text-container,
body.night .ai-text-container,
.night .ai-text-container,
.night-mode .ai-text-container,
html.night .ai-text-container,
.theme-dark .ai-text-container {
    background: #333333;
    border-color: #4a4a4a;
    color: #c8c8c8;
}

[data-night=&quot;night&quot;] .ai-header,
.dark-mode .ai-header,
body.dark .ai-header,
body.night .ai-header,
.night .ai-header,
.night-mode .ai-header,
html.night .ai-header,
.theme-dark .ai-header {
    color: #7c89f1 !important;
}

[data-night=&quot;night&quot;] .ai-cursor,
.dark-mode .ai-cursor,
body.dark .ai-cursor,
body.night .ai-cursor,
.night .ai-cursor,
.night-mode .ai-cursor,
html.night .ai-cursor,
.theme-dark .ai-cursor {
    background-color: #7c89f1;
}

[data-night=&quot;night&quot;] .ai-footer,
.dark-mode .ai-footer,
body.dark .ai-footer,
body.night .ai-footer,
.night .ai-footer,
.night-mode .ai-footer,
html.night .ai-footer,
.theme-dark .ai-footer {
    color: rgba(200, 200, 200, 0.6) !important;
}

/* 手动添加的暗色模式类 */
.aisummary.ai-dark-theme {
    background: #2c2c2e;
    border-color: #38383a;
    color: #d1d1d1;
    box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.15);
}

.ai-dark-theme .ai-text-container {
    background: #333333;
    border-color: #4a4a4a;
    color: #c8c8c8;
}

.ai-dark-theme .ai-header {
    color: #7c89f1 !important;
}

.ai-dark-theme .ai-cursor {
    background-color: #7c89f1;
}

.ai-dark-theme .ai-footer {
    color: rgba(200, 200, 200, 0.6) !important;
}
&lt;/style&gt;

&lt;!-- AI摘要打字机效果脚本 --&gt;
&lt;script&gt;
    // 全局变量
    let aiSummaryTypingTimeoutId = null;
    let aiSummaryLastProcessedUrl = window.location.href;
    
    // AI摘要主题设置函数
    window.aiSummaryUser = {
        setAiSummaryTheme: function(theme) {
            const summaryElements = document.querySelectorAll(&#039;.aisummary&#039;);
            if (summaryElements.length &gt; 0) {
                summaryElements.forEach(element =&gt; {
                    if (theme === &#039;dark&#039;) {
                        element.classList.add(&#039;ai-dark-theme&#039;);
                    } else {
                        element.classList.remove(&#039;ai-dark-theme&#039;);
                    }
                });
            }
        }
    };

    // 检测并同步Handsome主题的夜间模式
    function detectAndSyncTheme() {
        // 检查常见的夜间模式标识
        const isDarkMode = 
            document.body.classList.contains(&#039;night&#039;) || 
            document.body.classList.contains(&#039;dark&#039;) || 
            document.documentElement.classList.contains(&#039;night&#039;) ||
            document.documentElement.getAttribute(&#039;data-night&#039;) === &#039;night&#039; ||
            document.querySelector(&#039;html&#039;).classList.contains(&#039;dark-mode&#039;) ||
            document.querySelector(&#039;[data-theme=&quot;dark&quot;]&#039;) !== null;
            
        // 应用对应的主题
        if (isDarkMode) {
            window.aiSummaryUser.setAiSummaryTheme(&#039;dark&#039;);
        } else {
            window.aiSummaryUser.setAiSummaryTheme(&#039;light&#039;);
        }
    }

    // 打字机效果核心逻辑
    function executeAiSummaryTyping() {
        // 获取所有AI摘要容器
        const aiSummaryContainers = document.querySelectorAll(&#039;.aisummary&#039;);

        // 遍历每个AI摘要容器
        aiSummaryContainers.forEach(container =&gt; {
            // 获取当前容器内的打字机文本元素和隐藏的源文本元素
            const typewriterElement = container.querySelector(&#039;.ai-typewriter-text&#039;);
            const sourceTextElement = container.querySelector(&#039;.ai-hidden-text&#039;);
            const typingSpeed = 50; // 打字速度，数值越小越快

            // 如果没有找到对应的元素，则跳过当前容器
            if (!typewriterElement || !sourceTextElement) return;

            // 清除可能存在的旧定时器，防止重复执行
            if (typewriterElement.typingTimeoutId) {
                clearTimeout(typewriterElement.typingTimeoutId);
            }

            // 获取并处理源文本，添加首行缩进
            let textToType = sourceTextElement.textContent.trim();
            if (textToType.length &gt; 0) {
                textToType = &#039;  &#039; + textToType; // 首行缩进
            }

            // 初始化打字机文本内容和字符索引
            typewriterElement.textContent = &#039;&#039;;
            let charIndex = 0;

            // 定义打字机效果函数
            const typeNextCharacter = () =&gt; {
                // 如果还有字符未输出
                if (charIndex &lt; textToType.length) {
                    // 逐个添加字符到打字机文本元素
                    typewriterElement.textContent += textToType.charAt(charIndex);
                    charIndex++;
                    // 设置定时器，继续输出下一个字符
                    typewriterElement.typingTimeoutId = setTimeout(typeNextCharacter, typingSpeed);
                } else {
                    // 所有字符输出完毕，清除定时器ID
                    typewriterElement.typingTimeoutId = null;
                }
            };
            // 启动打字机效果
            typeNextCharacter();
        });
    }

    // 页面加载后执行
    document.addEventListener(&#039;DOMContentLoaded&#039;, function() {
        // 延迟执行，确保页面元素加载完毕
        setTimeout(() =&gt; {
            executeAiSummaryTyping(); // 执行打字机效果
            detectAndSyncTheme();     // 检测并同步主题
        }, 300);
    });

    // PJAX/SPA兼容处理
    setInterval(function() {
        if (window.location.href !== aiSummaryLastProcessedUrl) {
            aiSummaryLastProcessedUrl = window.location.href;
            // 延迟执行，确保新内容加载完毕
            setTimeout(() =&gt; {
                executeAiSummaryTyping(); // 重新执行打字机效果
                detectAndSyncTheme();     // 重新检测并同步主题
            }, 1000);
        }
    }, 100);
    
    // 监听主题切换
    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.attributeName === &#039;class&#039; || mutation.attributeName === &#039;data-night&#039;) {
                detectAndSyncTheme();
            }
        });
    });
    
    // 开始观察文档和body元素上的class变化
    document.addEventListener(&#039;DOMContentLoaded&#039;, function() {
        observer.observe(document.documentElement, { attributes: true });
        observer.observe(document.body, { attributes: true });
    });
    
    // 兼容Handsome主题的夜间模式切换事件
    document.addEventListener(&#039;DOMContentLoaded&#039;, function() {
        // 尝试找到夜间模式切换按钮并监听点击事件
        const nightModeButtons = document.querySelectorAll(&#039;[data-toggle-theme], .theme-toggle, #nightmode, .night-mode-btn&#039;);
        if (nightModeButtons.length &gt; 0) {
            nightModeButtons.forEach(button =&gt; {
                button.addEventListener(&#039;click&#039;, function() {
                    // 延迟检测，确保主题切换完成
                    setTimeout(detectAndSyncTheme, 100);
                });
            });
        }
    });

    // 如果存在全局主题切换函数，拦截它们以同步状态
    if (typeof window.switchNightMode === &#039;function&#039;) {
        const originalSwitchNightMode = window.switchNightMode;
        window.switchNightMode = function() {
            originalSwitchNightMode.apply(this, arguments);
            setTimeout(detectAndSyncTheme, 100);
        };
    }
&lt;/script&gt;</code></pre>
]]></content:encoded>
<slash:comments>1</slash:comments>
<comments>https://blog.zhifouli.top/index.php/archives/22/#comments</comments>
<wfw:commentRss>https://blog.zhifouli.top/index.php/feed/tag/AISummary/</wfw:commentRss>
</item>
</channel>
</rss>