前段时间有小伙伴问这个是怎么实现的,单独开一篇来说一下

首先说明一下Dream2.0Plus的主题文档中提供了访客侧边栏的源码方案(笔者也是自己写完了才发现原来主题作者其实有提供)

https://www.hcjike.com/docs/halo-theme-dream2.0/theme/sidebar-assembly

如果想直接采用主题作者方案的小伙伴,直接套用上述链接内的代码按照常规的自定义侧边栏配置即可,也可自行编写HTML嵌入

为了对得起自己造的轮子,下面笔者详细说明一下制作过程。

定位API服务

市面上有太多免费的API接口服务了,笔者测试了很多不同服务商的,当然也包括腾讯、高德、百度这些大厂,总结来说大厂的东西确实更好用,但作为个人开发者每天有限额,比如腾讯的地图服务大概是每天6000次的调用量,当然这个量级对于一个博客站来说已经完全够用了,但是配置过于复杂。经过三番比较,笔者最终选择了Nice猫,只能说这大概是笔者见过的最稳定最好用的API聚合站之一。

https://api.nsmao.net/

注册并获取API Key

打开Nice猫,注册并登录,随后在密钥管理中找您的API KEY 密钥同时根据需要配置安全校验方式 ,建议配置。

本站访客信息侧边栏源码

废话不多说,直接看代码

<style>
    /* 居中对齐样式 */
    .align-center {
        text-align: center
    }

    /* 字体颜色样式 - 天蓝色 */
    .font-color {
        color: deepskyblue
    }

    /* IP地址遮罩样式 */
    .ip-mask {
        cursor: pointer;
        /* 鼠标悬停时显示手型光标 */
        user-select: none;
        /* 禁止用户选择文本 */
        transition: all .3s ease;
        /* 添加过渡动画效果 */
        filter: blur(4px)
            /* 默认模糊效果 */
    }

    /* IP地址遮罩悬停效果 */
    .ip-mask:hover {
        filter: none
            /* 悬停时取消模糊效果 */
    }
</style>
<!-- 欢迎信息区域 -->
<div id="welcomeMessage" class="align-center" style="margin-top:13px">
    <p>🌍 小伙伴你好呀! 🌍</p>
    <p>正在寻找你的足迹...</p>
</div>

<hr />

<!-- 友情链接区域 -->
<div class="align-center"
    style="margin-left:5px;margin-right:5px;margin-top:5px;border-bottom-width:unset;margin-bottom:5px">
    <p>🚇 继续探索未知 👇️👇️ 发现更多精彩 🚇</p>
    <!-- Travellings 友链接力链接 -->
    <a href="https://www.travellings.cn/go.html" target="_blank" rel="noopener" title="开往-友链接力" aria-label="开往-友链接力">
        <img src="https://www.travellings.cn/assets/logo.gif" alt="开往-友链接力" width="120" />
    </a>
    <!-- BlogsClub 空间穿梭链接 -->
    <a href="https://blogs.quest" target="_blank" title="空间穿梭-随机访问 BlogsClub 成员博客"
        aria-label="空间穿梭-随机访问 BlogsClub 成员博客">
        <img src="https://www.blogsclub.org/images/shuttle_4.png" alt="空间穿梭" />
    </a>
