前景介绍

基于梦爱吃鱼的教程进行修改,主要改进如下

  1. 适配了Halo程序

  2. 修改API接口为 青桔API

使用方法

申请API密钥

首先需要在 https://api.nsuuu.com 申请一个API密钥

69366ab58b6d7.webp

获取经纬度信息

百度地图

访问百度地图拾取坐标系统

在搜索框输入地址

点击地图上的点获取经纬度

复制对应的经度(lng)和纬度(lat)值

高德地图

访问高德坐标拾取器

在搜索框输入地址

点击地图上的点获取经纬度

复制对应的经度(lng)和纬度(lat)值

相关代码

将以下内容填入自定义侧边栏中,自定义部分大约在第120行

<div style="display: flex; justify-content: center; margin-bottom: 10px; width: 100%;">
    <div style="text-align: left; line-height: 26px;">
        <div style="font-weight: bold; font-size: 18px; margin-bottom: 8px;">
            <span style="color: #ea444d; margin-right: 4px;">👤</span>欢迎来访者!
        </div>
        <div style="font-size: 15px;">
            <span>👋🏻 Hi,我是Vlig,欢迎你!</span><br>
            <span>❓ 如有问题欢迎评论区交流!</span><br>
            <span>😫 页面异常?尝试 <kbd>Ctrl</kbd> + <kbd>F5</kbd></span><br>
            <span>📧 如需联系我:<a href="mailto:aheayh@163.com" style="font-weight:bold; text-decoration: none; color: var(--anzhiyu-theme);">发送邮件🚀</a></span>
        </div>
    </div>
</div>

<style>
    /* 键盘按键样式 */
    kbd {
        background-color: #f7f7f7;
        border: 1px solid #ccc;
        border-radius: 3px;
        box-shadow: 0 1px 0 rgba(0,0,0,0.2);
        color: #333;
        display: inline-block;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 12px;
        line-height: 1.4;
        margin: 0 2px;
        padding: 0 6px;
        text-shadow: 0 1px 0 #fff;
        vertical-align: middle;
    }
    [data-theme="dark"] kbd {
        background-color: #333;
        border-color: #555;
        color: #eee;
        text-shadow: none;
        box-shadow: 0 1px 0 rgba(255,255,255,0.1);
    }

    /* 欢迎卡片容器样式 */
    #welcome-info {
        user-select: none;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 212px;
        padding: 10px;
        margin-top: 5px;
        border-radius: 12px;
        background-color: var(--anzhiyu-background);
        outline: 1px solid var(--anzhiyu-card-border);
    }

    /* 加载动画 */
    .loading-spinner {
        width: 50px;
        height: 50px;
        border: 3px solid rgba(0, 0, 0, 0.1);
        border-radius: 50%;
        border-top: 3px solid var(--anzhiyu-main);
        animation: spin 1s linear infinite;
    }
    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }

    /* IP地址模糊效果 */
    .ip-address {
        filter: blur(5px);
        transition: filter 0.3s ease;
    }
    .ip-address:hover {
        filter: blur(0);
    }

    /* 错误提示样式 */
    .error-message {
        color: #ff6565;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
    .error-message p { margin: 0; }
    .error-icon { font-size: 3rem; }
    
    /* 纯文字重试按钮样式 */
    #retry-button {
        cursor: pointer;
        color: var(--anzhiyu-main);
        text-decoration: underline;
        font-weight: bold;
        margin: 0 4px;
        transition: opacity 0.3s;
    }
    #retry-button:hover { opacity: 0.8; }

    /* 权限弹窗 */
    .permission-dialog { text-align: center; }
    .permission-dialog button {
        margin: 10px 5px;
        padding: 5px 10px;
        border: none;
        border-radius: 5px;
        background-color: #4299e1 !important;
        color: white;
        cursor: pointer;
    }
    .permission-dialog button:hover { opacity: 0.8; }
</style>

<div id="welcome-info"></div>

