配置说说页面

发布于 2025/08/10 建站手札 总字数:1.6k 阅读时长:5 分钟
文章导读 摘要由 Qwen 生成

这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方

ARTICLE SERIES

博客定制手记

第 1 / 14 篇
  1. 01 配置说说页面
  2. 02 在侧边栏中添加欢迎信息
  3. 03 自定义右键菜单
  4. 04 利用插件自定义页脚菜单
  5. 05 博客添加AI总结
  6. 06 利用 SiteMap 随机访问站内页面
  7. 07 自定义导航栏
  8. 08 自定义分类条
  9. 09 自定义页脚(新)
  10. 10 使用GitHub推送Hexo到服务器
  11. 11 在侧边栏添加日历和倒计时
  12. 12 自定义字体
  13. 13 添加typesense搜索
  14. 14 添加网站左上角菜单

注意:本文为早期 Hexo / Butterfly 魔改记录,本站现已迁移到 Astro,相关代码、路径或插件可能已经失效,仅作历史参考。

又开始折腾啦,这次把说说页面加上,改用moments项目的api Moments 极简朋友圈 githun · github.com

前期要求

硬件要求

  1. 一台服务器
  2. 一个可自主解析的域名

软件要求

  1. docker环境
  2. 反向代理工具(本文以Nginx为例)

介绍与展示

这里先给大家展示一下最终的效果,注意该教程可能仅适合部分主题,如果出现主题不适配的情况请自行适配,这里以本站主题 Hexo-theme-butterfly为基础进行修改:

  1. 说说页面: 我的说说 胡言乱语ing。。。 · blog.biss.click
  2. 轻量朋友圈 朋友圈 依旧胡言乱语 · mm.biss.blog
  3. 功能说明

Moments作为一个轻量朋友圈,其功能都是分享上的部分,如下所示:

  • 分享:链接,图片,音乐,视频,书籍,电影
  • 信息:自定义位置,自定义标签,是否公开
  • 页面:Markdown渲染,编辑说说,删除说说,暗夜模式,自定义图标,信息,CSS及JS代码
  • 功能:S3存储,文件查询,多用户注册,点赞,评论,API

简单介绍完毕,下面我就来教大家如何进行部署!

部署教程

Moments部署

Compose部署

官方给予了很完善的教程,这里我仅仅简单介绍一下 docker-compose部署的方式,如果你想以源码等其他方式进行部署,请查看文章开头部分的 github地址进行查阅。

首先,在服务器任意位置创建文件:docker-compose.yaml,填入以下内容:

version: '3'
services:
  moments:
    image: kingwrcy/moments:latest
    container_name: moments
    restart: always
    environment:
      port: 3000
      JWT_KEY: "自己随便生成点字符串"
      ENABLE_SWAGGER: "true"
      CORS_ORIGIN: # 填写跨域域名
    ports:
      - "3000:3000"  # 自行换端口,换前面的,后面的3000不要动
    volumes:
      - ./opt/data:/app/data
    #   - ./data/localtime:/etc/localtime:ro
    #   - ./data/timezone:/etc/timezone:ro

注意文件,我将 /opt/文件夹(当然可以改成其他的)下的 /data文件夹挂载了进去,数据都会在里面,迁移时仅需整体打包到新服务器即可。然后执行以下两条命令,后续需要升级也仅需要执行这两个命令:

docker-compose pull
docker-compose up -d

如果网络环境不佳,可尝试替换 docker源,可以自行查找

通过反向代理将其添加到某个域名中,这里就不再多说了,各大面板都有极其完备的反代文档。

Nginx修改

Moments在跨域 docker-compose文件中可以配置,所以不必进行此步

网站目录

返回到上一级目录,也就是域名名称的文件夹下,找到 Proxy文件夹,编辑里面的 root.conf文件为如下内容:

#代理配置

