添加来访者卡片
2048 字
10 分钟
添加来访者卡片
前言
本文参考自青桔气球
前段时间Nice猫的api失效了,这导致我本来的来访者卡片无法使用了,于是决定改用小小api,遂有了这篇文章。
教程部分
这次的教程使用小小api,显示来访者地址和ip
创建js文件
在[root]\hexo\source\js路径创建welcome.js,写入代码。
window.IP_CONFIG = { CACHE_DURATION: 1000 * 60 * 60, HOME_PAGE_ONLY: true,};
const insertAnnouncementComponent = () => { const announcementCards = document.querySelectorAll('.card-widget.card-announcement'); if (!announcementCards.length) return;
if (IP_CONFIG.HOME_PAGE_ONLY && !isHomePage()) { announcementCards.forEach(card => card.remove()); return; }
if (!document.querySelector('#welcome-info')) return; fetchIpInfo();};
const getWelcomeInfoElement = () => document.querySelector('#welcome-info');
// 获取 IP 数据const fetchIpData = async () => { const response = await fetch('https://v2.xxapi.cn/api/ua'); if (!response.ok) throw new Error('网络响应不正常'); const result = await response.json(); if (result?.code !== 200 || !result?.data?.address) { throw new Error('API 返回数据异常'); } return result;};
// 地址字符串解析const parseAddress = (address) => { if (!address) return { country: '其他', province: '', city: '' };
let country = '其他'; let addr = address.trim();
if (addr.startsWith('中国')) { country = '中国'; addr = addr.substring(2); } else if (addr.includes('台湾')) { country = '台湾'; } else if (addr.includes('香港')) { country = '香港特别行政区'; } else if (addr.includes('澳门')) { country = '澳门特别行政区'; } else if (/[A-Za-z]/.test(addr)) { country = addr.split(',')[0]?.trim() || '其他'; return { country, province: '', city: '' }; }
if (country !== '中国') { return { country, province: '', city: '' }; }
const provinces = [ '内蒙古自治区', '广西壮族自治区', '西藏自治区', '宁夏回族自治区', '新疆维吾尔自治区', '香港特别行政区', '澳门特别行政区', '北京市', '天津市', '上海市', '重庆市', '河北省', '山西省', '辽宁省', '吉林省', '黑龙江省', '江苏省', '浙江省', '安徽省', '福建省', '江西省', '山东省', '河南省', '湖北省', '湖南省', '广东省', '海南省', '四川省', '贵州省', '云南省', '陕西省', '甘肃省', '青海省', '台湾省' ].sort((a, b) => b.length - a.length);
let province = ''; let remaining = addr;
for (const prov of provinces) { if (addr.startsWith(prov)) { province = prov; remaining = addr.substring(prov.length); break; } }
let city = '未知'; if (remaining) { remaining = remaining.replace(/^[省市自治区]/, ''); const cityMatch = remaining.match(/^([\u4e00-\u9fa5]+?(?:市 | 地区 | 自治州 | 州 | 盟 | 县 | 区 | 旗)?)$/); if (cityMatch && cityMatch[1]) { city = cityMatch[1]; }
if (['北京市', '天津市', '上海市', '重庆市'].includes(province)) { city = province.replace('市', '') + (remaining ? ' ' + remaining : ''); } }
const provinceKey = province .replace('省', '') .replace('自治区', '') .replace('特别行政区', '') .replace('市', '');
return { country, province, provinceKey, city };};
const formatIpDisplay = (ip) => { if (!ip) return '未知'; return ip.includes(":") ? "<br>好复杂,咱看不懂~(ipv6)" : ip;};
const formatLocation = (country, province, city) => { if (!country) return '神秘地区'; if (country === '中国') { if (['北京市', '天津市', '上海市', '重庆市'].includes(province)) { return province; } if (city && city !== '未知') { return `${province} ${city}`; } return province; } return country;};
const generateWelcomeMessage = (pos, ipDisplay, greeting) => ` <div class="welcome-content"> <div class="welcome-line">欢迎来自 <b>${pos}</b> 的小友💖</div> <div class="welcome-line">你的 IP 地址:<b class="ip-address">${ipDisplay}</b></div> <div class="welcome-line">${getTimeGreeting()}</div> <div class="welcome-line tip-line">Tip:<b>${greeting}🍂</b></div> </div>`;
// 🌓 适配 Butterfly 黑暗模式的样式const addStyles = () => { const style = document.createElement('style'); style.textContent = ` /* ===== 明亮模式(默认,保持原样式)===== */ #welcome-info { user-select: none; display: flex; justify-content: center; align-items: center; min-height: 180px; padding: 18px 24px; margin-top: 10px; border-radius: 12px; /* 明亮模式固定颜色 */ background-color: #f8f9fa !important; border: 1px solid #e0e0e0 !important; line-height: 2; font-size: 17px; color: #333 !important; }
.welcome-content { text-align: center; width: 100%; }
.welcome-line { margin: 6px 0; }
.welcome-line b { font-weight: 600; /* 明亮模式固定主题色 */ color: #425aeff !important; font-size: 17px; }
.tip-line { margin-top: 10px; padding-top: 10px; border-top: 1px dashed #e0e0e0; }
/* IP 地址样式(明亮模式保持不变) */ .ip-address { filter: blur(4px); transition: filter 0.3s ease; cursor: pointer; padding: 3px 8px; background: rgba(0, 0, 0, 0.05); border-radius: 4px; color: #333 !important; }
.ip-address:hover { filter: blur(0); background: rgba(0, 0, 0, 0.1); }
.loading-spinner { width: 50px; height: 50px; border: 3px solid rgba(0, 0, 0, 0.1); border-radius: 50%; border-top: 3px solid #425aeff; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.error-message { color: #ff6565; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; gap: 12px; }
.error-icon { font-size: 3rem; }
#retry-button { margin: 0 5px; color: #425aeff; cursor: pointer; transition: transform 0.3s; display: inline-flex; align-items: center; gap: 4px; font-size: 17px; }
#retry-button:hover { transform: rotate(180deg); }
/* 🌓 黑暗模式适配(Butterfly 主题) */ [data-theme="dark"] #welcome-info, body.dark-mode #welcome-info, .dark-theme #welcome-info { /* 黑暗模式使用主题变量 */ background-color: var(--anzhiyu-background, #1c1c1c) !important; border-color: var(--anzhiyu-card-border, #3a3a3a) !important; color: var(--anzhiyu-fontcolor, #ccc) !important; }
[data-theme="dark"] .welcome-line b, body.dark-mode .welcome-line b, .dark-theme .welcome-line b { color: var(--anzhiyu-main, #5c82ff) !important; }
[data-theme="dark"] .tip-line, body.dark-mode .tip-line, .dark-theme .tip-line { border-top-color: var(--anzhiyu-card-border, #3a3a3a); }
[data-theme="dark"] .ip-address, body.dark-mode .ip-address, .dark-theme .ip-address { background: rgba(255, 255, 255, 0.08); color: var(--anzhiyu-fontcolor, #ccc) !important; }
[data-theme="dark"] .ip-address:hover, body.dark-mode .ip-address:hover, .dark-theme .ip-address:hover { background: rgba(255, 255, 255, 0.12); }
[data-theme="dark"] .loading-spinner, body.dark-mode .loading-spinner, .dark-theme .loading-spinner { border-color: rgba(255, 255, 255, 0.1); border-top-color: var(--anzhiyu-main, #5c82ff); }
[data-theme="dark"] #retry-button, body.dark-mode #retry-button, .dark-theme #retry-button { color: var(--anzhiyu-main, #5c82ff); }
/* 移动端适配 */ @media (max-width: 768px) { #welcome-info { font-size: 15px; padding: 14px 18px; min-height: 160px; } .welcome-line b { font-size: 15px; } #retry-button { font-size: 15px; } } `; document.head.appendChild(style);};
const showLoadingSpinner = () => { const el = document.querySelector("#welcome-info"); if (el) el.innerHTML = '<div class="loading-spinner"></div>';};
// 缓存管理const IP_CACHE_KEY = 'ip_info_cache_v6';
const getFromCache = (key) => { try { const cached = localStorage.getItem(key); if (!cached) return null; const { data, timestamp } = JSON.parse(cached); if (Date.now() - timestamp > IP_CONFIG.CACHE_DURATION) { localStorage.removeItem(key); return null; } return data; } catch { return null; }};
const setToCache = (key, data) => { try { localStorage.setItem(key, JSON.stringify({ data, timestamp: Date.now() })); } catch (e) { console.warn('缓存失败:', e); }};
const showWelcome = ({ data, ip }) => { const apiData = data?.data || data; const userIp = apiData?.ip || ip;
if (!apiData?.address) { console.error('❌ 地址数据为空:', apiData); return showErrorMessage(); }
const { country, province, provinceKey, city } = parseAddress(apiData.address); const ipDisplay = formatIpDisplay(userIp); const pos = formatLocation(country, province, city); const greeting = getGreeting(country, provinceKey);
const welcomeInfo = getWelcomeInfoElement(); if (!welcomeInfo) return;
welcomeInfo.style.display = 'block'; welcomeInfo.style.height = 'auto'; welcomeInfo.innerHTML = generateWelcomeMessage(pos, ipDisplay, greeting);};
const fetchIpInfo = async () => { showLoadingSpinner();
const cached = getFromCache(IP_CACHE_KEY); if (cached) { console.log('💾 使用缓存数据'); showWelcome(cached); return; }
try { const data = await fetchIpData(); setToCache(IP_CACHE_KEY, data); showWelcome(data); } catch (error) { console.error('❌ 获取 IP 信息失败:', error); showErrorMessage(); }};
const greetings = { "中国": { "北京": "北——京——欢迎你~~~", "天津": "讲段相声,品盏茶汤", "河北": "燕赵大地,慷慨悲歌", "山西": "晋善晋美,表里山河", "内蒙古": "天苍苍野茫茫,风吹草低见牛羊", "辽宁": "辽沈大地,豪爽敞亮", "吉林": "白山黑水,雾凇奇缘", "黑龙江": "冰雪童话,北国风光", "上海": "魔都闪耀,海纳百川", "江苏": "江南水乡,温婉如画", "浙江": "诗画浙江,人间天堂", "安徽": "徽风皖韵,山水人文", "福建": "山海福建,爱拼会赢", "江西": "物华天宝,人杰地灵", "山东": "孔孟之乡,好客山东", "河南": "中原腹地,华夏之源", "湖北": "千湖之省,荆楚风华", "湖南": "潇湘山水,敢为人先", "广东": "粤韵风华,食在广东", "广西": "桂林山水甲天下", "海南": "椰风海韵,天涯海角", "四川": "天府之国,巴适得板", "贵州": "山地公园,多彩贵州", "云南": "彩云之南,心灵故乡", "西藏": "圣洁西藏,心灵净土", "陕西": "千年古都,常来长安", "甘肃": "丝路明珠,壮美甘肃", "青海": "大美青海,三江之源", "宁夏": "塞上江南,神奇宁夏", "新疆": "大美新疆,亚克西", "台湾": "宝岛台湾,血脉相连", "香港": "东方之珠,魅力香港", "澳门": "莲花宝地,中西交融", "其他": "欢迎来到我的小小世界✨" }, "美国": "Hello! Welcome from the USA 🇺🇸", "日本": "ようこそ!一緒に桜を見ましょう 🌸", "俄罗斯": "Привет! 来自战斗民族的朋友 🇷🇺", "法国": "Bonjour! C'est la vie 🇫🇷", "德国": "Hallo! 严谨与浪漫的结合 🇩🇪", "澳大利亚": "G'day! 欢迎来到南半球 🇦🇺", "加拿大": "Hello! 枫叶之国欢迎你 🇨🇦", "其他": "Welcome! 世界因你而精彩 🌍"};
const getGreeting = (country, provinceKey) => { const countryData = greetings[country]; if (!countryData) return greetings["其他"]; if (typeof countryData === 'string') return countryData; return countryData[provinceKey] || countryData["其他"] || greetings["其他"];};
const getTimeGreeting = () => { const h = new Date().getHours(); if (h < 11) return "早上好🌤️ 一日之计在于晨"; if (h < 13) return "中午好☀️ 记得午休喔~"; if (h < 17) return "下午好🕞 饮茶先啦!"; if (h < 19) return "即将下班🚶♂️ 记得按时吃饭~"; return "晚上好🌙 夜生活嗨起来!";};
const showErrorMessage = (message = '抱歉,无法获取信息') => { const el = document.getElementById("welcome-info"); if (!el) return; el.innerHTML = ` <div class="error-message"> <div class="error-icon">😕</div> <p>${message}</p> <p><span id="retry-button">🔄</span> 点击重试或检查网络</p> </div> `; document.getElementById('retry-button')?.addEventListener('click', fetchIpInfo);};
const isHomePage = () => window.location.pathname === '/' || window.location.pathname === '/index.html';
// 初始化document.addEventListener('DOMContentLoaded', () => { addStyles(); insertAnnouncementComponent(); document.addEventListener('pjax:complete', insertAnnouncementComponent);});引入Js
在_config.butterfly.yml主题配置文件下inject配置项中的bottom引入welcome.js
inject: bottom: - <script src="/js/card-welcome.js"></script>引入卡片
在_config.butterfly.yml主题配置文件中引入<div id="welcome-info"></div>
card_announcement: enable: true content: <div id="welcome-info"></div>支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
相关文章 智能推荐
1
Butterfly:为博客添加微软Clarity数据统计
建站札记 这篇文章介绍了如何为博客添加微软Clarity数据统计工具,通过Hexo框架和Butterfly主题进行演示。首先需要注册微软账号并登录Clarity官网,然后新建项目并将网站添加到项目中。接下来,在`_config.butterfly.yml`主题配置文件中复制粘贴代码,按照缩进格式填写,最后保存后等待效果显现。
2
使用CloudFlare代理WebDav播放音乐
建站札记 这篇文章介绍了使用 CloudFlare Worker 实现 WebDav 服务的方法。文章中首先创建了一个名为 Workers 的目录,并在其中创建了两个文件:HelloWorld.worker.ts 和 proxyWorker.ts。这两个文件分别用于编写 HelloWorld 和代理音乐请求的代码。文章还提供了一些辅助函数,如 isOriginAllowed、getTestPage 和 debugInfo,以方便开发者进行调试和测试。
3
网站加速方案:阿里云免费版边缘安全加速ESA
建站札记 这篇文章介绍了如何通过阿里云免费版边缘安全加速ESA服务为个人博客网站加速。作者首先描述了自己无法使用国内CDN服务的情况,然后发现了阿里云的ESA服务,并决定尝试开通该服务。在开通过程中,作者详细解释了如何在控制台添加站点、设置DNS解析记录以及申请证书的具体步骤。
4
网站域名从blog.yvyang.fun迁移至blog.yvyang.top
建站札记 这篇文章主要讲述了作者从`yvyang.fun`这个域名迁移至`yvyang.top`的过程,以及将`yvyang.fun`重定向到`yvyang.top`的操作。文章还提到了续费价格的变化,以及作者对读者的祝福。
5
为网站添加并使用iconfont图标
建站札记 这篇文章介绍了如何在hexo-theme-butterfly主题中添加并使用Iconfont图标库。作者首先说明了在默认的FontAwesome图标库中存在的问题,如需要付费以及有时找不到特定图标的需求,随后转向引入Iconfont作为补充。Iconfont是由阿里妈妈MUX开发的一个矢量图标管理、交流平台。文章详细指导了注册账号和获取图标代码的过程,包括如何访问官网、选择图标加入购物车、添加到项目、更新配置文件等步骤。最后,文章总结了使用Iconfont图标服务的优势,并鼓励读者尝试这个工具以丰富网站内容。
随机文章 随机推荐