<script>
// ==========================================
// 配置区域
// ==========================================
const IP_CONFIG = {
    API_KEY: 'API密钥', // 替换为你的API密钥
    BLOG_LOCATION: {
        lng: 115.470,// 替换为你的经度
        lat: 38.880// 替换为你的纬度
    },
    CACHE_DURATION: 1000 * 60 * 60
};

// ==========================================
// 核心功能函数
// ==========================================

const getWelcomeInfoElement = () => document.querySelector('#welcome-info');

const fetchIpData = async () => {
    const response = await fetch(`https://v1.nsuuu.com/api/ipip?key=${IP_CONFIG.API_KEY}`);
    if (!response.ok) throw new Error('网络响应不正常');
    const json = await response.json();
    if (json.code !== 200) throw new Error('API返回错误');
    
    const apiData = json.data;
    return {
        ip: apiData.ip || apiData.query || '未知',
        data: {
            country: apiData.country || '神秘地区',
            province: apiData.province || apiData.prov || '', 
            city: apiData.city || '',
            lng: parseFloat(apiData.longitude || apiData.lng) || 0,
            lat: parseFloat(apiData.latitude || apiData.lat) || 0
        }
    };
};

const showWelcome = ({ data, ip }) => {
    if (!data) return showErrorMessage();

    const { lng, lat, country, province, city } = data;
    const welcomeInfo = getWelcomeInfoElement();
    if (!welcomeInfo) return;

    const dist = calculateDistance(lng, lat);
    const ipDisplay = formatIpDisplay(ip);
    const pos = formatLocation(country, province, city);

    welcomeInfo.style.display = 'block';
    welcomeInfo.style.height = 'auto';
    welcomeInfo.innerHTML = generateWelcomeMessage(pos, dist, ipDisplay, country, province, city);
};

const calculateDistance = (lng, lat) => {
    const R = 6371;
    const rad = Math.PI / 180;
    const dLat = (lat - IP_CONFIG.BLOG_LOCATION.lat) * rad;
    const dLon = (lng - IP_CONFIG.BLOG_LOCATION.lng) * rad;
    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(IP_CONFIG.BLOG_LOCATION.lat * rad) * Math.cos(lat * rad) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);
    return Math.round(R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)));
};

const formatIpDisplay = (ip) => ip.includes(":") ? "<br>好复杂,咱看不懂~(ipv6)" : ip;

// 去重直辖市/特别行政区
const formatLocation = (country, prov, city) => {
    if (country === "中国") {
        // 如果省和市名称相似(如 北京 和 北京市),或者完全相同,则只显示城市名
        if (prov && city && (prov === city || prov.includes(city) || city.includes(prov))) {
            return city;
        }
        return `${prov} ${city}`.trim() || prov;
    }
    return country || '神秘地区';
};

const generateWelcomeMessage = (pos, dist, ipDisplay, country, prov, city) => `
    欢迎来自 <b>${pos}</b> 的小友💖<br>
    你当前距博主约 <b>${dist}</b> 公里!<br>
    你的IP地址:<b class="ip-address">${ipDisplay}</b><br>
    ${getTimeGreeting()}<br>
    Tip:<b>${getGreeting(country, prov, city)}🍂</b>
`;

// ==========================================
// 智能匹配欢迎语逻辑
// ==========================================
const findMatchingKey = (object, searchKey) => {
    if (!object || !searchKey) return null;
    const keys = Object.keys(object);
    if (object[searchKey]) return searchKey;
    const match = keys.find(key => key.includes(searchKey) || searchKey.includes(key));
    return match || null;
};

const getGreeting = (country, province, city) => {
    const matchedCountryKey = findMatchingKey(greetings, country);
    const countryConfig = matchedCountryKey ? greetings[matchedCountryKey] : greetings["其他"];

    if (typeof countryConfig === 'string') return countryConfig;

    const matchedProvinceKey = findMatchingKey(countryConfig, province);
    const provinceConfig = matchedProvinceKey ? countryConfig[matchedProvinceKey] : countryConfig["其他"];

    if (typeof provinceConfig === 'string') return provinceConfig;

    const matchedCityKey = findMatchingKey(provinceConfig, city);
    const cityGreeting = matchedCityKey ? provinceConfig[matchedCityKey] : null;

    return cityGreeting || provinceConfig["其他"] || countryConfig["其他"] || greetings["其他"];
};