</div>
<script>
    /**
     * 根据当前时间获取问候语和建议
     * @returns {Object} 包含问候语(greeting)和建议(advice)的对象
     */
    function getGreetingAndAdvice() {
        // 获取当前小时数
        const hour = new Date().getHours();

        // 根据不同时间段返回相应的问候语和建议
        if (hour < 6) {
            return {
                greeting: "🌛 深夜好呀 👋",
                advice: "🌙不要熬夜 早点休息啦🌙"
            }
        }
        else if (hour < 11) {
            return {
                greeting: "🌞 早上好呀 👋",
                advice: "💪新的一天 充满活力💪"
            }
        }
        else if (hour < 13) {
            return {
                greeting: "🍽️ 中午好呀 👋",
                advice: "🍔别忘了享受一顿美味的午餐🍔"
            }
        }
        else if (hour < 18) {
            return {
                greeting: "☕ 下午好呀 👋",
                advice: "🍵休息一下 喝杯咖啡吧🍵"
            }
        }
        else if (hour < 22) {
            return {
                greeting: "🌜 晚上好呀 👋",
                advice: "🌃放松心情 享受夜晚的宁静🌃"
            }
        }
        else {
            return {
                greeting: "🌛 深夜好呀 👋",
                advice: "🌙不要熬夜 早点休息啦🌙"
            }
        }
    }

    /**
     * 角度转弧度函数
     * @param {number} degrees - 角度值
     * @returns {number} 弧度值
     */
    function toRadians(degrees) {
        return degrees * (Math.PI / 180)
    }

    /**
     * 计算两点间距离(Haversine公式)
     * @param {number} lat - 纬度
     * @param {number} lng - 经度
     * @returns {string} 格式化后的距离(公里)
     */
    function calculateDistance(lat, lng) {
        // 地球半径(公里)
        const R = 6371;
        // 自定义纬度(站点位置)
        const customLat = 0.00;
        // 自定义经度(站点位置)
        const customLng = 0.00;

        // 计算经纬度差值并转换为弧度
        const dLat = toRadians(lat - customLat);
        const dLon = toRadians(lng - customLng);

        // Haversine公式计算距离
        const a = Math.sin(dLat / 2) ** 2 +
            Math.cos(toRadians(customLat)) *
            Math.cos(toRadians(lat)) *
            Math.sin(dLon / 2) ** 2;

        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        // 返回格式化后的距离
        return (R * c).toFixed(2)
    }

    /**
     * 获取用户地理位置信息
     * @returns {Promise<Object|null>} 用户位置信息对象或null
     */
    async function fetchLocation() {
        // API配置数组
        const apis = [{
            url: "https://api.nsmao.net/api/ip/query?key=XXXXXXXXXXXXX",         
            provinceField: "prov"
        },
        {
            url: "https://api.nsmao.net/api/ipip/query?key=XXXXXXXXXXXXX",
            provinceField: "province"
        }
        ];

        // 遍历API尝试获取位置信息
        for (const api of apis) {
            try {
                // 发起请求
                const res = await fetch(api.url);
                // 解析响应
                const { code, data } = await res.json();
                // 请求成功
                if (code === 200) {
                    return {
                        // IP地址
                        ip: data.ip,
                        // 国家
                        country: data.country,
                        // 省份
                        province: data[api.provinceField],
                        // 城市
                        city: data.city,
                        // 区县
                        district: data.district,
                        // 行政区划代码
                        adcode: data.adcode,
                        // 距离
                        length: calculateDistance(data.lat, data.lng)
                    }
                }
            }
            catch (e) {
                console.error(`获取地理位置失败 (${api.url}):`, e);
                // 出错继续下一个API
                continue
            }
        }
        // 所有API都失败返回null
        return null
    }

    /**
     * 渲染欢迎信息
     * @param {Object} location - 位置信息对象
     */
    function renderWelcomeMessage(location) {
        // 获取问候语
        const { greeting, advice } = getGreetingAndAdvice();
        // 构建位置HTML字符串
        let locationHtml = "";

        // 按优先级拼接位置信息
        if (location.province) {
            locationHtml += `<strong class="font-color">${location.province} </strong>`
        }
        if (location.city) {
            locationHtml += `<strong class="font-color">${location.city} </strong>`
        }
        if (location.district) {
            locationHtml += `<strong class="font-color">${location.district}</strong>`
        }

        // 更新欢迎信息内容
        document.getElementById("welcomeMessage").innerHTML =
            `<p class="align-center"><strong>${greeting}</strong></p>` +
            `<p>欢迎来自 ${locationHtml} 的小伙伴!</p>` +
            `<p class="align-center">${advice}</p>` +
            `<p class="align-center">🌍 您当前的 IP 是<strong><span class="ip-mask font-color">${location.ip}</span></strong> 🌍</p>` +
            `<p class="align-center">📍 距离 <strong class="font-color">本站</strong> 约<strong class="font-color">${location.length}</strong> 公里哦!📍</p>`
    }

    /**
     * 初始化函数
     */
    async function init() {
        // 获取位置信息
        const location = await fetchLocation();

        if (location) {
            // 渲染欢迎信息
            renderWelcomeMessage(location);
        }
        else {
            // 获取失败时显示错误信息
            document.getElementById("welcomeMessage").innerHTML =
                `<p>🌍 这位小伙伴,你似乎迷失了~🌍</p><p>获取位置信息失败,请稍后再试~😅<p>`
        }
    }

    // 页面加载完成后执行初始化
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", init)
    }
    else {
        init()
    }
</script>

以上有几个需要进行配置的地方:

  • customLat(纬度)和customLng(经度)分别代表自定义的经纬度,默认是0.0,你可以设置成任何位置,同时也可以一并修改描述信息<strong class="font-color">本站</strong>,将本站改为你的目标地点名称。

  • url: "https://api.nsmao.net/api/ip/query?key=XXXXXXXXXXXXX"url: "https://api.nsmao.net/api/ipip/query?key=XXXXXXXXXXXXX"这里的key需要自行拼接上刚才在Nice猫生成的key

经纬度的获取也非常方便,直接使用百度坐标拾取器就行

https://lbs.baidu.com/maptool/getpoint

最后,把代码复制进自定义模块侧边栏的侧边栏内容里就大功告成了。