koishi-plugin-ets2-tools-tmp 3.1.1 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/command/tmpFootprint.js +1 -1
- package/lib/command/tmpQuery/tmpQueryImg.js +1 -0
- package/lib/index.js +1 -1
- package/lib/resource/dlc.html +150 -42
- package/lib/resource/mileage-leaderboard.html +448 -247
- package/lib/resource/query.html +20 -7
- package/lib/resource/server-list.html +264 -112
- package/lib/resource/traffic.html +246 -116
- package/package.json +1 -1
package/lib/resource/query.html
CHANGED
|
@@ -81,6 +81,11 @@
|
|
|
81
81
|
.avatar-wrapper {
|
|
82
82
|
position: relative;
|
|
83
83
|
flex-shrink: 0;
|
|
84
|
+
display: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.avatar-wrapper.show {
|
|
88
|
+
display: block;
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
.avatar {
|
|
@@ -244,16 +249,21 @@
|
|
|
244
249
|
color: #94a3b8;
|
|
245
250
|
display: flex;
|
|
246
251
|
align-items: center;
|
|
247
|
-
gap:
|
|
248
|
-
flex-shrink:
|
|
252
|
+
gap: 4px;
|
|
253
|
+
flex-shrink: 1;
|
|
254
|
+
white-space: nowrap;
|
|
255
|
+
min-width: 0;
|
|
249
256
|
}
|
|
250
257
|
|
|
251
258
|
.info-value {
|
|
252
259
|
font-size: 13px;
|
|
253
260
|
color: #e2e8f0;
|
|
254
261
|
text-align: right;
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
overflow: hidden;
|
|
264
|
+
text-overflow: ellipsis;
|
|
265
|
+
max-width: 70%;
|
|
266
|
+
margin-left: 8px;
|
|
257
267
|
}
|
|
258
268
|
|
|
259
269
|
.info-value.gold {
|
|
@@ -478,11 +488,11 @@
|
|
|
478
488
|
<div class="section-title">🚚 车队信息</div>
|
|
479
489
|
<div class="info-row">
|
|
480
490
|
<div class="info-label">🚚 所属车队</div>
|
|
481
|
-
<div class="info-value gold" id="tmp-vtc-name"></div>
|
|
491
|
+
<div class="info-value gold" id="tmp-vtc-name" style="white-space:normal;overflow:visible;text-overflow:unset;max-width:75%;"></div>
|
|
482
492
|
</div>
|
|
483
493
|
<div class="info-row">
|
|
484
494
|
<div class="info-label">🚚 车队角色</div>
|
|
485
|
-
<div class="info-value" id="tmp-vtc-role"></div>
|
|
495
|
+
<div class="info-value" id="tmp-vtc-role" style="white-space:normal;overflow:visible;text-overflow:unset;max-width:75%;"></div>
|
|
486
496
|
</div>
|
|
487
497
|
<div id="vtc-history-container"></div>
|
|
488
498
|
<div class="info-row" id="reward-points-row" style="display:none;">
|
|
@@ -606,7 +616,10 @@
|
|
|
606
616
|
|
|
607
617
|
function init(data) {
|
|
608
618
|
// Header
|
|
609
|
-
|
|
619
|
+
if (data.showAvatar) {
|
|
620
|
+
document.querySelector('.avatar-wrapper').classList.add('show');
|
|
621
|
+
document.getElementById('tmp-avatar').src = data.avatarUrl;
|
|
622
|
+
}
|
|
610
623
|
document.getElementById('tmp-name').innerText = data.name;
|
|
611
624
|
document.getElementById('tmp-id').innerText = 'TMP#' + data.tmpId;
|
|
612
625
|
document.getElementById('group-badge').innerText = data.groupName;
|
|
@@ -4,158 +4,330 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<title>Server List</title>
|
|
6
6
|
<style>
|
|
7
|
-
* {
|
|
8
|
-
|
|
9
|
-
padding: 0;
|
|
10
|
-
box-sizing: border-box;
|
|
11
|
-
}
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
|
|
12
9
|
body {
|
|
13
10
|
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
|
14
|
-
background
|
|
11
|
+
background: #080810;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
15
13
|
}
|
|
14
|
+
|
|
16
15
|
#container {
|
|
17
|
-
width:
|
|
18
|
-
background:
|
|
19
|
-
padding:
|
|
16
|
+
width: 480px;
|
|
17
|
+
background: #0a0a12;
|
|
18
|
+
padding-bottom: 12px;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
position: relative;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Grid background */
|
|
24
|
+
.grid-bg {
|
|
25
|
+
position: absolute;
|
|
26
|
+
inset: 0;
|
|
27
|
+
background-image:
|
|
28
|
+
linear-gradient(rgba(0, 200, 255, 0.03) 1px, transparent 1px),
|
|
29
|
+
linear-gradient(90deg, rgba(0, 200, 255, 0.03) 1px, transparent 1px);
|
|
30
|
+
background-size: 24px 24px;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
z-index: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Scan line effect */
|
|
36
|
+
.scan-line {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 0;
|
|
39
|
+
left: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
height: 2px;
|
|
42
|
+
background: linear-gradient(90deg, transparent, rgba(0, 200, 255, 0.15), transparent);
|
|
43
|
+
animation: scan 4s linear infinite;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
z-index: 1;
|
|
20
46
|
}
|
|
21
47
|
|
|
22
|
-
|
|
48
|
+
@keyframes scan {
|
|
49
|
+
0% { top: 0; opacity: 0; }
|
|
50
|
+
10% { opacity: 1; }
|
|
51
|
+
90% { opacity: 1; }
|
|
52
|
+
100% { top: 100%; opacity: 0; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ===== HEADER ===== */
|
|
23
56
|
.header {
|
|
24
|
-
|
|
25
|
-
background-color: rgba(0, 0, 0, 0.3);
|
|
57
|
+
padding: 20px 20px 14px;
|
|
26
58
|
display: flex;
|
|
27
59
|
align-items: center;
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
gap: 10px;
|
|
61
|
+
position: relative;
|
|
62
|
+
z-index: 2;
|
|
63
|
+
background: linear-gradient(180deg, rgba(0, 200, 255, 0.06) 0%, transparent 100%);
|
|
64
|
+
border-bottom: 1px solid rgba(0, 200, 255, 0.08);
|
|
30
65
|
}
|
|
66
|
+
|
|
67
|
+
.header-icon {
|
|
68
|
+
font-size: 22px;
|
|
69
|
+
filter: drop-shadow(0 0 6px rgba(0, 200, 255, 0.5));
|
|
70
|
+
}
|
|
71
|
+
|
|
31
72
|
.header .title {
|
|
32
|
-
|
|
33
|
-
font-
|
|
34
|
-
|
|
73
|
+
font-size: 16px;
|
|
74
|
+
font-weight: 800;
|
|
75
|
+
color: #e0f0ff;
|
|
76
|
+
letter-spacing: 2px;
|
|
77
|
+
text-transform: uppercase;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.header .title span {
|
|
81
|
+
color: #00c8ff;
|
|
82
|
+
text-shadow: 0 0 10px rgba(0, 200, 255, 0.4);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.header-stats {
|
|
86
|
+
margin-left: auto;
|
|
87
|
+
display: flex;
|
|
88
|
+
gap: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.header-stat {
|
|
92
|
+
text-align: right;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header-stat-value {
|
|
96
|
+
font-size: 16px;
|
|
97
|
+
font-weight: 800;
|
|
98
|
+
color: #00c8ff;
|
|
99
|
+
text-shadow: 0 0 8px rgba(0, 200, 255, 0.3);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.header-stat-label {
|
|
103
|
+
font-size: 9px;
|
|
104
|
+
color: #445;
|
|
35
105
|
letter-spacing: 1px;
|
|
106
|
+
text-transform: uppercase;
|
|
36
107
|
}
|
|
37
108
|
|
|
38
|
-
/*
|
|
109
|
+
/* ===== SERVER LIST ===== */
|
|
39
110
|
.server-list {
|
|
40
|
-
padding:
|
|
111
|
+
padding: 10px 14px 0;
|
|
41
112
|
display: flex;
|
|
42
113
|
flex-direction: column;
|
|
43
|
-
gap:
|
|
114
|
+
gap: 6px;
|
|
115
|
+
position: relative;
|
|
116
|
+
z-index: 2;
|
|
44
117
|
}
|
|
45
118
|
|
|
46
|
-
/*
|
|
119
|
+
/* ===== SERVER CARD ===== */
|
|
47
120
|
.server-card {
|
|
48
|
-
background: rgba(
|
|
49
|
-
border
|
|
50
|
-
border:
|
|
121
|
+
background: linear-gradient(135deg, rgba(0, 200, 255, 0.04) 0%, rgba(0, 200, 255, 0.01) 100%);
|
|
122
|
+
border: 1px solid rgba(0, 200, 255, 0.1);
|
|
123
|
+
border-radius: 10px;
|
|
51
124
|
overflow: hidden;
|
|
125
|
+
position: relative;
|
|
126
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(0, 200, 255, 0.05);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.server-card::before {
|
|
130
|
+
content: '';
|
|
131
|
+
position: absolute;
|
|
132
|
+
top: 0;
|
|
133
|
+
left: 0;
|
|
134
|
+
right: 0;
|
|
135
|
+
height: 1px;
|
|
136
|
+
background: linear-gradient(90deg, transparent 10%, rgba(0, 200, 255, 0.15) 50%, transparent 90%);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.server-card.offline {
|
|
140
|
+
opacity: 0.4;
|
|
141
|
+
border-color: rgba(255, 255, 255, 0.04);
|
|
142
|
+
background: rgba(255, 255, 255, 0.01);
|
|
52
143
|
}
|
|
53
144
|
|
|
54
|
-
/* Card info */
|
|
55
145
|
.server-info {
|
|
56
|
-
padding:
|
|
146
|
+
padding: 10px 14px 6px;
|
|
57
147
|
}
|
|
148
|
+
|
|
58
149
|
.server-row {
|
|
59
150
|
display: flex;
|
|
60
151
|
align-items: center;
|
|
61
152
|
justify-content: space-between;
|
|
62
153
|
}
|
|
154
|
+
|
|
63
155
|
.server-left {
|
|
64
156
|
display: flex;
|
|
65
157
|
align-items: center;
|
|
66
|
-
gap:
|
|
158
|
+
gap: 8px;
|
|
67
159
|
flex: 1;
|
|
68
160
|
min-width: 0;
|
|
69
161
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
162
|
+
|
|
163
|
+
.status-dot {
|
|
164
|
+
width: 8px;
|
|
165
|
+
height: 8px;
|
|
73
166
|
border-radius: 50%;
|
|
74
167
|
flex-shrink: 0;
|
|
168
|
+
position: relative;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.status-dot.online {
|
|
172
|
+
background: #00ff88;
|
|
173
|
+
box-shadow: 0 0 6px rgba(0, 255, 136, 0.6), 0 0 12px rgba(0, 255, 136, 0.2);
|
|
75
174
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
175
|
+
|
|
176
|
+
.status-dot.online::after {
|
|
177
|
+
content: '';
|
|
178
|
+
position: absolute;
|
|
179
|
+
inset: -3px;
|
|
180
|
+
border-radius: 50%;
|
|
181
|
+
border: 1px solid rgba(0, 255, 136, 0.2);
|
|
182
|
+
animation: pulse 2s ease-out infinite;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@keyframes pulse {
|
|
186
|
+
0% { transform: scale(0.8); opacity: 1; }
|
|
187
|
+
100% { transform: scale(1.8); opacity: 0; }
|
|
79
188
|
}
|
|
80
|
-
|
|
81
|
-
|
|
189
|
+
|
|
190
|
+
.status-dot.offline {
|
|
191
|
+
background: #333;
|
|
82
192
|
}
|
|
193
|
+
|
|
83
194
|
.server-name {
|
|
84
|
-
color: #
|
|
195
|
+
color: #d0e8ff;
|
|
85
196
|
font-size: 13px;
|
|
86
|
-
font-weight:
|
|
197
|
+
font-weight: 700;
|
|
87
198
|
white-space: nowrap;
|
|
88
199
|
overflow: hidden;
|
|
89
200
|
text-overflow: ellipsis;
|
|
90
|
-
|
|
201
|
+
letter-spacing: 0.3px;
|
|
91
202
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
203
|
+
|
|
204
|
+
.player-info {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: baseline;
|
|
207
|
+
gap: 4px;
|
|
96
208
|
flex-shrink: 0;
|
|
97
|
-
margin-left:
|
|
209
|
+
margin-left: 10px;
|
|
98
210
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
font-
|
|
102
|
-
font-
|
|
211
|
+
|
|
212
|
+
.player-num {
|
|
213
|
+
font-size: 16px;
|
|
214
|
+
font-weight: 900;
|
|
215
|
+
color: #00c8ff;
|
|
216
|
+
text-shadow: 0 0 8px rgba(0, 200, 255, 0.3);
|
|
217
|
+
font-variant-numeric: tabular-nums;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.player-max {
|
|
221
|
+
font-size: 11px;
|
|
222
|
+
color: #445;
|
|
223
|
+
font-weight: 500;
|
|
103
224
|
}
|
|
104
225
|
|
|
105
|
-
/*
|
|
226
|
+
/* Progress bar */
|
|
227
|
+
.server-bar {
|
|
228
|
+
height: 2px;
|
|
229
|
+
background: rgba(0, 200, 255, 0.06);
|
|
230
|
+
border-radius: 1px;
|
|
231
|
+
margin-top: 6px;
|
|
232
|
+
overflow: hidden;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.server-bar-fill {
|
|
236
|
+
height: 100%;
|
|
237
|
+
border-radius: 1px;
|
|
238
|
+
background: linear-gradient(90deg, #00c8ff, #00ff88);
|
|
239
|
+
box-shadow: 0 0 6px rgba(0, 200, 255, 0.3);
|
|
240
|
+
transition: width 0.3s;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Features */
|
|
106
244
|
.features {
|
|
107
245
|
display: flex;
|
|
108
246
|
align-items: center;
|
|
109
247
|
gap: 8px;
|
|
110
|
-
margin-top:
|
|
248
|
+
margin-top: 5px;
|
|
111
249
|
}
|
|
250
|
+
|
|
112
251
|
.feature {
|
|
113
|
-
font-size:
|
|
114
|
-
color: #
|
|
252
|
+
font-size: 9px;
|
|
253
|
+
color: #3a4a5a;
|
|
254
|
+
padding: 1px 6px;
|
|
255
|
+
border-radius: 4px;
|
|
256
|
+
border: 1px solid rgba(255, 255, 255, 0.04);
|
|
257
|
+
background: rgba(255, 255, 255, 0.02);
|
|
115
258
|
white-space: nowrap;
|
|
259
|
+
letter-spacing: 0.3px;
|
|
116
260
|
}
|
|
117
261
|
|
|
118
|
-
/* Queue badge */
|
|
119
262
|
.queue-badge {
|
|
120
|
-
color: #f0883e;
|
|
121
263
|
font-size: 10px;
|
|
264
|
+
color: #ff8844;
|
|
122
265
|
margin-left: 6px;
|
|
266
|
+
font-weight: 600;
|
|
123
267
|
}
|
|
124
268
|
|
|
125
|
-
/* Chart
|
|
269
|
+
/* Chart */
|
|
126
270
|
.chart-area {
|
|
127
271
|
position: relative;
|
|
128
|
-
height:
|
|
272
|
+
height: 36px;
|
|
273
|
+
opacity: 0.8;
|
|
129
274
|
}
|
|
275
|
+
|
|
130
276
|
.chart-area svg {
|
|
131
277
|
display: block;
|
|
132
278
|
width: 100%;
|
|
133
279
|
height: 100%;
|
|
134
280
|
}
|
|
135
281
|
|
|
282
|
+
/* ===== FOOTER ===== */
|
|
283
|
+
.footer {
|
|
284
|
+
text-align: center;
|
|
285
|
+
padding: 10px 20px 0;
|
|
286
|
+
position: relative;
|
|
287
|
+
z-index: 2;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.footer-line {
|
|
291
|
+
height: 1px;
|
|
292
|
+
background: linear-gradient(90deg, transparent, rgba(0, 200, 255, 0.15), transparent);
|
|
293
|
+
margin-bottom: 8px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.footer-text {
|
|
297
|
+
font-size: 9px;
|
|
298
|
+
color: #223;
|
|
299
|
+
letter-spacing: 1px;
|
|
300
|
+
}
|
|
136
301
|
</style>
|
|
137
302
|
</head>
|
|
138
303
|
<body>
|
|
139
304
|
<div id="container">
|
|
305
|
+
<div class="grid-bg"></div>
|
|
306
|
+
<div class="scan-line"></div>
|
|
307
|
+
|
|
140
308
|
<div class="header">
|
|
141
|
-
<
|
|
309
|
+
<span class="header-icon">📡</span>
|
|
310
|
+
<div class="title">TRUCKERS<span>MP</span> 服务器</div>
|
|
311
|
+
<div class="header-stats">
|
|
312
|
+
<div class="header-stat">
|
|
313
|
+
<div class="header-stat-value" id="total-online">-</div>
|
|
314
|
+
<div class="header-stat-label">在线</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
142
317
|
</div>
|
|
143
318
|
|
|
144
319
|
<div class="server-list" id="server-list"></div>
|
|
145
320
|
|
|
321
|
+
<div class="footer">
|
|
322
|
+
<div class="footer-line"></div>
|
|
323
|
+
<div class="footer-text">REAL-TIME SERVER MONITOR</div>
|
|
324
|
+
</div>
|
|
146
325
|
</div>
|
|
147
326
|
|
|
148
327
|
<script>
|
|
149
|
-
/**
|
|
150
|
-
* 构建 SVG 面积折线图
|
|
151
|
-
* maxPlayer 的 50% 作为余量,上限不超过 maxPlayer
|
|
152
|
-
*/
|
|
153
328
|
function buildChartSVG(data, width, height, maxPlayer) {
|
|
154
|
-
const padTop = 2;
|
|
155
|
-
const
|
|
156
|
-
const chartW = width;
|
|
157
|
-
const chartH = height - padTop - padBottom;
|
|
158
|
-
|
|
329
|
+
const padTop = 2, padBottom = 0;
|
|
330
|
+
const chartW = width, chartH = height - padTop - padBottom;
|
|
159
331
|
const dataMax = Math.max(...data, 1);
|
|
160
332
|
const max = Math.min(maxPlayer, maxPlayer * 0.12 + dataMax);
|
|
161
333
|
const step = chartW / (data.length - 1);
|
|
@@ -167,9 +339,7 @@ function buildChartSVG(data, width, height, maxPlayer) {
|
|
|
167
339
|
});
|
|
168
340
|
|
|
169
341
|
const lineD = points.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' ');
|
|
170
|
-
const areaD = lineD
|
|
171
|
-
+ ` L${points[points.length - 1][0]},${padTop + chartH}`
|
|
172
|
-
+ ` L${points[0][0]},${padTop + chartH} Z`;
|
|
342
|
+
const areaD = lineD + ` L${points[points.length - 1][0]},${padTop + chartH} L${points[0][0]},${padTop + chartH} Z`;
|
|
173
343
|
|
|
174
344
|
const gid = 'g' + Math.random().toString(36).slice(2, 8);
|
|
175
345
|
|
|
@@ -177,27 +347,19 @@ function buildChartSVG(data, width, height, maxPlayer) {
|
|
|
177
347
|
<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
|
178
348
|
<defs>
|
|
179
349
|
<linearGradient id="${gid}" x1="0" y1="0" x2="0" y2="1">
|
|
180
|
-
<stop offset="0%" stop-color="rgba(
|
|
181
|
-
<stop offset="100%" stop-color="rgba(
|
|
350
|
+
<stop offset="0%" stop-color="rgba(0,200,255,0.3)"/>
|
|
351
|
+
<stop offset="100%" stop-color="rgba(0,200,255,0.01)"/>
|
|
182
352
|
</linearGradient>
|
|
183
353
|
</defs>
|
|
184
354
|
<path d="${areaD}" fill="url(#${gid})"/>
|
|
185
|
-
<path d="${lineD}" fill="none" stroke="#
|
|
355
|
+
<path d="${lineD}" fill="none" stroke="#00c8ff" stroke-width="1.5" stroke-linejoin="round"/>
|
|
186
356
|
</svg>`;
|
|
187
357
|
}
|
|
188
358
|
|
|
189
|
-
/**
|
|
190
|
-
* 将 playerHistory 标准化为 288 个点(24h,每5分钟一个点)
|
|
191
|
-
* 缺失的时间段填充为 0
|
|
192
|
-
* @param {Array<{updateTime: string, playerCount: number}>} playerHistory
|
|
193
|
-
*/
|
|
194
359
|
function normalizeHistory(playerHistory) {
|
|
195
|
-
const SLOT_COUNT = 288;
|
|
196
|
-
const SLOT_MS = 300000;
|
|
197
|
-
|
|
360
|
+
const SLOT_COUNT = 288, SLOT_MS = 300000;
|
|
198
361
|
if (!playerHistory || playerHistory.length === 0) return [];
|
|
199
362
|
|
|
200
|
-
// API 返回的是北京时间(UTC+8),用 Date.UTC 生成时间戳再减去8小时偏移
|
|
201
363
|
function parseTime(str) {
|
|
202
364
|
const [datePart, timePart] = str.trim().replace('T', ' ').split(' ');
|
|
203
365
|
const [y, m, d] = datePart.split('-').map(Number);
|
|
@@ -208,7 +370,6 @@ function normalizeHistory(playerHistory) {
|
|
|
208
370
|
|
|
209
371
|
const now = Date.now();
|
|
210
372
|
const currentSlot = Math.floor(now / SLOT_MS) * SLOT_MS;
|
|
211
|
-
|
|
212
373
|
const dataMap = {};
|
|
213
374
|
for (const item of playerHistory) {
|
|
214
375
|
const ts = parseTime(item.updateTime);
|
|
@@ -222,54 +383,46 @@ function normalizeHistory(playerHistory) {
|
|
|
222
383
|
const slotTime = currentSlot - (SLOT_COUNT - 1 - i) * SLOT_MS;
|
|
223
384
|
result.push(dataMap[slotTime] !== undefined ? dataMap[slotTime] : 0);
|
|
224
385
|
}
|
|
225
|
-
|
|
226
386
|
return result;
|
|
227
387
|
}
|
|
228
388
|
|
|
229
|
-
/**
|
|
230
|
-
* 渲染一个服务器卡片
|
|
231
|
-
*/
|
|
232
389
|
function createServerCard(server) {
|
|
233
390
|
const card = document.createElement('div');
|
|
234
|
-
card.className =
|
|
391
|
+
card.className = `server-card${server.isOnline !== 1 ? ' offline' : ''}`;
|
|
392
|
+
|
|
393
|
+
const isOnline = server.isOnline === 1;
|
|
394
|
+
const pct = maxP => Math.min(100, (server.playerCount / maxP) * 100);
|
|
235
395
|
|
|
236
|
-
// 只展示存在的特性
|
|
237
396
|
let featuresHTML = '';
|
|
238
|
-
if (server.collisionsEnable === 1)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (server.
|
|
242
|
-
featuresHTML += '<span class="feature">💤 挂机</span>';
|
|
243
|
-
}
|
|
244
|
-
if (server.policeCarEnable === 1) {
|
|
245
|
-
featuresHTML += '<span class="feature">🚓 警车</span>';
|
|
246
|
-
}
|
|
247
|
-
if (server.speedLimiterEnable === 1) {
|
|
248
|
-
featuresHTML += '<span class="feature">🐢 限速</span>';
|
|
249
|
-
}
|
|
397
|
+
if (server.collisionsEnable === 1) featuresHTML += '<span class="feature">💥 碰撞</span>';
|
|
398
|
+
if (server.afkEnable === 0) featuresHTML += '<span class="feature">💤 挂机</span>';
|
|
399
|
+
if (server.policeCarEnable === 1) featuresHTML += '<span class="feature">🚓 警车</span>';
|
|
400
|
+
if (server.speedLimiterEnable === 1) featuresHTML += '<span class="feature">🐢 限速</span>';
|
|
250
401
|
|
|
251
|
-
// 队列
|
|
252
402
|
let queueText = '';
|
|
253
|
-
if (server.queueCount > 0) {
|
|
254
|
-
queueText = `<span class="queue-badge">队列 ${server.queueCount}</span>`;
|
|
255
|
-
}
|
|
403
|
+
if (server.queueCount > 0) queueText = `<span class="queue-badge">⏳ ${server.queueCount}</span>`;
|
|
256
404
|
|
|
257
405
|
card.innerHTML = `
|
|
258
406
|
<div class="server-info">
|
|
259
407
|
<div class="server-row">
|
|
260
408
|
<div class="server-left">
|
|
261
|
-
<
|
|
409
|
+
<div class="status-dot ${isOnline ? 'online' : 'offline'}"></div>
|
|
262
410
|
<span class="server-name">${server.serverName}</span>
|
|
263
411
|
</div>
|
|
264
|
-
<
|
|
412
|
+
<div class="player-info">
|
|
413
|
+
<span class="player-num">${server.playerCount}</span>
|
|
414
|
+
<span class="player-max">/ ${server.maxPlayer}</span>
|
|
415
|
+
${queueText}
|
|
416
|
+
</div>
|
|
265
417
|
</div>
|
|
418
|
+
<div class="server-bar"><div class="server-bar-fill" style="width:${pct(server.maxPlayer)}%"></div></div>
|
|
266
419
|
${featuresHTML ? '<div class="features">' + featuresHTML + '</div>' : ''}
|
|
267
420
|
</div>
|
|
268
421
|
<div class="chart-area">
|
|
269
|
-
${
|
|
422
|
+
${isOnline && server.playerHistory && server.playerHistory.length > 0
|
|
270
423
|
? (() => {
|
|
271
424
|
const normalized = normalizeHistory(server.playerHistory);
|
|
272
|
-
return normalized.length > 0 ? buildChartSVG(normalized,
|
|
425
|
+
return normalized.length > 0 ? buildChartSVG(normalized, 480, 36, server.maxPlayer) : '';
|
|
273
426
|
})()
|
|
274
427
|
: ''}
|
|
275
428
|
</div>`;
|
|
@@ -277,22 +430,21 @@ function createServerCard(server) {
|
|
|
277
430
|
return card;
|
|
278
431
|
}
|
|
279
432
|
|
|
280
|
-
/**
|
|
281
|
-
* 主渲染函数
|
|
282
|
-
*/
|
|
283
433
|
function setData(apiData) {
|
|
284
434
|
if (!apiData || !apiData.data) return;
|
|
285
435
|
|
|
286
436
|
const servers = apiData.data;
|
|
287
437
|
const listEl = document.getElementById('server-list');
|
|
288
|
-
|
|
289
438
|
listEl.innerHTML = '';
|
|
290
439
|
|
|
440
|
+
// Total online count
|
|
441
|
+
const totalOnline = servers.reduce((sum, s) => sum + (s.playerCount || 0), 0);
|
|
442
|
+
document.getElementById('total-online').textContent = totalOnline.toLocaleString();
|
|
443
|
+
|
|
291
444
|
servers.forEach(server => {
|
|
292
445
|
listEl.appendChild(createServerCard(server));
|
|
293
446
|
});
|
|
294
447
|
}
|
|
295
|
-
|
|
296
448
|
</script>
|
|
297
449
|
</body>
|
|
298
450
|
</html>
|