const getTimeGreeting = () => {
    const hour = new Date().getHours();
    if (hour < 6) return "凌晨好还在修仙吗?💤";
    if (hour < 9) return "早上好,一日之计在于晨 👋";
    if (hour < 12) return "上午好,工作顺利嘛?💻";
    if (hour < 14) return "中午好,午休时间到啦 ☀️";
    if (hour < 17) return "下午好,饮茶先啦 ☕";
    if (hour < 19) return "即将下班,记得按时吃饭 🍽️";
    if (hour < 22) return "晚上好,放松一下吧 🌙";
    return "夜深了,早点休息喔 🛌";
};

// ==========================================
//语料库
// ==========================================
const greetings = {
    "中国": {
        "北京市": "这里有紫禁城的红墙黄瓦,也有胡同里的烟火人间",
        "上海市": "魔都结界,咖啡与生煎共舞,侬好呀!",
        "天津市": "结界!这里是天津卫,煎饼果子来一套?",
        "重庆市": "勒是雾都!8D魔幻城市,火锅底料整起!",
        "香港": "东方之珠,购物天堂",
        "澳门": "中西合璧,美食无限",

        "甘肃省": {
            "兰州市": "一碗牛肉面,一条母亲河,一本读者",
            "天水市": "羲皇故里,麦积烟雨,吃呱呱了吗?",
            "嘉峪关市": "天下第一雄关,大漠孤烟直",
            "武威市": "马踏飞燕故里,中国旅游标志之都",
            "金昌市": "紫金花城,镍都风光",
            "白银市": "铜城岁月,工业记忆",
            "酒泉市": "敦者大也,煌者盛也,一眼千年看莫高",
            "张掖市": "七彩丹霞,上帝的调色盘",
            "庆阳市": "环县羊肉香,农耕文化源",
            "平凉市": "问道崆峒,养生胜地",
            "定西市": "苦甲天下?不,是马铃薯之乡!",
            "陇南市": "陇上江南,风景独好",
            "临夏回族自治州": "河州牡丹,花儿与少年",
            "甘南藏族自治州": "九色甘南,香巴拉的呼唤",
            "其他": "羌笛何须怨杨柳,春风不度玉门关"
        },

        "河北省": {
            "石家庄市": "国际庄欢迎您,摇滚之乡!",
            "秦皇岛市": "东临碣石,以观沧海",
            "保定市": "驴肉火烧,真香!",
            "唐山市": "凤凰涅槃,工业重镇",
            "邯郸市": "三千年古都,成语之乡",
            "其他": "山势巍巍成壁垒,无限江山"
        },
        "山西省": {
            "太原市": "人说山西好风光,地肥水美五谷香",
            "大同市": "云冈石窟,千年微笑",
            "平遥": "穿越回清朝,看古城风貌",
            "临汾市": "华夏第一都,寻根问祖",
            "其他": "展开坐具长三尺,已占山河五百余"
        },
        "内蒙古自治区": {
            "呼和浩特市": "青城乳香飘,我在草原等你",
            "包头市": "鹿城风光好,钢铁意志强",
            "呼伦贝尔市": "大草原,大森林,大湿地",
            "其他": "天苍苍,野茫茫,风吹草低见牛羊"
        },
        "辽宁省": {
            "沈阳市": "一朝发祥地,两代帝王都,鸡架管够!",
            "大连市": "海风吹,海浪涌,这里是浪漫之都",
            "鞍山市": "钢铁之都,共和国长子",
            "其他": "老铁,来吃顿烧烤不?"
        },
        "吉林省": {
            "长春市": "北国春城,电影与汽车的摇篮",
            "吉林市": "雾凇岛上看雾凇,松花江畔吹晚风",
            "延边": "这里的冷面和打糕超级好吃!",
            "其他": "长白山下,雪花飘飘"
        },
        "黑龙江省": {
            "哈尔滨市": "东方小巴黎,中央大街走一走",
            "齐齐哈尔市": "这里是烧烤的耶路撒冷!",
            "大庆市": "石油之城,铁人精神",
            "漠河": "去找北,看极光",
            "其他": "不管是也是北,此处最北!"
        },
        "江苏省": {
            "南京市": "六朝古都,鸭血粉丝汤一绝",
            "苏州市": "君到姑苏见,人家尽枕河",
            "无锡市": "太湖佳绝处,毕竟在鼋头",
            "常州市": "中华恐龙园,童心未泯",
            "扬州市": "烟花三月下扬州,早上皮包水",
            "徐州市": "彭城自古列九州,帝王之乡",
            "连云港市": "孙大圣的老家,花果山下",
            "其他": "散装江苏,各自精彩!"
        },
        "浙江省": {
            "杭州市": "欲把西湖比西子,淡妆浓抹总相宜",
            "宁波市": "书藏古今,港通天下",
            "温州市": "这里的人都很会做生意!",
            "绍兴市": "鲁迅故里,乌篷船摇",
            "嘉兴市": "南湖烟雨,红船启航",
            "其他": "鱼米之乡,丝绸之府"
        },
        "安徽省": {
            "合肥市": "霸都欢迎你,科技之光闪耀",
            "芜湖市": "芜湖!起飞!",
            "黄山市": "五岳归来不看山,黄山归来不看岳",
            "安庆市": "黄梅戏乡,万里长江此封喉",
            "其他": "一生痴绝处,无梦到徽州"
        },
        "福建省": {
            "福州市": "七溜八溜,不离福州",
            "厦门市": "城在海上,海在城中,文艺圣地",
            "泉州市": "半城烟火半城仙,诸神人间办事处",
            "武夷山": "大红袍的故乡,丹霞地貌",
            "其他": "爱拼才会赢,来杯功夫茶?"
        },
        "江西省": {
            "南昌市": "落霞与孤鹜齐飞,拌粉瓦罐汤绝配",
            "景德镇市": "天青色等烟雨,而我在等你",
            "赣州市": "客家摇篮,红色故都",
            "九江市": "庐山真面目,只缘身在此山中",
            "其他": "物华天宝,人杰地灵"
        },
        "山东省": {
            "济南市": "四面荷花三面柳,一城山色半城湖",
            "青岛市": "红瓦绿树,碧海蓝天,哈啤酒吃嘎啦",
            "烟台市": "人间仙境,苹果真甜",
            "威海市": "走遍四海,还是威海",
            "淄博市": "进淄赶烤,好客山东!",
            "泰安市": "登泰山,小天下",
            "曲阜": "孔子故里,儒家文化",
            "其他": "孔孟之乡,礼仪之邦"
        },
        "河南省": {
            "郑州市": "天地之中,功夫郑州",
            "洛阳市": "若问古今兴废事,请君只看洛阳城",
            "开封市": "一城宋韵,半城水,梦回千年",
            "南阳市": "臣本布衣,躬耕于南阳",
            "信阳市": "江南北国,北国江南,毛尖茶香",
            "安阳市": "一片甲骨惊天下",
            "其他": "中原大地,老家河南,整碗烩面中不中?"
        },
        "湖北省": {
            "武汉市": "热干面配蛋酒,过早文化博大精深",
            "宜昌市": "高峡出平湖,当惊世界殊",
            "襄阳市": "铁打的襄阳,侠义之城",
            "恩施": "仙居恩施,世外桃源",
            "其他": "天上九头鸟,地下湖北佬"
        },
        "湖南省": {
            "长沙市": "茶颜悦色喝到了吗?臭豆腐吃了吗?",
            "张家界市": "缩小的仙境,放大的盆景",
            "岳阳市": "洞庭天下水,岳阳天下楼",
            "凤凰": "边城故事,吊脚楼旁",
            "其他": "吃得苦,耐得烦,霸得蛮"
        },
        "广东省": {
            "广州市": "饮佐茶未?食在广州,味在西关",
            "深圳市": "来了就是深圳人,搞钱要紧!",
            "珠海市": "百岛之市,浪漫随行",
            "佛山市": "黄飞鸿的故乡,功夫美食皆一流",
            "东莞市": "世界工厂,潮流之都",
            "汕头市": "海滨邹鲁,美食孤岛",
            "湛江市": "中国海鲜美食之都",
            "其他": "靓仔/靓女,来玩呀!"
        },
        "广西壮族自治区": {
            "南宁市": "老友粉不仅是味道,更是情怀",
            "桂林市": "桂林山水甲天下,阳朔山水甲桂林",
            "柳州市": "闻着臭吃着香,螺蛳粉yyds!",
            "北海市": "去银滩踩沙,去涠洲岛看海",
            "其他": "唱山歌,这边唱来那边和"
        },
        "海南省": {
            "海口市": "骑楼老街,椰风海韵",
            "三亚市": "天涯海角,东方夏威夷",
            "文昌市": "看火箭升空,吃文昌鸡",
            "其他": "在椰树下躺平,听海浪的声音"
        },
        "四川省": {
            "成都市": "和我在成都的街头走一走,看大熊猫",
            "绵阳市": "富乐之乡,科技之城",
            "乐山市": "食在四川,味在乐山,看大佛",
            "阿坝": "九寨归来不看水",
            "宜宾市": "万里长江第一城,五粮液故乡",
            "其他": "巴适得板,安逸得惨"
        },
        "贵州省": {
            "贵阳市": "爽爽的贵阳,避暑天堂",
            "遵义市": "转折之城,红色圣地",
            "六盘水市": "中国凉都,19度的夏天",
            "其他": "天无三日晴,地无三里平,但风景绝美"
        },
        "云南省": {
            "昆明市": "天气常如二三月,花枝不断四时春",
            "大理": "上关花,下关风,苍山雪,洱海月",
            "丽江市": "去有风的地方,发发呆",
            "西双版纳": "热带雨林,孔雀之乡",
            "香格里拉": "心中的日月,人间的天堂",
            "其他": "彩云之南,心之所向"
        },
        "西藏自治区": {
            "拉萨市": "日光城,布达拉宫的信仰",
            "林芝市": "西藏江南,桃花盛开",
            "日喀则市": "珠峰的故乡",
            "其他": "离天堂最近的地方"
        },
        "陕西省": {
            "西安市": "吹过的风都是文化,踩下的土都是历史",
            "宝鸡市": "炎帝故里,青铜器之乡",
            "延安市": "几回回梦里回延安",
            "咸阳市": "秦砖汉瓦,帝王陵寝",
            "榆林市": "塞上驼城,能源之都",
            "其他": "三秦大地,肉夹馍凉皮来一套"
        },
        "青海省": {
            "西宁市": "中国夏都,清凉一夏",
            "格尔木市": "昆仑山口,万山之祖",
            "其他": "天空之镜,万湖之源"
        },
        "宁夏回族自治区": {
            "银川市": "塞上江南,神奇宁夏",
            "中卫市": "大漠孤烟直,长河落日圆",
            "其他": "星星的故乡"
        },
        "新疆维吾尔自治区": {
            "乌鲁木齐市": "离海洋最远的城市,瓜果飘香",
            "喀什地区": "不到喀什,不算到新疆",
            "伊犁": "塞外江南,薰衣草花海",
            "吐鲁番": "火焰山下,葡萄沟甜",
            "其他": "此时此刻,你也许在吃烤包子?"
        },
        "台湾": "宝岛风光,夜市美食",
        "其他": "带我去你的城市逛逛吧!"
    },
    "美国": "Make yourself at home.",
    "日本": "こんにちは,樱花开了吗?",
    "韩国": "안녕하세요,炸鸡啤酒安排上?",
    "俄罗斯": "战斗民族,伏特加吨吨吨",
    "英国": "天气不错,来杯下午茶?",
    "法国": "Bonjour,浪漫的国度",
    "德国": "严谨与啤酒的碰撞",
    "澳大利亚": "小心袋鼠拳击手!",
    "加拿大": "枫叶之国,冰雪奇缘",
    "新加坡": "花园城市,海南鸡饭",
    "泰国": "萨瓦迪卡,冬阴功汤",
    "其他": "世界那么大,欢迎你来看!"
};

