朋友圈配置记录与前端美化

笔记

📦 朋友圈配置记录与前端美化 📋 准备工作 服务器已安装 1Panel(我是用这个,按照您的实际情况来,这里只给出演示) 博客已配置好友链页面(link 页面),数据文件为 source/_data/link.yml 确保服务器能访问外网(用于抓取友链文章) 一、生成友链数据文件 friend.json Friend-Circle-Lite 需要一个 JSON 格式的友链列表。我们从你的 link.yml 生成。 1.1 安装依赖(如果未安装 yamljs) 在博客根目录执行: npm install yamljs --save-dev 1.2 创建生成脚本 link.js 在博客根目录创建 link.js,内容如下: const YML = require('yamljs'); const fs = require('fs'); // 黑名单:不想显示的友链名称(请根据实际情况修改) const blacklist = []; let friends = []; let data_f = YML.parse(fs.readFileSync('source/_data/link.yml').toString().replace(/(?<=rss:)\s*\n/g, ' ""\n')); // 默认取前两个分类(可根据你的 link.yml 结构调整) data_f.forEach((entry, index) => { // 限制分类数量,通常只取主要分类(如“友情链接”) if (index < 2) { const filtered = entry.link_list.filter(item => !blacklist.includes(item.name)); friends = friends.concat(filtered); } }); // 构建 Friend-Circle-Lite 需要的格式 const friendData = { friends: friends.map(item => [item.name, item.link, item.avatar]) }; // 写入到 source/ 下,生成后会被 hexo 复制到 public fs.writeFileSync('./source/friend.json', JSON.stringify(friendData, null, 2)); console.log('✅ friend.json 已生成'); 1.3 执行生成 node link.js 此时会在 source/ 目录下生成 friend.json。 1.4 将 friend.json 部署到可访问的 URL 你需要让这个文件能被 Friend-Circle-Lite 后端访问到。有两种方式: 方式A(推荐):直接放在你的博客网站目录下,例如 https://你的域名/friend.json(通过 Hexo 生成后自动存在于 public 根目录)。访问 https://你的域名/friend.json 应能看到 JSON 数据。 方式B:在 1Panel 管理的网站中创建一个 API 路径,手动放置文件。 我们采用方式A,因为最简单。执行 hexo g 后,friend.json 会被复制到 public 目录。 二、在服务器部署 Friend-Circle-Lite 后端 2.1 通过 SSH 登录服务器 ssh root@你的服务器IP 2.2 克隆项目 git clone https://github.com/willow-god/Friend-Circle-Lite.git cd Friend-Circle-Lite 2.3 安装 Python 依赖 pip install -r requirements.txt pip install -r server/requirements-server.txt 如果系统没有 pip,先安装 python3-pip。 2.4 修改配置文件 conf.yaml vim conf.yaml 修改关键部分: spider_settings: enable: true json_url: "https://你的域名/friend.json" # 换成你的实际 URL article_count: 5 # 每个博客获取最新几篇文章 merge_result: enable: false merge_json_url: "" email_push: enable: false rss_subscribe: enable: false 保存退出。 2.5 测试服务 运行后端服务: python3 server.py 默认监听 0.0.0.0:1223。访问 http://你的服务器IP:1223/all,如果返回 JSON 数据(可能为空),说明服务启动正常。按 Ctrl+C 停止。 2.6 配置为系统服务(可选但推荐) 创建 systemd 服务文件 /etc/systemd/system/fcircle.service: [Unit] Description=Friend Circle Lite Service After=network.target [Service] Type=simple User=root WorkingDirectory=/root/Friend-Circle-Lite ExecStart=/usr/bin/python3 /root/Friend-Circle-Lite/server.py Restart=always [Install] WantedBy=multi-user.target 启动并设置开机自启: systemctl daemon-reload systemctl start fcircle systemctl enable fcircle 2.7 验证服务 curl http://127.0.0.1:1223/all 应返回 JSON。 三、在 1Panel 中配置反向代理 为了通过域名访问后端接口(例如 https://friends.你的域名.com),并避免跨域问题。 3.1 添加反向代理 登录 1Panel,进入「网站」→「创建反向代理」。 填写: 域名:friends.你的域名.com(需提前解析到服务器) 目标地址:http://127.0.0.1:1223 其他默认即可。 提交后,等待 SSL 证书申请(可选)。 3.2 测试代理 访问 https://friends.你的域名.com/all,应该返回和后端一样的 JSON 数据。 3.3 我遇到的问题 我部署的时候python安装异常,所以我用的是虚拟环境,过程如下: 🐍 解决 Python 包安装错误:externally-managed-environment 问题原因 较新的 Linux 发行版(如 Ubuntu 22.04+、Debian 12+)默认启用了 Python 的 EXTERNALLY-MANAGED 机制,防止用 pip 直接安装包到系统环境中,以免破坏系统依赖。执行 pip install 时会报该错误。 解决方案 使用虚拟环境 虚拟环境能隔离依赖,不影响系统,且便于管理。 安装 python3-venv(如果尚未安装) apt update && apt install python3-venv -y 进入 Friend-Circle-Lite 目录,创建虚拟环境 cd ~/Friend-Circle-Lite python3 -m venv venv 激活虚拟环境 source venv/bin/activate 激活后,命令行提示符会显示 (venv)。 安装依赖(在虚拟环境中) pip install -r requirements.txt pip install -r server/requirements-server.txt 此时安装的包都在虚拟环境中,不会影响系统。 运行服务 在虚拟环境中执行: python server.py 如果成功运行,那么你就可以进行下一步了。 定时任务 进入计划任务,点击「添加任务」。 任务类型:Shell脚本。 任务名称:如 friend-circle-crawl。 执行周期:按需设置(例如每6小时)。 脚本内容:填入你的命令 cd /root/Friend-Circle-Lite /root/Friend-Circle-Lite/venv/bin/python run.py 保存。 那么一个每天定时抓取的朋友圈后端就完成了 🎨 美化版朋友圈前端代码 统计信息置顶:将订阅数(友链总数)、成功数(文章总数)、总数(总抓取量)移到页面最上方,以卡片形式展示。 动态获取:自动从后端 API 获取实时统计数据。 完整兼容:保留原朋友圈全部功能,布局不变。 **谨慎复制:**请谨慎复制,将我的链接换成你自己的! 📁 完整代码(替换你的 source/fcircle/index.md) --- title: 朋友圈 date: 2024-01-01 00:00:00 comments: false aside: false top_img: false --- <style> /* ===== 头部区域 ===== */ .fcircle-header { max-width: 1200px; margin: 30px auto 20px; padding: 0 20px; display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; } .header-title { flex: 1; } .header-title h1 { font-size: 2.8rem; font-weight: 700; color: #fff; margin: 0 0 5px; text-shadow: 0 2px 10px rgba(0,0,0,0.3); letter-spacing: 1px; } .header-title .subtitle { font-size: 1.1rem; color: #ccc; border-left: 4px solid #b87333; padding-left: 15px; margin: 0; } .header-meta { display: flex; align-items: center; gap: 20px; background: rgba(20,20,20,0.5); backdrop-filter: blur(8px); border-radius: 50px; padding: 12px 25px; border: 1px solid rgba(255,255,255,0.1); margin-top: 15px; } .update-time { color: #ccc; font-size: 0.95rem; display: flex; align-items: center; gap: 6px; } .update-time i { color: #b87333; } .refresh-btn { background: rgba(184,115,51,0.15); border: 1px solid #b87333; color: #b87333; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s; font-size: 1.2rem; } .refresh-btn:hover { background: #b87333; color: #111; transform: rotate(180deg); } /* ===== 统计卡片样式(保持不变) ===== */ .stats-container { display: flex; justify-content: center; gap: 25px; margin: 20px auto 40px; max-width: 900px; padding: 0 20px; flex-wrap: wrap; } .stat-card { flex: 1; min-width: 150px; background: rgba(20, 20, 20, 0.65); backdrop-filter: blur(12px) saturate(180%); -webkit-backdrop-filter: blur(12px) saturate(180%); border-radius: 32px; padding: 28px 15px; text-align: center; border: 1px solid rgba(255, 255, 255, 0.12); box-shadow: 0 25px 45px -15px rgba(0, 0, 0, 0.4); transition: transform 0.25s ease, border-color 0.25s; } .stat-card:hover { transform: translateY(-6px); border-color: #b87333; } .stat-number { font-size: 3.2rem; font-weight: 800; color: #b87333; line-height: 1.2; text-shadow: 0 0 15px rgba(184, 115, 51, 0.3); letter-spacing: 1px; } .stat-label { font-size: 1rem; color: #ccc; letter-spacing: 1.5px; text-transform: uppercase; margin-top: 10px; font-weight: 500; } /* ===== 文章卡片列表(与之前相同) ===== */ .articles-container { max-width: 1200px; margin: 30px auto; padding: 0 20px; } .article-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 25px; margin-bottom: 40px; } .article-card { background: rgba(20, 20, 20, 0.6); backdrop-filter: blur(12px) saturate(180%); -webkit-backdrop-filter: blur(12px) saturate(180%); border-radius: 28px; padding: 20px; border: 1px solid rgba(255, 255, 255, 0.1); transition: transform 0.3s ease, border-color 0.3s; display: flex; flex-direction: column; } .article-card:hover { transform: translateY(-6px); border-color: #b87333; } .article-header { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; } .article-avatar { width: 48px; height: 48px; border-radius: 50%; overflow: hidden; border: 2px solid rgba(255, 255, 255, 0.2); flex-shrink: 0; } .article-avatar img { width: 100%; height: 100%; object-fit: cover; } .article-author { font-size: 1.1rem; font-weight: 600; color: #fff; line-height: 1.3; } .article-author small { display: block; font-size: 0.8rem; color: #aaa; font-weight: 400; margin-top: 4px; } .article-title { font-size: 1.2rem; font-weight: 600; color: #fff; margin-bottom: 12px; line-height: 1.4; word-break: break-word; } .article-title a { color: inherit; text-decoration: none; } .article-title a:hover { color: #b87333; } .article-meta { display: flex; justify-content: space-between; align-items: center; margin-top: auto; font-size: 0.9rem; color: #aaa; } .article-time { display: flex; align-items: center; gap: 5px; } .article-time i { font-size: 0.8rem; } /* 分页控件 */ .pagination { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 40px 0; } .page-btn { background: rgba(184, 115, 51, 0.2); border: 1px solid #b87333; color: #b87333; width: 44px; height: 44px; border-radius: 50%; font-size: 1.4rem; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2…

Read more · Journal