侧边栏添加个性定位欢迎信息

前景介绍

查看代码测试

基于梦爱吃鱼的教程进行修改,主要改进是更换 API 地址,因为原 API 已经失效。

使用方法

创建 JS 文件

在博客主题目录的 js 文件夹(~\themes\anzhiyu\source\js)下创建 welcome.js 文件(也可以在source文件夹下另外新建文件夹)。

配置经纬度

点击这里,找到
1
"latitude": xxx,"longitude": xxx,

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


将以下内容复制到 welcome.js 中,并修改文件顶部配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// 适配uapis.cn API的来访者欢迎卡片JS(完美适配简写省份:浙江 / 江苏 / 河南)
window.IP_CONFIG = {
BLOG_LOCATION: {
lng: 121.4, // 博主经度
lat: 29.9, // 博主纬度
},
CACHE_DURATION: 1000 * 60 * 60, // 缓存1小时
HOME_PAGE_ONLY: true, // 是否只在首页显示
};

// 添加样式
const addStyles = () => {
const style = document.createElement('style');
style.textContent = `
#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-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,
.permission-dialog p {
margin: 0;
}
.error-icon {
font-size: 3rem;
}
#retry-button {
margin: 0 5px;
color: var(--anzhiyu-main);
transition: transform 0.3s ease;
cursor: pointer;
}
#retry-button:hover {
transform: rotate(180deg);
}
`;
document.head.appendChild(style);
};

// 获取欢迎信息元素
const getWelcomeInfoElement = () => document.querySelector('#welcome-info');

// 显示加载动画
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>请<i id="retry-button" class="fa-solid fa-arrows-rotate"></i>重试或检查网络连接</p>
</div>
`;
document.getElementById('retry-button').addEventListener('click', fetchIpInfo);
};

// 缓存相关
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 calculateDistance = (lng, lat) => {
const R = 6371; // 地球半径(km)
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)));
};

// 格式化IP显示
const formatIpDisplay = (ip) => {
if (!ip) return "未知IP";
return ip.includes(":") ? "IPv6地址" : ip;
};

// 格式化位置
const formatLocation = (region) => {
if (!region) return '神秘地区';
return region.replace(/\s+/g, " ");
};

// 时段问候
const getTimeGreeting = () => {
const hour = new Date().getHours();
if (hour < 11) return "早上好🌤️,一日之计在于晨";
if (hour < 13) return "中午好☀️,记得午休喔~";
if (hour < 17) return "下午好🕞,饮茶先啦!";
if (hour < 19) return "即将下班🚶‍♂️,记得按时吃饭~";
return "晚上好🌙,夜生活嗨起来!";
};

// ====================== 终极修复:适配 浙江、江苏 这种简写 ======================
const getGreeting = (region) => {
if (!region) return "带我去你的城市逛逛吧!";

const parts = region.trim().split(/\s+/).filter(Boolean);
const province = parts[1] || ""; // 浙江
const city = parts[2] || ""; // 宁波

// 城市匹配
if (city === "南京") return "六朝古都,风华依旧";
if (city === "苏州") return "上有天堂,下有苏杭";
if (city === "杭州") return "东风渐绿西湖柳,雁已还人未南归";
if (city === "郑州") return "豫州之域,天地之中";
if (city === "洛阳") return "洛阳城里见秋风,欲作家书意万重";
if (city === "开封") return "包公那叫一个铁面无私,严惩贪官污吏";
if (city === "宁波") return "海上丝路的起点,和我一个城市。";

// 省份匹配(简写版)
if (province === "北京") return "北京欢迎你~~~";
if (province === "天津") return "讲段相声吧";
if (province === "河北") return "山势巍巍成壁垒,天下雄关";
if (province === "山西") return "展开坐具长三尺,已占山河五百余";
if (province === "内蒙古") return "天苍苍,野茫茫,风吹草低见牛羊";
if (province === "辽宁") return "我想吃烤鸡架!";
if (province === "吉林") return "状元阁就是东北烧烤之王";
if (province === "黑龙江") return "很喜欢哈尔滨大剧院";
if (province === "上海") return "众所周知,中国只有两个城市";
if (province === "江苏") return "散装是必须要散装的";
if (province === "浙江") return "望海楼明照曙霞,闲塘十里尽梅花";
if (province === "河南") return "可否带我品尝河南烩面啦?";

return "欢迎来到我的博客!";
};
// ==============================================================================

// 生成欢迎信息
const generateWelcomeMessage = (region, dist, ipDisplay) => {
const location = formatLocation(region);
const timeGreet = getTimeGreeting();
const cityGreet = getGreeting(region);

return `
欢迎来自 <b>${location}</b> 的小友💖<br>
你当前距博主约 <b>${dist}</b> 公里!<br>
你的IP地址:<b class="ip-address">${ipDisplay}</b><br>
${timeGreet}<br>
Tip:<b>${cityGreet}🍂</b>
`;
};

// 显示欢迎信息
const showWelcome = (data) => {
if (!data || !data.ip) return showErrorMessage();

const welcomeInfo = getWelcomeInfoElement();
if (!welcomeInfo) return;

const dist = calculateDistance(data.longitude, data.latitude);
const ipDisplay = formatIpDisplay(data.ip);

welcomeInfo.style.display = 'block';
welcomeInfo.style.height = 'auto';
welcomeInfo.innerHTML = generateWelcomeMessage(data.region, dist, ipDisplay);
};

// 从uapis.cn获取IP信息
const fetchIpData = async () => {
const response = await fetch('https://uapis.cn/api/v1/network/myip?source=commercial');
if (!response.ok) throw new Error('API请求失败');
const result = await response.json();
return result;
};

// 获取IP信息主函数
const fetchIpInfo = async () => {
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('获取IP信息失败,请重试');
}
};

// 判断是否首页
const isHomePage = () => {
return window.location.pathname === '/' || window.location.pathname === '/index.html';
};

// 插入组件
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();
};

// 初始化
document.addEventListener('DOMContentLoaded', () => {
addStyles();
insertAnnouncementComponent();
document.addEventListener('pjax:complete', insertAnnouncementComponent);
});

插入 JS 文件

_config.anzhiyu.yml 主题配置文件下 inject配置项中的 bottom 引入 welcome.js

1
- <script src="/js/welcome.js"></script>

图片内容仅供参考,具体根据实际情况修改。

截屏

配置内容

确认 _config.anzhiyu.yml 主题配置文件下 aside 配置项中的 card_announcement 是否开启,配置参考:

1
2
3
4
card_announcement:
enable: true # 必须开启
content: ···(省略)
<div id="welcome-info"></div> # 必须项

截屏


注意事项
  • 确保API密钥正确填写,正确设置白名单
  • 经纬度没必要填写详细,为了自身隐私安全
  • 选择器可以根据你的主题实际情况修改

常见问题

Q: 为什么显示”无法获取信息”?
A: 可能是 API 炸了,检查配置并重试。实在不行联系我更新文章。

Q: 距离显示不准确?
A: 正常现象,计算有无法避免的误差,在 10 公里以内就行了。