location / {
    # 跨域设置
    add_header Access-Control-Allow-Origin *;  # 允许所有域名访问,你也可以指定具体域名
    add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';  # 允许的 HTTP 方法
    add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';  # 允许的请求头

    # 处理 OPTIONS 请求,预检请求
    if ($request_method = 'OPTIONS') {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
        add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
        add_header Access-Control-Max-Age 1728000;
        add_header Content-Type 'text/plain charset=UTF-8';
        add_header Content-Length 0;
        return 204;
    }

    # 原代理设置
    proxy_pass http://127.0.0.1:3003;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    add_header X-Cache $upstream_cache_status;
    add_header Cache-Control no-cache;
    proxy_ssl_server_name off;
    proxy_ssl_name $proxy_host;
    add_header Strict-Transport-Security "max-age=31536000";
}

下面是原代理设置,可以仅仅复制上面部分内容,下面保持不变,注意端口不要出问题。

前端实现

由于该项目利用了MetingJS和APlayer,所以请提前引入这两个包,Hexo-theme-butterfly中虽然有内置的两个包,仅需修改配置文件即可开启,但是版本比较老,这里我建议自行引入最新版本,在配置中引入以下文件,注意css和js应该是分开引入的:

<link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="all" onload="this.media="all"">
<script src="https://fastly.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/meting/dist/Meting.min.js"></script>

新建页面shuoshuo,在文件内写入一下内容:

---
title: 日常哔哔,键盘侠的日常吐槽
aside: false
---
<div id="talk"></div>
<div class="limit">- 只展示最近30条说说 -</div>
<script src="/js/shuoshuo.js" no-pjax></script>

其中的JS文件地址清自行修改,放在主题目录 /script/目录,自行创建,并写入以下内容:

function renderTalks() {
    const talkContainer = document.querySelector('#talk');
    const domain = 'https://mm.biss.click'; 
    if (!talkContainer) return;
    talkContainer.innerHTML = '';
    const generateIconSVG = () => {
        return `<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z"fill="#1da1f2"></path></svg>`;
    }
    const waterfall = (a) => {
        function b(a, b) {
            var c = window.getComputedStyle(b);
            return parseFloat(c["margin" + a]) || 0
        }

        function c(a) {
            return a + "px"
        }

        function d(a) {
            return parseFloat(a.style.top)
        }

        function e(a) {
            return parseFloat(a.style.left)
        }

        function f(a) {
            return a.clientWidth
        }

        function g(a) {
            return a.clientHeight
        }

        function h(a) {
            return d(a) + g(a) + b("Bottom", a)
        }

        function i(a) {
            return e(a) + f(a) + b("Right", a)
        }

        function j(a) {
            a = a.sort(function (a, b) {
                return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
            })
        }

        function k(b) {
            f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
        }
        "string" == typeof a && (a = document.querySelector(a));
        var l = [].map.call(a.children, function (a) {
            return a.style.position = "absolute", a
        });
        a.style.position = "relative";
        var m = [];
        l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
        for (var n = 1; n < l.length; n++) {
            var o = l[n - 1],
                p = l[n],
                q = i(o) + f(p) <= f(a);
            if (!q) break;
            p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
        }
        for (; n < l.length; n++) {
            j(m);
            var p = l[n],
                r = m.pop();
            p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
        }
        j(m);
        var s = m[0];
        a.style.height = c(h(s) + b("Bottom", s));
        var t = f(a);
        window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
    };

    const fetchAndRenderTalks = () => {
        const url = 'https://mm.biss.click/api/memo/list';
        const cacheKey = 'talksCache';
        const cacheTimeKey = 'talksCacheTime';
        const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
  
        const cachedData = localStorage.getItem(cacheKey);
        const cachedTime = localStorage.getItem(cacheTimeKey);
        const currentTime = new Date().getTime();
  
        // 判断缓存是否有效
        if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
            const data = JSON.parse(cachedData);
            renderTalks(data); // 使用缓存渲染数据
        } else {
            if (talkContainer) {
                talkContainer.innerHTML = '';
                fetch(url, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                        },
                        body: JSON.stringify({
                            size: 30
                        })
                    })
                    .then(res => res.json())
                    .then(data => {
                        if (data.code === 0 && data.data && Array.isArray(data.data.list)) {
                            // 缓存数据
                            localStorage.setItem(cacheKey, JSON.stringify(data.data.list));
                            localStorage.setItem(cacheTimeKey, currentTime.toString());
                            renderTalks(data.data.list); // 渲染数据
                        }
                    })
                    .catch(error => {
                        console.error('Error fetching data:', error);
                    });
            }
        }
  
        // 渲染函数
        function renderTalks(list) {
            // 确保 data 是一个数组
            if (Array.isArray(list)) {
                let items = list.map(item => formatTalk(item, url));
                items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
                waterfall('#talk');
            } else {
                console.error('Data is not an array:', list);
            }
        }
    };
  

    const formatTalk = (item, url) => {
        let date = formatTime(new Date(item.createdAt).toString());
        let content = item.content;
        let imgs = item.imgs ? item.imgs.split(',') : [];
        let text = content;
        content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2">@$1</a>`)
            .replace(/- \[ \]/g, '⚪')
            .replace(/- \[x\]/g, '⚫');
        // 保留换行符,转换 \n 为 <br>
		content = content.replace(/\n/g, '<br>');
        // 将content用一个类包裹,便于后续处理
        content = `<div class="talk_content_text">${content}</div>`;
        if (imgs.length > 0) {
            const imgDiv = document.createElement('div');
            imgDiv.className = 'zone_imgbox';
            imgs.forEach(e => {
                const imgLink = document.createElement('a');
                const imgUrl = domain + e;
                imgLink.href = imgUrl;
                imgLink.setAttribute('data-fancybox', 'gallery');
                imgLink.className = 'fancybox';
                imgLink.setAttribute('data-thumb', e);
                const imgTag = document.createElement('img');
                imgTag.src = domain + e;
                imgLink.appendChild(imgTag);
                imgDiv.appendChild(imgLink);
            });
            content += imgDiv.outerHTML;
        }

        // 外链分享功能
        if (item.externalUrl) {
            const externalUrl = item.externalUrl;
            const externalTitle = item.externalTitle;
            const externalFavicon = item.externalFavicon;

            const externalContainer = `
            <div class="shuoshuo-external-link">
                <a class="external-link" href="${externalUrl}" target="_blank" rel="external nofollow noopener noreferrer">
                    <div class="external-link-left" style="background-image: url(${externalFavicon})"></div>
                    <div class="external-link-right">
                        <div class="external-link-title">${externalTitle}</div>
                        <div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
                    </div>
                </a>
            </div>`;

            content += externalContainer;
        }

        const ext = JSON.parse(item.ext || '{}');

        if (ext.music && ext.music.id) {
            const music = ext.music;
            const musicUrl = music.api.replace(':server', music.server)
                .replace(':type', music.type)
                .replace(':id', music.id);
            content += `
            <meting-js server="${music.server}" type="${music.type}" id="${music.id}" api="${music.api}"></meting-js>
        `;
        }

        if (ext.doubanMovie && ext.doubanMovie.id) {
            const doubanMovie = ext.doubanMovie;
            const doubanMovieUrl = doubanMovie.url;
            const doubanTitle = doubanMovie.title;
            // const doubanDesc = doubanMovie.desc || '暂无描述';
            const doubanImage = doubanMovie.image;
            const doubanDirector = doubanMovie.director || '未知导演';
            const doubanRating = doubanMovie.rating || '暂无评分';
            // const doubanReleaseDate = doubanMovie.releaseDate || '未知上映时间';
            // const doubanActors = doubanMovie.actors || '未知演员';
            const doubanRuntime = doubanMovie.runtime || '未知时长';

            content += `
                <a class="douban-card" href="${doubanMovieUrl}" target="_blank">
                    <div class="douban-card-bgimg" style="background-image: url('${doubanImage}');"></div>
                    <div class="douban-card-left">
                        <div class="douban-card-img" style="background-image: url('${doubanImage}');"></div>
                    </div>
                    <div class="douban-card-right">
                        <div class="douban-card-item"><span>电影名: </span><strong>${doubanTitle}</strong></div>
                        <div class="douban-card-item"><span>导演: </span><span>${doubanDirector}</span></div>
                        <div class="douban-card-item"><span>评分: </span><span>${doubanRating}</span></div>
                        <div class="douban-card-item"><span>时长: </span><span>${doubanRuntime}</span></div>
                    </div>
                </a>
            `;
        }

        if (ext.doubanBook && ext.doubanBook.id) {
            const doubanBook = ext.doubanBook;
            const bookUrl = doubanBook.url;
            const bookTitle = doubanBook.title;
            // const bookDesc = doubanBook.desc;
            const bookImage = doubanBook.image;
            const bookAuthor = doubanBook.author;
            const bookRating = doubanBook.rating;
            const bookPubDate = doubanBook.pubDate;

            const bookTemplate = `
                <a class="douban-card" href="${bookUrl}" target="_blank">
                    <div class="douban-card-bgimg" style="background-image: url('${bookImage}');"></div>
                        <div class="douban-card-left">
                            <div class="douban-card-img" style="background-image: url('${bookImage}');"></div>
                        </div>
                        <div class="douban-card-right">
                            <div class="douban-card-item">
                                <span>书名: </span><strong>${bookTitle}</strong>
                            </div>
                            <div class="douban-card-item">
                                <span>作者: </span><span>${bookAuthor}</span>
                            </div>
                            <div class="douban-card-item">
                                <span>出版年份: </span><span>${bookPubDate}</span>
                            </div>
                            <div class="douban-card-item">
                                <span>评分: </span><span>${bookRating}</span>
                            </div>
                        </div>
                </a>
            `;

            content += bookTemplate;
        }

        if (ext.video && ext.video.type) {
            const videoType = ext.video.type;
            const videoUrl = ext.video.value;
            if (videoType === 'bilibili') {
                // Bilibili 视频模板
                // 从形如https://www.bilibili.com/video/BV1VGAPeAEMQ/?vd_source=91b3158d27d98ff41f842508c3794a13 的链接中提取视频 BV1VGAPeAEMQ
                const biliTemplate = `
                <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
                    <iframe 
                        style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;" 
                        src="${videoUrl}&autoplay=0"
                        scrolling="no" 
                        frameborder="no" 
                        allowfullscreen>
                    </iframe>
                </div>
            `;
                // 将模板插入到 DOM 中
                content += biliTemplate;

            } else if (videoType === 'youtube') {
                // YouTube 视频模板
                // 从形如https://youtu.be/2V6lvCUPT8I?si=DVhUas6l6qlAr6Ru的链接中提取视频 ID2V6lvCUPT8I
                const youtubeTemplate = `
                <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
                    <iframe width="100%"
                        style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
                        src="${videoUrl}"
                        title="YouTube video player" 
                        frameborder="0" 
                        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 
                        referrerpolicy="strict-origin-when-cross-origin" 
                        allowfullscreen>
                    </iframe>
                </div>
            `;
                // 将模板插入到 DOM 中
                content += youtubeTemplate;
            }
        }

        return {
            content: content,
            user: item.user.nickname || '匿名',
            avatar: item.user.avatarUrl || 'https://p.liiiu.cn/i/2024/03/29/66061417537af.png',
            date: date,
            location: item.location || '山西',
            tags: item.tags ? item.tags.split(',').filter(tag => tag.trim() !== '') : ['无标签'],
            text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]' + `${imgs.length ? '[图片]' : ''}`)
        };
    };

    const generateTalkElement = (item) => {
        const talkItem = document.createElement('div');
        talkItem.className = 'talk_item';

        const talkMeta = document.createElement('div');
        talkMeta.className = 'talk_meta';

        const avatar = document.createElement('img');
        avatar.className = 'no-lightbox avatar';
        avatar.src = item.avatar;

        const info = document.createElement('div');
        info.className = 'info';

        const talkNick = document.createElement('span');
        talkNick.className = 'talk_nick';
        talkNick.innerHTML = `${item.user} ${generateIconSVG()}`;

        const talkDate = document.createElement('span');
        talkDate.className = 'talk_date';
        talkDate.textContent = item.date;

        const talkContent = document.createElement('div');
        talkContent.className = 'talk_content';
        talkContent.innerHTML = item.content;

        const talkBottom = document.createElement('div');
        talkBottom.className = 'talk_bottom';

        const TagContainer = document.createElement('div');

        const talkTag = document.createElement('span');
        talkTag.className = 'talk_tag';
        talkTag.textContent = `🏷️${item.tags}`;

        const locationTag = document.createElement('span');
        locationTag.className = 'location_tag';
        locationTag.textContent = `🌍${item.location}`;

        TagContainer.appendChild(talkTag);
        TagContainer.appendChild(locationTag);

        const commentLink = document.createElement('a');
        commentLink.href = 'javascript:;';
        commentLink.onclick = () => goComment(item.text);
        const commentIcon = document.createElement('span');
        commentIcon.className = 'icon';
        const commentIconInner = document.createElement('i');
        commentIconInner.className = 'fa-solid fa-message fa-fw';
        commentIcon.appendChild(commentIconInner);
        commentLink.appendChild(commentIcon);

        talkMeta.appendChild(avatar);
        info.appendChild(talkNick);
        info.appendChild(talkDate);
        talkMeta.appendChild(info);
        talkItem.appendChild(talkMeta);
        talkItem.appendChild(talkContent);
        talkBottom.appendChild(TagContainer);
        talkBottom.appendChild(commentLink);
        talkItem.appendChild(talkBottom);

        return talkItem;
    };

    const goComment = (e) => {
        const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
        const textContent = match ? match[1] : "";
        const n = document.querySelector(".atk-textarea");
        n.value = `> ${textContent}\n\n`;
        n.focus();
        btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
        // const n = document.querySelector(".atk-textarea");
        // n.value = `> ${e}\n\n`;
        // n.focus();
        // btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
    };

    const formatTime = (time) => {
        const d = new Date(time);
        const ls = [
            d.getFullYear(),
            d.getMonth() + 1,
            d.getDate(),
            d.getHours(),
            d.getMinutes(),
            d.getSeconds(),
        ];
        const r = ls.map((a) => (a.toString().length === 1 ? '0' + a : a));
        return `${r[0]}-${r[1]}-${r[2]} ${r[3]}:${r[4]}`;
    };

    fetchAndRenderTalks();
}

