Skip to content

Commit 12d6aa5

Browse files
committed
feat: 添加两个新工具 - 二维码解码器: 上传二维码图片并解析内容,支持拖拽上传 - IP地址信息查询: 查询IP地址的详细信息(地理位置/ISP/时区等)
1 parent ca7fee3 commit 12d6aa5

5 files changed

Lines changed: 1310 additions & 0 deletions

File tree

index.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,6 +3077,56 @@
30773077
"updatedAt": "2026-01-02",
30783078
"featured": true,
30793079
"repo": "https://github.com/justhtmls/html-tools"
3080+
},
3081+
{
3082+
"id": "qr-decoder",
3083+
"name": "二维码解码器",
3084+
"slug": "qr-decoder",
3085+
"category": "utility",
3086+
"tags": [
3087+
"qrcode",
3088+
"decoder",
3089+
"scanner",
3090+
"utility"
3091+
],
3092+
"author": "JustHTMLs",
3093+
"authorUrl": "https://github.com/justhtmls",
3094+
"version": "1.0.0",
3095+
"description": "上传二维码图片,快速解析其中的内容",
3096+
"longDescription": "二维码解码器可以将二维码图片解析为文本内容,支持上传本地图片或拖拽图片,快速提取二维码中的信息,如网址、文本、联系方式等。",
3097+
"icon": "🔍",
3098+
"color": "#8b5cf6",
3099+
"entry": "tools/qr-decoder/app.html",
3100+
"detail": "tools/qr-decoder/index.html",
3101+
"createdAt": "2026-01-02",
3102+
"updatedAt": "2026-01-02",
3103+
"featured": false,
3104+
"repo": "https://github.com/justhtmls/html-tools"
3105+
},
3106+
{
3107+
"id": "ip-info",
3108+
"name": "IP 地址信息查询",
3109+
"slug": "ip-info",
3110+
"category": "developer",
3111+
"tags": [
3112+
"ip",
3113+
"network",
3114+
"geolocation",
3115+
"utility"
3116+
],
3117+
"author": "JustHTMLs",
3118+
"authorUrl": "https://github.com/justhtmls",
3119+
"version": "1.0.0",
3120+
"description": "快速查询 IP 地址的地理位置、ISP 等信息",
3121+
"longDescription": "IP 地址信息查询工具可以快速查询 IP 地址的详细信息,包括地理位置、时区、ISP、货币等信息。支持查询本机 IP 和指定 IP 地址。",
3122+
"icon": "🌐",
3123+
"color": "#06b6d4",
3124+
"entry": "tools/ip-info/app.html",
3125+
"detail": "tools/ip-info/index.html",
3126+
"createdAt": "2026-01-02",
3127+
"updatedAt": "2026-01-02",
3128+
"featured": false,
3129+
"repo": "https://github.com/justhtmls/html-tools"
30803130
}
30813131
],
30823132
"categories": [

tools/ip-info/app.html

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
<head>
4+
<link rel="icon" href="../../favicon.svg">
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>IP 地址信息查询</title>
8+
<link rel="canonical" href="https://www.htmls.dev/tools/ip-info/app.html">
9+
<style>
10+
* {
11+
margin: 0;
12+
padding: 0;
13+
box-sizing: border-box;
14+
}
15+
16+
body {
17+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
19+
color: #eee;
20+
min-height: 100vh;
21+
padding: 20px;
22+
}
23+
24+
.container {
25+
max-width: 900px;
26+
margin: 0 auto;
27+
}
28+
29+
.header {
30+
text-align: center;
31+
margin-bottom: 30px;
32+
}
33+
34+
.header h1 {
35+
font-size: 2rem;
36+
background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);
37+
-webkit-background-clip: text;
38+
-webkit-text-fill-color: transparent;
39+
background-clip: text;
40+
}
41+
42+
.card {
43+
background: rgba(30, 30, 50, 0.8);
44+
border-radius: 12px;
45+
padding: 24px;
46+
border: 1px solid rgba(255, 255, 255, 0.1);
47+
margin-bottom: 20px;
48+
}
49+
50+
.input-group {
51+
margin-bottom: 20px;
52+
}
53+
54+
.input-group label {
55+
display: block;
56+
margin-bottom: 8px;
57+
color: #888;
58+
font-size: 14px;
59+
}
60+
61+
.input-wrapper {
62+
display: flex;
63+
gap: 12px;
64+
}
65+
66+
.input-wrapper input {
67+
flex: 1;
68+
padding: 12px 16px;
69+
background: rgba(0, 0, 0, 0.3);
70+
border: 1px solid rgba(255, 255, 255, 0.1);
71+
border-radius: 8px;
72+
color: #fff;
73+
font-size: 15px;
74+
}
75+
76+
.input-wrapper input:focus {
77+
outline: none;
78+
border-color: #06b6d4;
79+
}
80+
81+
.btn {
82+
padding: 12px 24px;
83+
border: none;
84+
border-radius: 8px;
85+
font-size: 14px;
86+
font-weight: 600;
87+
cursor: pointer;
88+
transition: all 0.2s;
89+
background: linear-gradient(135deg, #06b6d4, #3b82f6);
90+
color: white;
91+
}
92+
93+
.btn:hover {
94+
transform: translateY(-2px);
95+
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.4);
96+
}
97+
98+
.btn:disabled {
99+
opacity: 0.5;
100+
cursor: not-allowed;
101+
}
102+
103+
.info-grid {
104+
display: grid;
105+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
106+
gap: 16px;
107+
margin-top: 20px;
108+
}
109+
110+
.info-item {
111+
background: rgba(0, 0, 0, 0.3);
112+
padding: 16px;
113+
border-radius: 8px;
114+
border: 1px solid rgba(255, 255, 255, 0.1);
115+
}
116+
117+
.info-label {
118+
color: #888;
119+
font-size: 12px;
120+
margin-bottom: 8px;
121+
}
122+
123+
.info-value {
124+
color: #fff;
125+
font-size: 16px;
126+
font-weight: 600;
127+
word-break: break-all;
128+
}
129+
130+
.loading {
131+
text-align: center;
132+
padding: 40px;
133+
color: #888;
134+
}
135+
136+
.error-msg {
137+
color: #ef4444;
138+
background: rgba(239, 68, 68, 0.1);
139+
padding: 12px;
140+
border-radius: 6px;
141+
margin-top: 16px;
142+
display: none;
143+
}
144+
145+
.error-msg.active {
146+
display: block;
147+
}
148+
149+
.tool-doc-link,
150+
.tool-home-link {
151+
position: fixed;
152+
top: 16px;
153+
padding: 6px 12px;
154+
border-radius: 999px;
155+
font-size: 12px;
156+
font-weight: 600;
157+
color: #0f172a;
158+
background: rgba(255, 255, 255, 0.9);
159+
border: 1px solid rgba(15, 23, 42, 0.12);
160+
text-decoration: none;
161+
letter-spacing: 0.02em;
162+
z-index: 9999;
163+
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
164+
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
165+
}
166+
167+
.tool-doc-link {
168+
right: 16px;
169+
}
170+
171+
.tool-home-link {
172+
right: 74px;
173+
}
174+
175+
.tool-doc-link:hover,
176+
.tool-home-link:hover {
177+
background: rgba(255, 255, 255, 1);
178+
border-color: rgba(15, 23, 42, 0.2);
179+
transform: translateY(-1px);
180+
}
181+
</style>
182+
</head>
183+
<body>
184+
<a class="tool-home-link" href="../../index.html">首页</a>
185+
<a class="tool-doc-link" href="index.html">说明</a>
186+
<div class="container">
187+
<div class="header">
188+
<h1>IP 地址信息查询</h1>
189+
</div>
190+
191+
<div class="card">
192+
<div class="input-group">
193+
<label>查询 IP 地址(留空查询本机 IP):</label>
194+
<div class="input-wrapper">
195+
<input type="text" id="ip-input" placeholder="例如: 8.8.8.8">
196+
<button class="btn" onclick="queryIP()" id="query-btn">查询</button>
197+
</div>
198+
<button class="btn" onclick="getMyIP()" style="margin-top: 12px; width: 100%;">查询本机 IP</button>
199+
</div>
200+
<div class="error-msg" id="error-msg"></div>
201+
</div>
202+
203+
<div class="card" id="result-card" style="display: none;">
204+
<div class="info-grid" id="info-grid"></div>
205+
</div>
206+
207+
<div class="loading" id="loading" style="display: none;">
208+
<i class="fas fa-spinner fa-spin"></i> 正在查询...
209+
</div>
210+
</div>
211+
212+
<script>
213+
function queryIP() {
214+
const ipInput = document.getElementById('ip-input').value.trim();
215+
const ip = ipInput || '';
216+
fetchIPInfo(ip);
217+
}
218+
219+
function getMyIP() {
220+
document.getElementById('ip-input').value = '';
221+
fetchIPInfo('');
222+
}
223+
224+
function fetchIPInfo(ip) {
225+
const loading = document.getElementById('loading');
226+
const resultCard = document.getElementById('result-card');
227+
const errorMsg = document.getElementById('error-msg');
228+
const infoGrid = document.getElementById('info-grid');
229+
const queryBtn = document.getElementById('query-btn');
230+
231+
loading.style.display = 'block';
232+
resultCard.style.display = 'none';
233+
errorMsg.classList.remove('active');
234+
queryBtn.disabled = true;
235+
236+
const url = ip ? `https://ipapi.co/${ip}/json/` : 'https://ipapi.co/json/';
237+
238+
fetch(url)
239+
.then(response => {
240+
if (!response.ok) {
241+
throw new Error('查询失败');
242+
}
243+
return response.json();
244+
})
245+
.then(data => {
246+
if (data.error) {
247+
throw new Error(data.reason || '查询失败');
248+
}
249+
displayIPInfo(data, ip);
250+
})
251+
.catch(error => {
252+
showError(error.message || '查询失败,请检查网络连接或 IP 地址格式');
253+
})
254+
.finally(() => {
255+
loading.style.display = 'none';
256+
queryBtn.disabled = false;
257+
});
258+
}
259+
260+
function displayIPInfo(data, queriedIP) {
261+
const infoGrid = document.getElementById('info-grid');
262+
const resultCard = document.getElementById('result-card');
263+
264+
const infoItems = [
265+
{ label: 'IP 地址', value: data.ip || queriedIP || '未知' },
266+
{ label: '版本', value: data.version || '未知' },
267+
{ label: '城市', value: data.city || '未知' },
268+
{ label: '地区', value: data.region || '未知' },
269+
{ label: '国家', value: (data.country_name || '未知') + ' (' + (data.country_code || '') + ')' },
270+
{ label: '时区', value: data.timezone || '未知' },
271+
{ label: '货币', value: (data.currency || '未知') + ' (' + (data.currency_name || '') + ')' },
272+
{ label: '邮政编码', value: data.postal || '未知' },
273+
{ label: '纬度', value: data.latitude || '未知' },
274+
{ label: '经度', value: data.longitude || '未知' },
275+
{ label: 'ISP', value: data.org || data.asn || '未知' },
276+
{ label: '语言', value: (data.languages || '未知').split(',')[0] }
277+
].filter(item => item.value && item.value !== '未知');
278+
279+
infoGrid.innerHTML = infoItems.map(item => `
280+
<div class="info-item">
281+
<div class="info-label">${item.label}</div>
282+
<div class="info-value">${item.value}</div>
283+
</div>
284+
`).join('');
285+
286+
resultCard.style.display = 'block';
287+
}
288+
289+
function showError(message) {
290+
const errorMsg = document.getElementById('error-msg');
291+
errorMsg.textContent = message;
292+
errorMsg.classList.add('active');
293+
}
294+
295+
// 页面加载时自动查询本机 IP
296+
window.addEventListener('load', () => {
297+
getMyIP();
298+
});
299+
</script>
300+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" onerror="this.onerror=null;this.href='../../assets/vendor/fontawesome/css/all.min.css';">
301+
<script src="../../assets/clicks.js" defer></script>
302+
</body>
303+
</html>

0 commit comments

Comments
 (0)