// ==========================================
// 缓存与权限逻辑
// ==========================================

const IP_CACHE_KEY = 'ip_info_cache';
const getIpInfoFromCache = () => {
    const cached = localStorage.getItem(IP_CACHE_KEY);
    if (!cached) return null;
    const { data, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp > IP_CONFIG.CACHE_DURATION) {
        localStorage.removeItem(IP_CACHE_KEY);
        return null;
    }
    return data;
};
const setIpInfoCache = (data) => {
    localStorage.setItem(IP_CACHE_KEY, JSON.stringify({
        data,
        timestamp: Date.now()
    }));
};

const checkLocationPermission = () => localStorage.getItem('locationPermission') === 'granted';
const saveLocationPermission = (permission) => localStorage.setItem('locationPermission', permission);

const showLocationPermissionDialog = () => {
    const welcomeInfoElement = getWelcomeInfoElement();
    welcomeInfoElement.innerHTML = `
        <div class="permission-dialog">
            <div class="error-icon">❓</div>
            <p>是否允许访问您的位置信息?</p>
            <button id="btn-allow">允许</button>
            <button id="btn-deny">拒绝</button>
        </div>
    `;

    document.getElementById('btn-allow').onclick = () => {
        handleLocationPermission('granted');
    };
    document.getElementById('btn-deny').onclick = () => {
        handleLocationPermission('denied');
    };
};

