Memos 是一款轻量级的开源笔记应用,不仅部署方便,还支持多端 APP 发布,非常适合用来当作个人的‘朋友圈’记录日常、发发牢骚。最近刚重新搭建了 Typecho 博客,便萌生了将 Memos 嵌入到博客页面中的想法。起初在网上找到了一些现成的插件,但由于年久失修无法正常运行。最后,我决定借助 AI 自己动手实现这个功能。目前我的博客使用的是 Joe 主题,完美适配了嵌入效果。具体效果大家可以点击博客导航栏的【说说】跳转预览
Memos嵌入Typecho具体代码如下:
在主题文件夹新建memos.php,复制下面代码粘贴:

<?php
/**
 * memos 朋友圈模板 - 优化版
 * 适配:Typecho + Joe主题
 * 功能:真正分页加载、图片灯箱、兼容性处理
 **/
?>
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) {
    http_response_code(404);
    exit;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <?php $this->need('module/head.php'); ?>
    <?php if (!empty($this->options->JPrismTheme)) : ?>
        <link rel="stylesheet" href="<?= \Joe\theme_url('assets/plugin/prism/themes/' . $this->options->JPrismTheme) ?>">
    <?php else : ?>
        <link rel="stylesheet" href="<?= \joe\cdn('prism/1.23.0/themes/prism.min.css') ?>">
    <?php endif; ?>
    <script src="<?= joe\cdn('clipboard.js/2.0.6/clipboard.min.js') ?>"></script>
    <script src="<?= joe\theme_url('assets/plugin/prism/prism.min.js') ?>"></script>
    <script src="<?= joe\theme_url('assets/js/joe.post_page.js'); ?>"></script>
    <style>
        .memo-container { margin: 0 auto; background: #fff; min-height: 200px; }
        .memo-item { padding: 20px; border-bottom: 1px solid #f2f2f2; animation: fadeIn 0.4s ease-out; }
        @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
        
        .memo-content { font-size: 15px; line-height: 1.6; color: #333; white-space: pre-wrap; word-break: break-all; }
        .memo-date { font-size: 12px; color: #bbb; margin-top: 12px; }
        
        /* 图片网格布局 */
        .memo-res-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 10px; }
        .memo-res-grid img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 4px; border: 1px solid #eee; cursor: pointer; transition: opacity 0.2s; }
        .memo-res-grid img:hover { opacity: 0.8; }
        
        /* 只有单张图片时的大图显示 */
        .memo-res-grid:has(img:only-child) { display: block; }
        .memo-res-grid:has(img:only-child) img { width: auto; max-width: 80%; max-height: 400px; aspect-ratio: auto; }
        
        /* 加载按钮 */
        .load-more-wrapper { padding: 40px 20px; text-align: center; }
        #loadMoreBtn { 
            background: #f8f8f8; border: 1px solid #ddd; color: #576b95; 
            padding: 10px 40px; border-radius: 20px; font-size: 14px;
            cursor: pointer; transition: all 0.2s; outline: none;
        }
        #loadMoreBtn:disabled { color: #ccc; cursor: not-allowed; }
        #loadMoreBtn:not(:disabled):hover { background: #f0f0f0; border-color: #ccc; }
        
        /* 灯箱 */
        #lightbox { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: 0.3s; cursor: zoom-out; }
        #lightbox.active { opacity: 1; visibility: visible; }
        #lightbox img { max-width: 95%; max-height: 95%; transform: scale(0.9); transition: 0.3s; }
        #lightbox.active img { transform: scale(1); }
    </style>
</head>

<body>
    <div id="Joe">
        <?php $this->need('module/header.php'); ?>
        <div class="joe_container">
            <div class="joe_main">
                <div class="joe_detail" data-cid="<?php echo $this->cid ?>">
                    
                    <div class="memo-container" id="memoList"></div>

                    <div class="load-more-wrapper">
                        <div id="initLoading" style="color: #999; font-size: 14px; margin-bottom: 10px;">正在加载动态...</div>
                        <button id="loadMoreBtn" style="display: none;">加载更多</button>
                    </div>

                    <div id="lightbox"><img src=""></div>

                    <script>
                        const CONFIG = {
                            apiUrl: 'https://你的memos地址/api/v1/memos', 
                            token: 'memos后台token',
                            step: 10 // 每次获取 10 条
                        };

                        let nextPageToken = '';
                        let isFinished = false;

                        const memoList = document.getElementById('memoList');
                        const loadMoreBtn = document.getElementById('loadMoreBtn');
                        const initLoading = document.getElementById('initLoading');
                        const lightbox = document.getElementById('lightbox');
                        const lightboxImg = lightbox.querySelector('img');

                        // 获取并渲染数据
                        async function fetchMemos() {
                            if (isFinished) return;

                            loadMoreBtn.disabled = true;
                            loadMoreBtn.innerText = "加载中...";

                            let url = `${CONFIG.apiUrl}?pageSize=${CONFIG.step}`;
                            if (nextPageToken) url += `&pageToken=${nextPageToken}`;

                            try {
                                const response = await fetch(url, {
                                    headers: { 'Authorization': `Bearer ${CONFIG.token}` }
                                });
                                const data = await response.json();
                                
                                const memos = data.memos || (Array.isArray(data) ? data : []);
                                nextPageToken = data.nextPageToken || '';

                                if (memos.length > 0) {
                                    renderMemos(memos);
                                }

                                initLoading.style.display = 'none';
                                loadMoreBtn.style.display = 'inline-block';
                                loadMoreBtn.disabled = false;
                                loadMoreBtn.innerText = "加载更多";

                                if (!nextPageToken || memos.length < CONFIG.step) {
                                    isFinished = true;
                                    loadMoreBtn.innerText = "没有更多内容了";
                                    loadMoreBtn.disabled = true;
                                    loadMoreBtn.style.opacity = '0.6';
                                }
                            } catch (error) {
                                console.error('Fetch error:', error);
                                initLoading.innerText = "加载失败,请检查网络或配置";
                            }
                        }

                        function renderMemos(memos) {
                            let html = '';
                            memos.forEach(item => {
                                // 兼容处理时间
                                const rawTime = item.createTime || item.createdTs;
                                const date = new Date(rawTime).toLocaleString('zh-CN', { hour12: false });
                                
                                // 资源/图片处理
                                let imgs = '';
                                const res = item.resources || item.attachments || [];
                                res.forEach(r => {
                                    const u = r.externalLink || (r.name ? `${CONFIG.apiUrl.replace('/api/v1/memos','')}/file/${r.name}` : '');
                                    if(u) imgs += `<img src="${u}" loading="lazy">`;
                                });
                                
                                html += `
                                    <div class="memo-item">
                                        <div class="memo-content">${escapeHtml(item.content)}</div>
                                        ${imgs ? `<div class="memo-res-grid">${imgs}</div>` : ''}
                                        <div class="memo-date">${date}</div>
                                    </div>`;
                            });
                            memoList.insertAdjacentHTML('beforeend', html);
                        }

                        function escapeHtml(t) { 
                            const d = document.createElement('div'); 
                            d.textContent = t; 
                            return d.innerHTML.replace(/\n/g, '<br>'); // 保留换行
                        }

                        // 事件绑定
                        loadMoreBtn.addEventListener('click', fetchMemos);

                        memoList.addEventListener('click', (e) => {
                            if (e.target.tagName === 'IMG') {
                                lightboxImg.src = e.target.src;
                                lightbox.classList.add('active');
                                document.body.style.overflow = 'hidden';
                            }
                        });

                        lightbox.addEventListener('click', () => {
                            lightbox.classList.remove('active');
                            document.body.style.overflow = '';
                        });

                        // 初始加载
                        fetchMemos();
                    </script>
                </div>
                <?php $this->need('module/comment.php'); ?>
            </div>
            <?php $this->need('module/aside.php'); ?>
        </div>
        <?php $this->need('module/footer.php'); ?>
    </div>
</body>
</html>

最后新建一个页面,模板选择memos就可以。

最后修改:2026 年 03 月 18 日
如果觉得我的文章对你有用,请随意赞赏