renderTalks();

// function whenDOMReady() {
//     const talkContainer = document.querySelector('#talk');
//     talkContainer.innerHTML = '';
//     fetchAndRenderTalks();
// }
// whenDOMReady();
// document.addEventListener("pjax:complete", whenDOMReady);

自行修改js文件中的Moments地址为你的地址,在文件中,有一个gocomment函数,实现的是获取卡片中的文本内容,如果如果出现不匹配的情况,请自行修改一下类名,这里我匹配的是artalk的输入框。在这里我把代码做了修改,因为我不想使用s3,结果导致说说图片无法加载。。。 修改历程:日后再写

然后引入样式文件,这个文件可以在配置文件中引用,也可以在页面文件中类似于shuoshuo.js一样引用,样式内容如下:

:root {
    --liushen-card-bg: #fff;
    --liushen-card-border: 1px solid #e3e8f7;
    --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
    --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
    --liushen-card-secondbg: #f1f3f8;
    --liushen-button-hover-bg: #2679cc;
    --liushen-text: #4c4948;
    --liushen-button-bg: #f1f3f8;
    --liushen-fancybox-bg: rgba(255,255,255,0.5);
}

:root, [data-theme=dark] {
    --liushen-card-bg: #181818;
    --liushen-card-secondbg: #30343f;
    --liushen-card-border: 1px solid #42444a;
    --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
    --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
    --liushen-button-bg: #30343f;
    --liushen-button-hover-bg: #2679cc;
    --liushen-text: rgba(255,255,255,0.702);
    --liushen-fancybox-bg: rgba(0,0,0,0.5);
}

