koishi-plugin-ets2-tools-tmp 2.5.91 → 2.6.0
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/tmpDlcMap.js +1 -1
- package/lib/command/tmpFootprint.js +1 -1
- package/lib/command/tmpMileageRanking.js +1 -1
- package/lib/command/tmpPosition.js +1 -1
- package/lib/command/tmpServer/tmpServer.d.ts +2 -0
- package/lib/command/tmpServer/tmpServer.js +15 -0
- package/lib/command/tmpServer/tmpServerImg.d.ts +3 -0
- package/lib/command/tmpServer/tmpServerImg.js +35 -0
- package/lib/command/tmpServer/tmpServerText.d.ts +2 -0
- package/lib/command/tmpServer/tmpServerText.js +33 -0
- package/lib/command/tmpTraffic/tmpTrafficMap.js +1 -1
- package/lib/index.js +8 -1
- package/lib/resource/dlc.html +1 -1
- package/lib/resource/footprint.html +2 -2
- package/lib/resource/position.html +1 -1
- package/lib/resource/server-list.html +298 -0
- package/lib/resource/traffic.html +1 -1
- package/package.json +1 -1
package/lib/command/tmpDlcMap.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = async (ctx, session) => {
|
|
|
11
11
|
let page;
|
|
12
12
|
try {
|
|
13
13
|
page = await ctx.puppeteer.page();
|
|
14
|
-
await page.setViewport({ width: 1000, height: 1000 });
|
|
14
|
+
await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 });
|
|
15
15
|
await page.goto(`file:///${resolve(__dirname, '../resource/dlc.html')}`);
|
|
16
16
|
await page.evaluate(`setData(${JSON.stringify(dlcData.data)})`);
|
|
17
17
|
await page.waitForNetworkIdle();
|
|
@@ -122,7 +122,7 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
122
122
|
let page
|
|
123
123
|
try {
|
|
124
124
|
page = await ctx.puppeteer.page()
|
|
125
|
-
await page.setViewport({ width: 1000, height: 1000 })
|
|
125
|
+
await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 })
|
|
126
126
|
await page.goto(`file:///${resolve(__dirname, '../resource/footprint.html')}`)
|
|
127
127
|
await page.evaluate(`init(${JSON.stringify(data)})`)
|
|
128
128
|
await common.sleep(100)
|
|
@@ -33,7 +33,7 @@ module.exports = async (ctx, session, rankingType) => {
|
|
|
33
33
|
let page;
|
|
34
34
|
try {
|
|
35
35
|
page = await ctx.puppeteer.page();
|
|
36
|
-
await page.setViewport({ width: 1000, height: 1000 });
|
|
36
|
+
await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 });
|
|
37
37
|
await page.goto(`file:///${resolve(__dirname, '../resource/mileage-leaderboard.html')}`);
|
|
38
38
|
await page.evaluate(`setData(${JSON.stringify(data)})`);
|
|
39
39
|
await page.waitForNetworkIdle();
|
|
@@ -98,7 +98,7 @@ module.exports = async (ctx, cfg, session, tmpId) => {
|
|
|
98
98
|
let page
|
|
99
99
|
try {
|
|
100
100
|
page = await ctx.puppeteer.page()
|
|
101
|
-
await page.setViewport({ width: 1000, height: 1000 })
|
|
101
|
+
await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 })
|
|
102
102
|
await page.goto(`file:///${resolve(__dirname, '../resource/position.html')}`)
|
|
103
103
|
await page.evaluate(`setData(${JSON.stringify(data)})`)
|
|
104
104
|
await common.sleep(100)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const tmpServerText = require("./tmpServerText");
|
|
2
|
+
const tmpServerImg = require("./tmpServerImg");
|
|
3
|
+
/**
|
|
4
|
+
* 查询服务器列表
|
|
5
|
+
*/
|
|
6
|
+
module.exports = async (ctx, cfg) => {
|
|
7
|
+
switch (cfg.tmpServer?.type) {
|
|
8
|
+
case 1:
|
|
9
|
+
return await tmpServerText(ctx);
|
|
10
|
+
case 2:
|
|
11
|
+
return await tmpServerImg(ctx);
|
|
12
|
+
default:
|
|
13
|
+
return '指令配置错误';
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const evmOpenApi = require('../../api/evmOpenApi');
|
|
2
|
+
const { resolve } = require("path");
|
|
3
|
+
const common = require("../../util/common");
|
|
4
|
+
const { segment } = require("koishi");
|
|
5
|
+
module.exports = async (ctx) => {
|
|
6
|
+
if (!ctx.puppeteer) {
|
|
7
|
+
return '未启用 puppeteer 服务';
|
|
8
|
+
}
|
|
9
|
+
// 查询服务器信息
|
|
10
|
+
let serverData = await evmOpenApi.serverList(ctx.http);
|
|
11
|
+
if (serverData.error) {
|
|
12
|
+
return '查询服务器失败,请稍后重试';
|
|
13
|
+
}
|
|
14
|
+
let page;
|
|
15
|
+
try {
|
|
16
|
+
page = await ctx.puppeteer.page();
|
|
17
|
+
await page.setViewport({ width: 380, height: 1000, deviceScaleFactor: 1.5 });
|
|
18
|
+
await page.goto(`file:///${resolve(__dirname, '../../resource/server-list.html')}`);
|
|
19
|
+
await page.evaluate(`setData(${JSON.stringify(serverData)})`);
|
|
20
|
+
await common.sleep(100);
|
|
21
|
+
await page.waitForNetworkIdle();
|
|
22
|
+
const element = await page.$("#container");
|
|
23
|
+
return (segment.image(await element.screenshot({
|
|
24
|
+
encoding: "binary"
|
|
25
|
+
}), "image/jpg"));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '渲染异常,请重试';
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
if (page) {
|
|
32
|
+
await page.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const evmOpenApi = require('../../api/evmOpenApi');
|
|
2
|
+
module.exports = async (ctx) => {
|
|
3
|
+
// 查询服务器信息
|
|
4
|
+
let serverData = await evmOpenApi.serverList(ctx.http);
|
|
5
|
+
if (serverData.error) {
|
|
6
|
+
return '查询服务器失败,请稍后重试';
|
|
7
|
+
}
|
|
8
|
+
// 构建消息
|
|
9
|
+
let message = '';
|
|
10
|
+
for (let server of serverData.data) {
|
|
11
|
+
// 如果前面有内容,换行
|
|
12
|
+
if (message) {
|
|
13
|
+
message += '\n\n';
|
|
14
|
+
}
|
|
15
|
+
message += '服务器: ' + (server.isOnline === 1 ? '🟢' : '⚫') + server.serverName;
|
|
16
|
+
message += `\n玩家人数: ${server.playerCount}/${server.maxPlayer}`;
|
|
17
|
+
if (server.queue) {
|
|
18
|
+
message += ` (队列: ${server.queueCount})`;
|
|
19
|
+
}
|
|
20
|
+
// 服务器特性
|
|
21
|
+
let characteristicList = [];
|
|
22
|
+
if (!(server.afkEnable === 1)) {
|
|
23
|
+
characteristicList.push('⏱挂机');
|
|
24
|
+
}
|
|
25
|
+
if (server.collisionsEnable === 1) {
|
|
26
|
+
characteristicList.push('💥碰撞');
|
|
27
|
+
}
|
|
28
|
+
if (characteristicList && characteristicList.length > 0) {
|
|
29
|
+
message += '\n服务器特性: ' + characteristicList.join(' ');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return message;
|
|
33
|
+
};
|
|
@@ -102,7 +102,7 @@ module.exports = async (ctx, cfg, serverName) => {
|
|
|
102
102
|
let page;
|
|
103
103
|
try {
|
|
104
104
|
page = await ctx.puppeteer.page();
|
|
105
|
-
await page.setViewport({ width: 1000, height: 1000 });
|
|
105
|
+
await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 });
|
|
106
106
|
await page.goto(`file:///${resolve(__dirname, '../../resource/traffic.html')}`);
|
|
107
107
|
await page.evaluate(`setData(${JSON.stringify(data)})`);
|
|
108
108
|
await common.sleep(100);
|
package/lib/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const { MileageRankingType } = require('./util/constant');
|
|
|
10
10
|
|
|
11
11
|
const commands = {
|
|
12
12
|
tmpQuery: require('./command/tmpQuery/tmpQuery'),
|
|
13
|
-
tmpServer: require('./command/tmpServer'),
|
|
13
|
+
tmpServer: require('./command/tmpServer/tmpServer'),
|
|
14
14
|
tmpBind: require('./command/tmpBind'),
|
|
15
15
|
tmpTraffic: require('./command/tmpTraffic/tmpTraffic'),
|
|
16
16
|
tmpPosition: require('./command/tmpPosition'),
|
|
@@ -101,6 +101,13 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
101
101
|
koishi_1.Schema.const(2).description('热力图')
|
|
102
102
|
]).default(1).description('路况信息展示方式'),
|
|
103
103
|
}).description('路况查询配置'),
|
|
104
|
+
tmpServer: koishi_1.Schema.object({
|
|
105
|
+
type: koishi_1.Schema.union([
|
|
106
|
+
koishi_1.Schema.const(1).description('文字'),
|
|
107
|
+
koishi_1.Schema.const(2).description('图片')
|
|
108
|
+
]).default(1).description('服务器信息展示方式'),
|
|
109
|
+
}).description('服务器查询配置'),
|
|
110
|
+
|
|
104
111
|
tmpVersionCheck: koishi_1.Schema.object({
|
|
105
112
|
checkInterval: koishi_1.Schema.number().description("版本检查间隔(分钟)").default(30),
|
|
106
113
|
groups: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("接收版本更新通知的群组ID列表").default([])
|
package/lib/resource/dlc.html
CHANGED
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
|
|
151
151
|
let dist = calculateDistance(prev, curr);
|
|
152
152
|
dist = dist * 19;
|
|
153
|
-
const isDistJump = dist >
|
|
153
|
+
const isDistJump = dist > 30000; // > 30km
|
|
154
154
|
|
|
155
155
|
let timeDiff = 0;
|
|
156
156
|
try {
|
|
@@ -238,4 +238,4 @@
|
|
|
238
238
|
}
|
|
239
239
|
</script>
|
|
240
240
|
</body>
|
|
241
|
-
</html>
|
|
241
|
+
</html>
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Server List</title>
|
|
6
|
+
<style>
|
|
7
|
+
* {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
}
|
|
12
|
+
body {
|
|
13
|
+
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
|
14
|
+
background-color: #0b1120;
|
|
15
|
+
}
|
|
16
|
+
#container {
|
|
17
|
+
width: 380px;
|
|
18
|
+
background: linear-gradient(180deg, #0f1a2e 0%, #0b1120 100%);
|
|
19
|
+
padding: 0 0 6px 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Header */
|
|
23
|
+
.header {
|
|
24
|
+
height: 42px;
|
|
25
|
+
background-color: rgba(0, 0, 0, 0.3);
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
|
|
30
|
+
}
|
|
31
|
+
.header .title {
|
|
32
|
+
color: #8bafff;
|
|
33
|
+
font-size: 14px;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
letter-spacing: 1px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Server list */
|
|
39
|
+
.server-list {
|
|
40
|
+
padding: 6px 10px 0 10px;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 5px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Server card */
|
|
47
|
+
.server-card {
|
|
48
|
+
background: rgba(22, 33, 55, 0.8);
|
|
49
|
+
border-radius: 6px;
|
|
50
|
+
border: 1px solid rgba(139, 175, 255, 0.08);
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Card info */
|
|
55
|
+
.server-info {
|
|
56
|
+
padding: 7px 12px 5px 12px;
|
|
57
|
+
}
|
|
58
|
+
.server-row {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
}
|
|
63
|
+
.server-left {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 6px;
|
|
67
|
+
flex: 1;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
}
|
|
70
|
+
.status-indicator {
|
|
71
|
+
width: 6px;
|
|
72
|
+
height: 6px;
|
|
73
|
+
border-radius: 50%;
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
}
|
|
76
|
+
.status-indicator.online {
|
|
77
|
+
background-color: #34d058;
|
|
78
|
+
box-shadow: 0 0 5px rgba(52, 208, 88, 0.5);
|
|
79
|
+
}
|
|
80
|
+
.status-indicator.offline {
|
|
81
|
+
background-color: #484f58;
|
|
82
|
+
}
|
|
83
|
+
.server-name {
|
|
84
|
+
color: #e6edf3;
|
|
85
|
+
font-size: 13px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
text-overflow: ellipsis;
|
|
90
|
+
max-width: 200px;
|
|
91
|
+
}
|
|
92
|
+
.player-count {
|
|
93
|
+
color: #6e7a8a;
|
|
94
|
+
font-size: 11px;
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
margin-left: 6px;
|
|
98
|
+
}
|
|
99
|
+
.player-count .num {
|
|
100
|
+
color: #c9d1d9;
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Features row */
|
|
106
|
+
.features {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 8px;
|
|
110
|
+
margin-top: 3px;
|
|
111
|
+
}
|
|
112
|
+
.feature {
|
|
113
|
+
font-size: 10px;
|
|
114
|
+
color: #5a6572;
|
|
115
|
+
white-space: nowrap;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Queue badge */
|
|
119
|
+
.queue-badge {
|
|
120
|
+
color: #f0883e;
|
|
121
|
+
font-size: 10px;
|
|
122
|
+
margin-left: 6px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Chart area */
|
|
126
|
+
.chart-area {
|
|
127
|
+
position: relative;
|
|
128
|
+
height: 38px;
|
|
129
|
+
}
|
|
130
|
+
.chart-area svg {
|
|
131
|
+
display: block;
|
|
132
|
+
width: 100%;
|
|
133
|
+
height: 100%;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
</style>
|
|
137
|
+
</head>
|
|
138
|
+
<body>
|
|
139
|
+
<div id="container">
|
|
140
|
+
<div class="header">
|
|
141
|
+
<div class="title">TruckersMP 服务器状态</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div class="server-list" id="server-list"></div>
|
|
145
|
+
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<script>
|
|
149
|
+
/**
|
|
150
|
+
* 构建 SVG 面积折线图
|
|
151
|
+
* maxPlayer 的 50% 作为余量,上限不超过 maxPlayer
|
|
152
|
+
*/
|
|
153
|
+
function buildChartSVG(data, width, height, maxPlayer) {
|
|
154
|
+
const padTop = 2;
|
|
155
|
+
const padBottom = 0;
|
|
156
|
+
const chartW = width;
|
|
157
|
+
const chartH = height - padTop - padBottom;
|
|
158
|
+
|
|
159
|
+
const dataMax = Math.max(...data, 1);
|
|
160
|
+
const max = Math.min(maxPlayer, maxPlayer * 0.12 + dataMax);
|
|
161
|
+
const step = chartW / (data.length - 1);
|
|
162
|
+
|
|
163
|
+
const points = data.map((v, i) => {
|
|
164
|
+
const x = i * step;
|
|
165
|
+
const y = padTop + chartH - (v / max) * chartH;
|
|
166
|
+
return [x, y];
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
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`;
|
|
173
|
+
|
|
174
|
+
const gid = 'g' + Math.random().toString(36).slice(2, 8);
|
|
175
|
+
|
|
176
|
+
return `
|
|
177
|
+
<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
|
178
|
+
<defs>
|
|
179
|
+
<linearGradient id="${gid}" x1="0" y1="0" x2="0" y2="1">
|
|
180
|
+
<stop offset="0%" stop-color="rgba(79,139,255,0.4)"/>
|
|
181
|
+
<stop offset="100%" stop-color="rgba(79,139,255,0.02)"/>
|
|
182
|
+
</linearGradient>
|
|
183
|
+
</defs>
|
|
184
|
+
<path d="${areaD}" fill="url(#${gid})"/>
|
|
185
|
+
<path d="${lineD}" fill="none" stroke="#4f8bff" stroke-width="1.5" stroke-linejoin="round"/>
|
|
186
|
+
</svg>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 将 playerHistory 标准化为 288 个点(24h,每5分钟一个点)
|
|
191
|
+
* 缺失的时间段填充为 0
|
|
192
|
+
* @param {Array<{updateTime: string, playerCount: number}>} playerHistory
|
|
193
|
+
*/
|
|
194
|
+
function normalizeHistory(playerHistory) {
|
|
195
|
+
const SLOT_COUNT = 288;
|
|
196
|
+
const SLOT_MS = 300000;
|
|
197
|
+
|
|
198
|
+
if (!playerHistory || playerHistory.length === 0) return [];
|
|
199
|
+
|
|
200
|
+
// API 返回的是北京时间(UTC+8),用 Date.UTC 生成时间戳再减去8小时偏移
|
|
201
|
+
function parseTime(str) {
|
|
202
|
+
const [datePart, timePart] = str.trim().replace('T', ' ').split(' ');
|
|
203
|
+
const [y, m, d] = datePart.split('-').map(Number);
|
|
204
|
+
const parts = timePart.split(':').map(Number);
|
|
205
|
+
const h = parts[0] || 0, min = parts[1] || 0;
|
|
206
|
+
return Date.UTC(y, m - 1, d, h, min, 0) - 8 * 3600000;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const currentSlot = Math.floor(now / SLOT_MS) * SLOT_MS;
|
|
211
|
+
|
|
212
|
+
const dataMap = {};
|
|
213
|
+
for (const item of playerHistory) {
|
|
214
|
+
const ts = parseTime(item.updateTime);
|
|
215
|
+
if (isNaN(ts)) continue;
|
|
216
|
+
const slot = Math.floor(ts / SLOT_MS) * SLOT_MS;
|
|
217
|
+
dataMap[slot] = item.playerCount;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = [];
|
|
221
|
+
for (let i = 0; i < SLOT_COUNT; i++) {
|
|
222
|
+
const slotTime = currentSlot - (SLOT_COUNT - 1 - i) * SLOT_MS;
|
|
223
|
+
result.push(dataMap[slotTime] !== undefined ? dataMap[slotTime] : 0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 渲染一个服务器卡片
|
|
231
|
+
*/
|
|
232
|
+
function createServerCard(server) {
|
|
233
|
+
const card = document.createElement('div');
|
|
234
|
+
card.className = 'server-card';
|
|
235
|
+
|
|
236
|
+
// 只展示存在的特性
|
|
237
|
+
let featuresHTML = '';
|
|
238
|
+
if (server.collisionsEnable === 1) {
|
|
239
|
+
featuresHTML += '<span class="feature">💥 碰撞</span>';
|
|
240
|
+
}
|
|
241
|
+
if (server.afkEnable === 0) {
|
|
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
|
+
}
|
|
250
|
+
|
|
251
|
+
// 队列
|
|
252
|
+
let queueText = '';
|
|
253
|
+
if (server.queueCount > 0) {
|
|
254
|
+
queueText = `<span class="queue-badge">队列 ${server.queueCount}</span>`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
card.innerHTML = `
|
|
258
|
+
<div class="server-info">
|
|
259
|
+
<div class="server-row">
|
|
260
|
+
<div class="server-left">
|
|
261
|
+
<span class="status-indicator ${server.isOnline === 1 ? 'online' : 'offline'}"></span>
|
|
262
|
+
<span class="server-name">${server.serverName}</span>
|
|
263
|
+
</div>
|
|
264
|
+
<span class="player-count"><span class="num">${server.playerCount}</span> / ${server.maxPlayer}${queueText}</span>
|
|
265
|
+
</div>
|
|
266
|
+
${featuresHTML ? '<div class="features">' + featuresHTML + '</div>' : ''}
|
|
267
|
+
</div>
|
|
268
|
+
<div class="chart-area">
|
|
269
|
+
${server.isOnline === 1 && server.playerHistory && server.playerHistory.length > 0
|
|
270
|
+
? (() => {
|
|
271
|
+
const normalized = normalizeHistory(server.playerHistory);
|
|
272
|
+
return normalized.length > 0 ? buildChartSVG(normalized, 380, 38, server.maxPlayer) : '';
|
|
273
|
+
})()
|
|
274
|
+
: ''}
|
|
275
|
+
</div>`;
|
|
276
|
+
|
|
277
|
+
return card;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 主渲染函数
|
|
282
|
+
*/
|
|
283
|
+
function setData(apiData) {
|
|
284
|
+
if (!apiData || !apiData.data) return;
|
|
285
|
+
|
|
286
|
+
const servers = apiData.data;
|
|
287
|
+
const listEl = document.getElementById('server-list');
|
|
288
|
+
|
|
289
|
+
listEl.innerHTML = '';
|
|
290
|
+
|
|
291
|
+
servers.forEach(server => {
|
|
292
|
+
listEl.appendChild(createServerCard(server));
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
</script>
|
|
297
|
+
</body>
|
|
298
|
+
</html>
|