const handleLocationPermission = (permission) => {
    saveLocationPermission(permission);
    if (permission === 'granted') {
        showLoadingSpinner();
        fetchIpInfo();
    } else {
        showErrorMessage('您已拒绝访问位置信息');
    }
};

const showLoadingSpinner = () => {
    const welcomeInfoElement = getWelcomeInfoElement();
    if (!welcomeInfoElement) return;
    welcomeInfoElement.innerHTML = '<div class="loading-spinner"></div>';
};

const showErrorMessage = (message = '抱歉,无法获取信息') => {
    const welcomeInfoElement = getWelcomeInfoElement();
    if (!welcomeInfoElement) return;
    welcomeInfoElement.innerHTML = `
        <div class="error-message">
            <div class="error-icon">😕</div>
            <p>${message}</p>
            <p>请 <span id="retry-button">重试</span> 或检查网络连接</p>
        </div>
    `;
    const retryButton = document.getElementById('retry-button');
    if (retryButton) retryButton.onclick = () => {
        if (!checkLocationPermission()) {
            showLocationPermissionDialog();
        } else {
            fetchIpInfo();
        }
    };
};

const fetchIpInfo = async () => {
    if (!checkLocationPermission()) {
        showLocationPermissionDialog();
        return;
    }
    showLoadingSpinner();
    const cachedData = getIpInfoFromCache();
    if (cachedData) {
        showWelcome(cachedData);
        return;
    }
    try {
        const data = await fetchIpData();
        setIpInfoCache(data);
        showWelcome(data);
    } catch (error) {
        console.error('获取IP信息失败:', error);
        showErrorMessage();
    }
};

document.addEventListener('DOMContentLoaded', () => {
    fetchIpInfo();
});
</script>

注意事项

  1. 确保API密钥正确填写,正确设置白名单

  2. 经纬度没必要填写详细,为了自身隐私安全