/* 卡片初始化 */
#talk .talk_item {
    width: calc(33.333% - 6px);
    background: var(--liushen-card-bg);
    border: var(--liushen-card-border);
    box-shadow: var(--card-box-shadow);
    transition: box-shadow .3s ease-in-out;
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    padding: 20px;
    margin-bottom: 9px;
    margin-right: 9px;
}
#talk .talk_item:hover {
    box-shadow: var(--card-hover-box-shadow);
}

@media (max-width: 900px) {
    #talk .talk_item {
      width: calc(50% - 5px);
    }
}
@media (max-width: 450px) {
    #talk .talk_item {
      width: calc(100%);
    }
}

#talk{
    position: relative;
    width: 100%;
    box-sizing: border-box;
}

#talk .talk_meta .avatar {
    margin: 0 !important;
    width: 60px;
    height: 60px;
    border-radius: 12px;
}
#talk .talk_bottom,
#talk .talk_meta {
    display: flex;
    align-items: center;
}
#talk .talk_meta {
    display: flex;
    align-items: center;
    width: 100%;
    padding-bottom: 10px;
    border-bottom: 1px dashed grey; /* 添加灰色虚线边框 */
}
#talk .talk_bottom {
    margin-top: 15px;
    padding-top: 10px;
    border-top: 1px dashed grey; /* 添加灰色虚线边框 */
    justify-content: space-between;
}
#talk .talk_meta .info {
    display: flex;
    flex-direction: column;
    margin-left: 10px;
}
#talk .talk_meta .info .talk_nick {
    color: #6dbdc3;
    font-size: 1.2rem;
}
#talk .talk_meta .info svg.is-badge.icon {
    width: 15px;
    padding-top: 3px;
}
#talk .talk_meta .info span.talk_date {
    opacity: .6;
}
#talk .talk_item .talk_content {
    margin-top: 10px;
}
#talk .talk_item .talk_content .zone_imgbox {
    display: flex;
    flex-wrap: wrap;
    --w: calc(25% - 8px);
    gap: 10px;
    margin-top: 10px;
}
#talk .talk_item .talk_content .zone_imgbox a {
    display: block;
    border-radius: 12px;
    width: var(--w);
    aspect-ratio: 1/1;
    position: relative;
}
#talk .talk_item .talk_content .zone_imgbox a:first-child {
    width: 100%;
    aspect-ratio: 1.8;
}
#talk .talk_item .talk_content .zone_imgbox img {
    border-radius: 10px;
    width: 100%;
    height: 100%;
    margin: 0 !important;
    object-fit: cover;
}
/* 底部 */
#talk .talk_item .talk_bottom {
    opacity: .9;
}
#talk .talk_item .talk_bottom .icon {
    float: right;
    transition: all .3s;
}
#talk .talk_item .talk_bottom .icon:hover {
    color: #49b1f5;
}
#talk .talk_item .talk_bottom span.talk_tag,
#talk .talk_item .talk_bottom span.location_tag {
    font-size: 14px;
    background-color: var(--liushen-card-secondbg);
    border-radius: 12px;
    padding: 3px 15px 3px 10px;
    transition: box-shadow 0.3s ease;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

