最后活跃于 2 months ago

友链状态页面

Joe

修订 62929613142f84d0ef2e4de41f72064cda84561f

php 原始文件
1<?php
2
3/**
4 * 友链状态
5 *
6 * @package custom
7 *
8 **/
9
10if (!defined('__TYPECHO_ROOT_DIR__')) {
11 http_response_code(404);
12 exit;
13}
14$this->need('module/single/pjax.php');
15?>
16
17<!DOCTYPE html>
18<html lang="zh">
19 <head>
20 <?php $this->need('module/head.php') ?>
21 <meta charset="UTF-8" />
22 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
23 <meta name="robots" content="noindex, nofollow" />
24 <meta name="googlebot" content="noindex" />
25 <link
26 href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.min.css"
27 rel="stylesheet"
28 />
29 <style>
30 :root {
31 --primary-color: #2c3e50;
32 --secondary-color: #3498db;
33 --accent-color: #e74c3c;
34 --text-color: #34495e;
35 --background-color: #fff;
36 --code-background-color: #efefef;
37 }
38
39 body.dark {
40 --primary-color: #a0c1e6;
41 --secondary-color: #5dade2;
42 --accent-color: #e74c3c;
43 --text-color: #e0e0e0;
44 --background-color: #1a1a2e;
45 --code-background-color: #2d2d44;
46 }
47
48 .joe_container {
49 max-width: 1200px;
50 margin: 0 auto;
51 padding: 0 15px;
52 }
53
54 .joe_main {
55 background-color: var(--background-color);
56 padding: 20px;
57 border-radius: 8px;
58 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
59 margin-bottom: 20px;
60 }
61
62 .flink-status-container {
63 font-family: "KingHwa_OldSong", "Noto Serif SC", serif;
64 padding: 40px 20px;
65 background-color: var(--background-color);
66 color: var(--text-color);
67 display: flex;
68 flex-direction: column;
69 align-items: center;
70 }
71
72 .container {
73 max-width: 900px;
74 width: 100%;
75 padding: 0 20px;
76 }
77
78 .header {
79 text-align: center;
80 margin-bottom: 40px;
81 position: relative;
82 }
83
84 h1 {
85 font-size: 2.8rem;
86 color: var(--primary-color);
87 margin: 20px 0;
88 position: relative;
89 }
90
91 h1::after {
92 content: "";
93 display: block;
94 width: 60%;
95 height: 3px;
96 background: var(--secondary-color);
97 margin: 10px auto 0;
98 }
99
100 .subtitle {
101 font-size: 1.1rem;
102 color: #7f8c8d;
103 max-width: 600px;
104 margin: 0 auto;
105 line-height: 1.6;
106 }
107
108 .total-info {
109 display: grid;
110 grid-template-columns: repeat(2, 1fr);
111 gap: 15px;
112 margin-top: 30px;
113 }
114
115 @media (min-width: 1200px) {
116 .total-info {
117 grid-template-columns: repeat(4, 1fr);
118 }
119 }
120
121 @media (max-width: 380px) {
122 .total-info {
123 grid-template-columns: repeat(1, 1fr);
124 }
125 }
126
127 .total-status-card {
128 background: var(--background-color);
129 border: 1px solid rgba(0, 0, 0, 0.1);
130 border-radius: 8px;
131 padding: 15px;
132 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
133 text-align: center;
134 font-size: 1rem;
135 display: flex;
136 justify-content: space-between;
137 align-items: center;
138 transition: all 0.2s ease-in-out;
139 }
140
141 .dark .total-status-card {
142 border-color: rgba(255, 255, 255, 0.1);
143 }
144
145 .total-status-card span {
146 font-weight: bold;
147 white-space: nowrap;
148 overflow: hidden;
149 text-overflow: ellipsis;
150 }
151
152 .total-status-card:hover {
153 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
154 transform: translateY(-3px);
155 }
156
157 .status-grid {
158 display: grid;
159 gap: 15px;
160 margin-top: 15px;
161 }
162
163 .status-grid.other {
164 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
165 }
166
167 .status-grid.error {
168 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
169 }
170
171 .fail_counts {
172 margin-right: 10px;
173 color: #000000a5;
174 }
175
176 .dark .fail_counts {
177 color: #ffffffa5;
178 }
179
180 .status-card {
181 display: flex;
182 background: var(--background-color);
183 border: 1px solid rgba(0, 0, 0, 0.1);
184 border-radius: 8px;
185 padding: 15px;
186 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
187 text-align: center;
188 font-size: 1rem;
189 justify-content: space-between;
190 align-items: center;
191 transition: all 0.2s ease-in-out;
192 }
193
194 .dark .status-card {
195 border-color: rgba(255, 255, 255, 0.1);
196 }
197
198 .status-card:hover {
199 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
200 transform: translateY(-3px);
201 }
202
203 .status-link {
204 color: var(--primary-color);
205 text-decoration: none;
206 font-weight: bold;
207 transition: all 0.2s ease-in-out;
208 white-space: nowrap;
209 overflow: hidden;
210 text-overflow: ellipsis;
211 }
212
213 .status-link:hover {
214 color: var(--secondary-color);
215 }
216
217 .status-dot {
218 display: inline-block;
219 width: 12px;
220 height: 12px;
221 border-radius: 50%;
222 margin-right: 6px;
223 position: relative;
224 }
225
226 .status-dot::before {
227 content: "";
228 position: absolute;
229 top: -4px;
230 left: -4px;
231 width: 20px;
232 height: 20px;
233 border-radius: 50%;
234 animation: pulse 1.5s infinite;
235 }
236
237 @keyframes pulse {
238 0% {
239 transform: scale(1);
240 opacity: 1;
241 }
242 50% {
243 transform: scale(1.5);
244 opacity: 0.5;
245 }
246 100% {
247 transform: scale(1);
248 opacity: 1;
249 }
250 }
251
252 .status-normal {
253 background: green;
254 }
255
256 .status-normal::before {
257 background-color: rgba(0, 128, 0, 0.2);
258 }
259
260 .status-slow {
261 background: rgb(255, 200, 0);
262 }
263
264 .status-slow::before {
265 background-color: rgba(255, 174, 0, 0.2);
266 }
267
268 .status-error {
269 background: red;
270 }
271
272 .status-error::before {
273 background-color: rgba(255, 0, 0, 0.2);
274 }
275
276 footer {
277 margin-top: 50px;
278 padding-top: 30px;
279 border-top: 1px solid #ecf0f1;
280 width: 100%;
281 text-align: center;
282 color: #95a5a6;
283 }
284
285 .dark footer {
286 border-top: 1px solid rgba(255, 255, 255, 0.1);
287 }
288
289 footer a {
290 color: #95a5a6;
291 text-decoration: none;
292 transition: all 0.2s ease-in-out;
293 }
294
295 footer a:hover {
296 color: var(--secondary-color);
297 }
298 </style>
299 </head>
300
301 <body>
302 <?php $this->need('module/header.php') ?>
303 <div class="joe_container">
304 <div class="joe_main">
305 <div class="flink-status-container">
306 <div class="container">
307 <header class="header">
308 <h1>友链状态监控</h1>
309 <p class="subtitle">更新时间:<span id="update-time"></span></p>
310 </header>
311 <section class="total-info" id="total-info">
312 <div class="total-status-card">
313 <div class="total-status-card-title">
314 <i class="fa-solid fa-users"></i>
315 <span>友链总数</span>
316 </div>
317 <span id="total-links">加载中...</span>
318 </div>
319 <div class="total-status-card">
320 <div class="total-status-card-title">
321 <i class="fa-solid fa-circle-check"></i>
322 <span>正常友链</span>
323 </div>
324 <span id="normal-links">加载中...</span>
325 </div>
326 <div class="total-status-card">
327 <div class="total-status-card-title">
328 <i class="fa-solid fa-circle-exclamation"></i>
329 <span>慢速友链</span>
330 </div>
331 <span id="slow-links">加载中...</span>
332 </div>
333 <div class="total-status-card">
334 <div class="total-status-card-title">
335 <i class="fa-solid fa-circle-xmark"></i>
336 <span>错误友链</span>
337 </div>
338 <span id="error-links">加载中...</span>
339 </div>
340 </section>
341 <hr
342 style="
343 width: 100%;
344 margin: 15px 0;
345 border: 0;
346 border-top: 2px solid #00000021;
347 "
348 />
349 <h2 class="error-link-h2">失效友链</h2>
350 <section class="status-grid error" id="status-container-error"></section>
351 <hr
352 class="error-link-hr"
353 style="
354 width: 100%;
355 margin: 15px 0;
356 border: 0;
357 border-top: 2px solid #00000021;
358 "
359 />
360 <h2 class="other-link-h2">其他友链</h2>
361 <section class="status-grid other" id="status-container-other"></section>
362 <footer>
363 <script defer src="https://cn.vercount.one/js"></script>
364 <div style="margin-bottom: 0.5em">
365 网站总请求量:<span id="vercount_value_site_pv">🤕</span> |
366 独立访客数:<span id="vercount_value_site_uv">🤕</span>
367 </div>
368 </footer>
369 </div>
370 </div>
371 <?php $this->need('module/single/comment.php'); ?>
372 </div>
373 <?php joe\isPc() ? $this->need('module/aside.php') : null ?>
374 </div>
375 <script>
376 // 检测暗黑模式
377 function checkDarkMode() {
378 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
379 document.body.classList.add('dark');
380 }
381 }
382
383 // 初始检测
384 checkDarkMode();
385
386 // 监听暗黑模式变化
387 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
388 if (e.matches) {
389 document.body.classList.add('dark');
390 } else {
391 document.body.classList.remove('dark');
392 }
393 });
394
395 // 加载友链数据
396 fetch("https://你的实际部署域名/result.json")
397 .then((response) => response.json())
398 .then((data) => {
399 // 更新更新时间
400 const updateTime = data.timestamp;
401 const updateTimeElement = document.getElementById("update-time");
402 updateTimeElement.textContent = updateTime;
403 const container_error = document.getElementById(
404 "status-container-error"
405 );
406 const container_other = document.getElementById(
407 "status-container-other"
408 );
409 let statusHTML = "";
410 const sortedLinks = data.link_status.sort((a, b) =>
411 a.latency === -1 ? -1 : b.latency === -1 ? 1 : b.latency - a.latency
412 );
413 let totalLinks = 0;
414 let normalLinks = 0;
415 let slowLinks = 0;
416 let errorLinks = 0;
417 let errorLinksHTML = "";
418 let otherLinksHTML = "";
419 sortedLinks.forEach((link) => {
420 totalLinks++;
421 let statusClass = "status-normal";
422 let extraInfo = "";
423 if (link.latency === -1) {
424 statusClass = "status-error";
425 errorLinks++;
426 extraInfo = `<span class="fail_counts">已持续${link.fail_count}次</span>`;
427 errorLinksHTML += `<div class="status-card">
428 <a class="status-link" href="${link.link}" target="_blank" rel="noopener noreferrer noreferrer">${link.name}</a>
429 <div class="fail-details">
430 ${extraInfo}
431 <span class="status-dot ${statusClass}"></span>
432 </div>
433 </div>`;
434 } else if (link.latency > 4) {
435 statusClass = "status-slow";
436 slowLinks++;
437 otherLinksHTML += `<div class="status-card">
438 <a class="status-link" href="${link.link}" target="_blank" rel="noopener noreferrer noreferrer">${link.name}</a>
439 <span class="status-dot ${statusClass}"></span>
440 </div>`;
441 } else {
442 normalLinks++;
443 otherLinksHTML += `<div class="status-card">
444 <a class="status-link" href="${link.link}" target="_blank" rel="noopener noreferrer noreferrer">${link.name}</a>
445 <span class="status-dot ${statusClass}"></span>
446 </div>`;
447 }
448 });
449 if (errorLinksHTML) {
450 container_error.innerHTML = errorLinksHTML;
451 } else {
452 // 如果没有错误链接,移除标题和分割线
453 const errorTitle = document.querySelector(".error-link-h2");
454 const errorHr = document.querySelector(".error-link-hr");
455 if (errorTitle) errorTitle.remove();
456 if (errorHr) errorHr.remove();
457 }
458 if (otherLinksHTML) {
459 container_other.innerHTML = otherLinksHTML;
460 }
461 document.getElementById("total-links").textContent = totalLinks;
462 document.getElementById("normal-links").textContent = normalLinks;
463 document.getElementById("slow-links").textContent = slowLinks;
464 document.getElementById("error-links").textContent = errorLinks;
465 })
466 .catch((error) => console.error("Error loading status:", error));
467 </script>
468 </body>
469</html>
470<?php $this->need('module/footer.php') ?>