#talk .talk_item .talk_bottom span.location_tag {
    margin-left: 5px;
}

#talk .talk_item .talk_bottom span.talk_tag:hover,
#talk .talk_item .talk_bottom span.location_tag:hover {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
#talk .talk_item .talk_content>a {
    margin: 0 3px;
    color: #ff7d73 !important;
}
#talk .talk_item .talk_content>a:hover{
    text-decoration: none !important;
    color: #ff5143 !important
}

@media screen and (max-width: 900px) {
    #talk .talk_item .talk_content .zone_imgbox {
        --w: calc(33% - 5px);
    }
    #talk .talk_item #post-comment{
        margin: 0 3px
    }
}
@media screen and (max-width: 768px) {
    .zone_imgbox {
        gap: 6px;
    }
    .zone_imgbox {
        --w: calc(50% - 3px);
    }
    span.talk_date {
        font-size: 14px;
    }
}

#talk .talk_item .talk_content .douban-card {
    margin-top: 10px !important;
    text-decoration: none;
    align-items: center;
    border-radius: 12px;
    color: #faebd7;
    display: flex;
    justify-content: center;
    margin: 10px;
    max-width: 400px;
    overflow: hidden;
    padding: 15px;
    position: relative;
}

.douban-card .douban-card-bgimg {
    background-position: 50%;
    background-repeat: no-repeat;
    background-size: 100%;
    filter: blur(15px) brightness(.6);
    height: 115%;
    position: absolute;
    width: 115%;
}

.douban-card .douban-card-left {
    align-items: center;
    display: flex;
    flex-direction: column;
    position: relative;
}

.douban-card .douban-card-left .douban-card-img {
    transition: all .5s ease;
    height: 130px;
    position: relative;
    width: 80px;
    background-position: 50%;
    background-repeat: no-repeat;
    background-size: 100%;
}

.douban-card .douban-card-left:hover .douban-card-img {
    filter: blur(5px) brightness(.6);
    transform: perspective(800px) rotateX(180deg);
}

.douban-card .douban-card-right {
    color: #faebd7;
    display: flex;
    flex-direction: column;
    font-size: 14px;
    line-height: 1.5;
    margin-left: 12px;
    position: relative;
}

.douban-card .douban-card-right .douban-card-item  {
    margin-top: 4px;
    max-width: 95%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 外链卡片 */
#talk .talk_item .talk_content .shuoshuo-external-link {
    /* 无下划线 */
    width: 100%;
    height: 80px;
    margin-top: 10px;
    border-radius: 12px;
    background-color: var(--liushen-card-secondbg);
    color: var(--liushen-card-text);
    border: var(--liushen-card-border);
    transition: background-color .3s ease-in-out;
}

.shuoshuo-external-link:hover {
    background-color: var(--liushen-button-hover-bg);
}

.shuoshuo-external-link .external-link {
    display: flex;
    color: var(--liushen-text) !important;
    width: 100%;
    height: 100%;
}

.shuoshuo-external-link .external-link:hover {
    color: white !important;
}

.shuoshuo-external-link .external-link:hover {
    text-decoration: none !important;
}

.shuoshuo-external-link .external-link-left {
    width: 60px;
    height: 60px;
    margin: 10px;
    border-radius: 12px;
    background-size: cover;
    background-position: center;
}

.shuoshuo-external-link .external-link-right {
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: calc(100% - 80px);
    padding: 10px;
}

.shuoshuo-external-link .external-link-right .external-link-title {
    font-size: 1.0rem;
    font-weight: 800;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.shuoshuo-external-link .external-link-right i {
    margin-left: 5px;
}

.limit {
    width: 100%;
    text-align: center;
    margin-top: 30px;
}

如果一切正常,应该就可以显示了,但是我在使用时发现卡片是黑色的,把前面的root内容删除之后就好了。

参考内容

liushen的博客 liushen · blog.liushen.fun

当然还有chatgpt的支持

配置说说页面

/posts/ad244066/
作者
biss
发布于
许可协议
CC BY-NC-SA 4.0
网站
订阅 打赏

Comments

配置说说页面

Ctrl + 右键:浏览器原生菜单