koishi-plugin-ets2-tools-tmp 3.1.5 → 3.1.7
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/api/evmOpenApi.js +28 -2
- package/lib/command/tmpServer/tmpServer.js +3 -3
- package/lib/command/tmpServer/tmpServerImg.js +3 -3
- package/lib/command/tmpServer/tmpServerText.js +6 -3
- package/lib/command/tmpVtcOnline/tmpVtcOnline.js +13 -0
- package/lib/command/tmpVtcOnline/tmpVtcOnlineImg.js +47 -0
- package/lib/command/tmpVtcOnline/tmpVtcOnlineText.js +22 -0
- package/lib/index.js +15 -0
- package/lib/resource/server-list.html +9 -1
- package/lib/resource/tmpVtcOnline.html +313 -0
- package/package.json +1 -1
package/lib/api/evmOpenApi.js
CHANGED
|
@@ -17,16 +17,42 @@ module.exports = {
|
|
|
17
17
|
/**
|
|
18
18
|
* 查询服务器列表
|
|
19
19
|
*/
|
|
20
|
-
async serverList(http) {
|
|
20
|
+
async serverList(http, game) {
|
|
21
21
|
let result = null
|
|
22
22
|
try {
|
|
23
|
-
result =
|
|
23
|
+
result = game === 'ATS'
|
|
24
|
+
? await http.get('https://api.114512.xyz/truckersmp/servers')
|
|
25
|
+
: await requestWithFallback(http, '/server/list')
|
|
24
26
|
} catch {
|
|
25
27
|
return {
|
|
26
28
|
error: true
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
if (game === 'ATS') {
|
|
33
|
+
const servers = result?.response
|
|
34
|
+
const isError = result?.error === true || !Array.isArray(servers)
|
|
35
|
+
return {
|
|
36
|
+
error: isError,
|
|
37
|
+
...(isError ? {} : {
|
|
38
|
+
data: servers
|
|
39
|
+
.filter(server => server.game === 'ATS')
|
|
40
|
+
.map(server => ({
|
|
41
|
+
serverName: server.name,
|
|
42
|
+
isOnline: server.online ? 1 : 0,
|
|
43
|
+
playerCount: server.players,
|
|
44
|
+
maxPlayer: server.maxplayers,
|
|
45
|
+
queueCount: server.queue || 0,
|
|
46
|
+
afkEnable: server.afkenabled ? 1 : 0,
|
|
47
|
+
collisionsEnable: server.collisions ? 1 : 0,
|
|
48
|
+
policeCarEnable: server.policecarsforplayers ? 1 : 0,
|
|
49
|
+
speedLimiterEnable: server.speedlimiter > 0 ? 1 : 0,
|
|
50
|
+
playerHistory: []
|
|
51
|
+
}))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
30
56
|
// 拼接返回数据
|
|
31
57
|
let data = {
|
|
32
58
|
error: result.code !== 200
|
|
@@ -3,12 +3,12 @@ const tmpServerImg = require("./tmpServerImg");
|
|
|
3
3
|
/**
|
|
4
4
|
* 查询服务器列表
|
|
5
5
|
*/
|
|
6
|
-
module.exports = async (ctx, cfg) => {
|
|
6
|
+
module.exports = async (ctx, cfg, game) => {
|
|
7
7
|
switch (cfg.tmpServer?.type) {
|
|
8
8
|
case 1:
|
|
9
|
-
return await tmpServerText(ctx);
|
|
9
|
+
return await tmpServerText(ctx, game);
|
|
10
10
|
case 2:
|
|
11
|
-
return await tmpServerImg(ctx);
|
|
11
|
+
return await tmpServerImg(ctx, game);
|
|
12
12
|
default:
|
|
13
13
|
return '指令配置错误';
|
|
14
14
|
}
|
|
@@ -2,12 +2,12 @@ const evmOpenApi = require('../../api/evmOpenApi');
|
|
|
2
2
|
const { resolve } = require("path");
|
|
3
3
|
const common = require("../../util/common");
|
|
4
4
|
const { segment } = require("koishi");
|
|
5
|
-
module.exports = async (ctx) => {
|
|
5
|
+
module.exports = async (ctx, game) => {
|
|
6
6
|
if (!ctx.puppeteer) {
|
|
7
7
|
return '未启用 puppeteer 服务';
|
|
8
8
|
}
|
|
9
9
|
// 查询服务器信息
|
|
10
|
-
let serverData = await evmOpenApi.serverList(ctx.http);
|
|
10
|
+
let serverData = await evmOpenApi.serverList(ctx.http, game);
|
|
11
11
|
if (serverData.error) {
|
|
12
12
|
return '查询服务器失败,请稍后重试';
|
|
13
13
|
}
|
|
@@ -16,7 +16,7 @@ module.exports = async (ctx) => {
|
|
|
16
16
|
page = await ctx.puppeteer.page();
|
|
17
17
|
await page.setViewport({ width: 380, height: 1000, deviceScaleFactor: 1.5 });
|
|
18
18
|
await page.goto(`file:///${resolve(__dirname, '../../resource/server-list.html')}`);
|
|
19
|
-
await page.evaluate(`setData(${JSON.stringify(serverData)})`);
|
|
19
|
+
await page.evaluate(`setData(${JSON.stringify({ ...serverData, game })})`);
|
|
20
20
|
await common.sleep(100);
|
|
21
21
|
await page.waitForNetworkIdle();
|
|
22
22
|
const element = await page.$("#container");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const evmOpenApi = require('../../api/evmOpenApi');
|
|
2
|
-
module.exports = async (ctx) => {
|
|
2
|
+
module.exports = async (ctx, game) => {
|
|
3
3
|
// 查询服务器信息
|
|
4
|
-
let serverData = await evmOpenApi.serverList(ctx.http);
|
|
4
|
+
let serverData = await evmOpenApi.serverList(ctx.http, game);
|
|
5
5
|
if (serverData.error) {
|
|
6
6
|
return '查询服务器失败,请稍后重试';
|
|
7
7
|
}
|
|
@@ -14,7 +14,7 @@ module.exports = async (ctx) => {
|
|
|
14
14
|
}
|
|
15
15
|
message += '服务器: ' + (server.isOnline === 1 ? '🟢' : '⚫') + server.serverName;
|
|
16
16
|
message += `\n玩家人数: ${server.playerCount}/${server.maxPlayer}`;
|
|
17
|
-
if (server.
|
|
17
|
+
if (server.queueCount > 0) {
|
|
18
18
|
message += ` (队列: ${server.queueCount})`;
|
|
19
19
|
}
|
|
20
20
|
// 服务器特性
|
|
@@ -25,6 +25,9 @@ module.exports = async (ctx) => {
|
|
|
25
25
|
if (server.collisionsEnable === 1) {
|
|
26
26
|
characteristicList.push('💥碰撞');
|
|
27
27
|
}
|
|
28
|
+
if (server.speedLimiterEnable === 1) {
|
|
29
|
+
characteristicList.push('🐢限速');
|
|
30
|
+
}
|
|
28
31
|
if (characteristicList && characteristicList.length > 0) {
|
|
29
32
|
message += '\n服务器特性: ' + characteristicList.join(' ');
|
|
30
33
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const tmpVtcOnlineText = require('./tmpVtcOnlineText');
|
|
2
|
+
const tmpVtcOnlineImg = require('./tmpVtcOnlineImg');
|
|
3
|
+
|
|
4
|
+
module.exports = async (ctx, cfg) => {
|
|
5
|
+
switch (cfg.tmpVtcOnline?.type) {
|
|
6
|
+
case 1:
|
|
7
|
+
return await tmpVtcOnlineText(ctx, cfg);
|
|
8
|
+
case 2:
|
|
9
|
+
return await tmpVtcOnlineImg(ctx, cfg);
|
|
10
|
+
default:
|
|
11
|
+
return '指令配置错误';
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { segment } = require('koishi');
|
|
2
|
+
const { resolve } = require('path');
|
|
3
|
+
const common = require('../../util/common');
|
|
4
|
+
|
|
5
|
+
module.exports = async (ctx, cfg) => {
|
|
6
|
+
const vtcId = cfg.tmpActivityService?.api?.vtcId;
|
|
7
|
+
if (!vtcId) {
|
|
8
|
+
return '请先在活动查询配置中填写 VTC ID';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let result;
|
|
12
|
+
try {
|
|
13
|
+
result = await ctx.http.get(`https://www.cnly.top/api/player_online/api?vtcId=${encodeURIComponent(vtcId)}`);
|
|
14
|
+
} catch {
|
|
15
|
+
return '查询车队在线成员失败,请稍后重试';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!result || result.code !== 200 || !Array.isArray(result.data)) {
|
|
19
|
+
return '查询车队在线成员失败,请稍后重试';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const members = result.data;
|
|
23
|
+
const onlineMembers = members.filter(item => item.isOnline);
|
|
24
|
+
|
|
25
|
+
const rows = [];
|
|
26
|
+
for (let i = 0; i < onlineMembers.length; i += 3) {
|
|
27
|
+
rows.push(onlineMembers.slice(i, i + 3));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let page;
|
|
31
|
+
try {
|
|
32
|
+
page = await ctx.puppeteer.page();
|
|
33
|
+
await page.setViewport({ width: 800, height: 1200, deviceScaleFactor: 1.5 });
|
|
34
|
+
await page.goto(`file:///${resolve(__dirname, '../../resource/tmpVtcOnline.html')}`);
|
|
35
|
+
await page.evaluate(`setData(${JSON.stringify({ members, onlineMembers, rows })})`);
|
|
36
|
+
await common.sleep(100);
|
|
37
|
+
await page.waitForNetworkIdle();
|
|
38
|
+
const element = await page.$('#container');
|
|
39
|
+
return segment.image(await element.screenshot({ encoding: 'binary' }), 'image/jpg');
|
|
40
|
+
} catch {
|
|
41
|
+
return '渲染异常,请重试';
|
|
42
|
+
} finally {
|
|
43
|
+
if (page) {
|
|
44
|
+
await page.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = async (ctx, cfg) => {
|
|
2
|
+
const vtcId = cfg.tmpActivityService?.api?.vtcId;
|
|
3
|
+
if (!vtcId) {
|
|
4
|
+
return '请先在活动查询配置中填写 VTC ID';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let result;
|
|
8
|
+
try {
|
|
9
|
+
result = await ctx.http.get(`https://www.cnly.top/api/player_online/api?vtcId=${encodeURIComponent(vtcId)}`);
|
|
10
|
+
} catch {
|
|
11
|
+
return '查询车队在线成员失败,请稍后重试';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!result || result.code !== 200 || !Array.isArray(result.data)) {
|
|
15
|
+
return '查询车队在线成员失败,请稍后重试';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const onlineMembers = result.data.filter(item => item.isOnline);
|
|
19
|
+
const lines = onlineMembers.map(item => `${item.name} - ${item.serverName || '未知服务器'}`);
|
|
20
|
+
|
|
21
|
+
return `车队在线成员(${onlineMembers.length}/${result.data.length})\n${lines.length ? lines.join('\n') : '当前没有成员在线'}`;
|
|
22
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const commands = {
|
|
|
23
23
|
addMember: require('./command/ets-app/addMember/addMemberV2'),
|
|
24
24
|
pointRanking: require('./command/ets-app/pointRanking/pointRanking'),
|
|
25
25
|
tmpVtc: require('./command/tmpVtc'),
|
|
26
|
+
tmpVtcOnline: require('./command/tmpVtcOnline/tmpVtcOnline'),
|
|
26
27
|
tmpFootprint: require('./command/tmpFootprint')
|
|
27
28
|
};
|
|
28
29
|
const { ActivityService } = require('./command/tmpActivityService');
|
|
@@ -78,6 +79,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
78
79
|
tmpMileageRanking: koishi_1.Schema.boolean().default(true).description('是否启用里程排行榜'),
|
|
79
80
|
pointRanking: koishi_1.Schema.boolean().default(false).description('是否启用积分排行榜'),
|
|
80
81
|
tmpVtc: koishi_1.Schema.boolean().default(true).description('是否启用VTC查询'),
|
|
82
|
+
tmpVtcOnline: koishi_1.Schema.boolean().default(true).description('是否启用车队在线成员查询功能'),
|
|
81
83
|
tmpFootprint: koishi_1.Schema.boolean().default(true).description('是否启用足迹查询'),
|
|
82
84
|
mainSettings: koishi_1.Schema.boolean().default(false).description('是否启用车队平台积分查询功能'),
|
|
83
85
|
resetPassword: koishi_1.Schema.boolean().default(false).description('是否启用车队平台重置密码功能'),
|
|
@@ -113,6 +115,12 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
113
115
|
koishi_1.Schema.const(2).description('图片')
|
|
114
116
|
]).default(1).description('服务器信息展示方式'),
|
|
115
117
|
}).description('服务器查询配置'),
|
|
118
|
+
tmpVtcOnline: koishi_1.Schema.object({
|
|
119
|
+
type: koishi_1.Schema.union([
|
|
120
|
+
koishi_1.Schema.const(1).description('文字'),
|
|
121
|
+
koishi_1.Schema.const(2).description('图片')
|
|
122
|
+
]).default(1).description('车队在线成员展示方式')
|
|
123
|
+
}).description('车队在线成员查询'),
|
|
116
124
|
|
|
117
125
|
tmpVersionCheck: koishi_1.Schema.object({
|
|
118
126
|
checkInterval: koishi_1.Schema.number().description("版本检查间隔(分钟)").default(30),
|
|
@@ -224,6 +232,7 @@ function logDisabledCommands(ctx, cfg) {
|
|
|
224
232
|
{ key: 'tmpMileageRanking', label: '里程排行榜/今日里程排行榜' },
|
|
225
233
|
{ key: 'pointRanking', label: '积分排行榜' },
|
|
226
234
|
{ key: 'tmpVtc', label: 'vtc查询' },
|
|
235
|
+
{ key: 'tmpVtcOnline', label: '车队在线成员' },
|
|
227
236
|
{ key: 'tmpFootprint', label: '足迹查询' },
|
|
228
237
|
{ key: 'resetPassword', label: '重置密码' },
|
|
229
238
|
{ key: 'mainSettings', label: '查询积分' },
|
|
@@ -309,6 +318,12 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
309
318
|
.action(async ({ session }, vtcid) => await commands.tmpVtc(ctx, cfg, session, vtcid));
|
|
310
319
|
}
|
|
311
320
|
|
|
321
|
+
if (cfg.commands?.tmpVtcOnline) {
|
|
322
|
+
ctx.command('车队在线成员')
|
|
323
|
+
.usage('查询车队在线成员列表')
|
|
324
|
+
.action(async () => await commands.tmpVtcOnline(ctx, cfg));
|
|
325
|
+
}
|
|
326
|
+
|
|
312
327
|
if (cfg.commands?.tmpFootprint) {
|
|
313
328
|
ctx.command('近七日足迹 [tmpId:string]')
|
|
314
329
|
.usage("查询ETS服务器今日足迹")
|
|
@@ -82,6 +82,13 @@
|
|
|
82
82
|
text-shadow: 0 0 10px rgba(0, 200, 255, 0.4);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
.header .title span.game-title {
|
|
86
|
+
margin-left: 4px;
|
|
87
|
+
color: #e0f0ff;
|
|
88
|
+
text-shadow: none;
|
|
89
|
+
letter-spacing: 1px;
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
.header-stats {
|
|
86
93
|
margin-left: auto;
|
|
87
94
|
display: flex;
|
|
@@ -307,7 +314,7 @@
|
|
|
307
314
|
|
|
308
315
|
<div class="header">
|
|
309
316
|
<span class="header-icon">📡</span>
|
|
310
|
-
<div class="title">TRUCKERS<span>MP</span>
|
|
317
|
+
<div class="title">TRUCKERS<span>MP</span> <span class="game-title" id="game-title">服务器</span></div>
|
|
311
318
|
<div class="header-stats">
|
|
312
319
|
<div class="header-stat">
|
|
313
320
|
<div class="header-stat-value" id="total-online">-</div>
|
|
@@ -436,6 +443,7 @@ function setData(apiData) {
|
|
|
436
443
|
const servers = apiData.data;
|
|
437
444
|
const listEl = document.getElementById('server-list');
|
|
438
445
|
listEl.innerHTML = '';
|
|
446
|
+
document.getElementById('game-title').textContent = apiData.game === 'ATS' ? '美卡服务器' : '欧卡服务器';
|
|
439
447
|
|
|
440
448
|
// Total online count
|
|
441
449
|
const totalOnline = servers.reduce((sum, s) => sum + (s.playerCount || 0), 0);
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>车队在线成员</title>
|
|
6
|
+
<style>
|
|
7
|
+
@font-face {
|
|
8
|
+
font-family: 'segui-emj';
|
|
9
|
+
src: url('./package/SEGUIEMJ.TTF');
|
|
10
|
+
font-weight: normal;
|
|
11
|
+
font-style: normal;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html,
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
font-family: 'segui-emj', 'Microsoft YaHei', 'Segoe UI', sans-serif;
|
|
26
|
+
color: #f8fbff;
|
|
27
|
+
background: #050711;
|
|
28
|
+
-webkit-font-smoothing: antialiased;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#container {
|
|
32
|
+
width: 760px;
|
|
33
|
+
min-height: 220px;
|
|
34
|
+
padding: 18px;
|
|
35
|
+
position: relative;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
background:
|
|
38
|
+
radial-gradient(circle at 8% -8%, rgba(34, 211, 238, 0.24), transparent 30%),
|
|
39
|
+
radial-gradient(circle at 100% 0%, rgba(129, 140, 248, 0.22), transparent 33%),
|
|
40
|
+
radial-gradient(circle at 86% 100%, rgba(217, 70, 239, 0.12), transparent 28%),
|
|
41
|
+
linear-gradient(145deg, #0b1020 0%, #070a16 48%, #0c1021 100%);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#container::before {
|
|
45
|
+
content: '';
|
|
46
|
+
position: absolute;
|
|
47
|
+
inset: 0;
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
opacity: 0.32;
|
|
50
|
+
background-image:
|
|
51
|
+
linear-gradient(rgba(148, 163, 184, 0.035) 1px, transparent 1px),
|
|
52
|
+
linear-gradient(90deg, rgba(148, 163, 184, 0.035) 1px, transparent 1px);
|
|
53
|
+
background-size: 26px 26px;
|
|
54
|
+
mask-image: linear-gradient(to bottom, black, transparent 85%);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#container::after {
|
|
58
|
+
content: '';
|
|
59
|
+
position: absolute;
|
|
60
|
+
top: -120px;
|
|
61
|
+
left: 24%;
|
|
62
|
+
width: 360px;
|
|
63
|
+
height: 180px;
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
background: rgba(56, 189, 248, 0.10);
|
|
66
|
+
filter: blur(70px);
|
|
67
|
+
transform: rotate(-8deg);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.content {
|
|
71
|
+
position: relative;
|
|
72
|
+
z-index: 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.header {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 14px;
|
|
79
|
+
padding: 17px 18px;
|
|
80
|
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
81
|
+
border-radius: 20px;
|
|
82
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.105), rgba(255, 255, 255, 0.035));
|
|
83
|
+
box-shadow:
|
|
84
|
+
0 18px 42px rgba(0, 0, 0, 0.30),
|
|
85
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
|
86
|
+
inset 0 -1px 0 rgba(0, 0, 0, 0.18);
|
|
87
|
+
backdrop-filter: blur(24px) saturate(1.25);
|
|
88
|
+
-webkit-backdrop-filter: blur(24px) saturate(1.25);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.header-mark {
|
|
92
|
+
width: 46px;
|
|
93
|
+
height: 46px;
|
|
94
|
+
flex: 0 0 46px;
|
|
95
|
+
display: grid;
|
|
96
|
+
place-items: center;
|
|
97
|
+
border: 1px solid rgba(103, 232, 249, 0.35);
|
|
98
|
+
border-radius: 15px;
|
|
99
|
+
color: #67e8f9;
|
|
100
|
+
font-size: 23px;
|
|
101
|
+
background: linear-gradient(145deg, rgba(34, 211, 238, 0.24), rgba(99, 102, 241, 0.14));
|
|
102
|
+
box-shadow: 0 0 24px rgba(34, 211, 238, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.18);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.header-copy {
|
|
106
|
+
min-width: 0;
|
|
107
|
+
flex: 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.eyebrow {
|
|
111
|
+
color: #67e8f9;
|
|
112
|
+
font-size: 10px;
|
|
113
|
+
font-weight: 800;
|
|
114
|
+
letter-spacing: 2.6px;
|
|
115
|
+
text-transform: uppercase;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.title {
|
|
119
|
+
margin-top: 4px;
|
|
120
|
+
color: #f8fafc;
|
|
121
|
+
font-size: 23px;
|
|
122
|
+
font-weight: 900;
|
|
123
|
+
letter-spacing: 2px;
|
|
124
|
+
text-shadow: 0 0 20px rgba(125, 211, 252, 0.22);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.subtitle {
|
|
128
|
+
margin-top: 5px;
|
|
129
|
+
color: #94a3b8;
|
|
130
|
+
font-size: 11px;
|
|
131
|
+
letter-spacing: 0.3px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.section-heading {
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 9px;
|
|
138
|
+
margin: 16px 2px 10px;
|
|
139
|
+
color: #cbd5e1;
|
|
140
|
+
font-size: 11px;
|
|
141
|
+
font-weight: 800;
|
|
142
|
+
letter-spacing: 1.8px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.section-heading::after {
|
|
146
|
+
content: '';
|
|
147
|
+
flex: 1;
|
|
148
|
+
height: 1px;
|
|
149
|
+
background: linear-gradient(90deg, rgba(103, 232, 249, 0.32), transparent);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.section-heading::before {
|
|
153
|
+
content: '';
|
|
154
|
+
width: 7px;
|
|
155
|
+
height: 7px;
|
|
156
|
+
border-radius: 50%;
|
|
157
|
+
background: #67e8f9;
|
|
158
|
+
box-shadow: 0 0 10px rgba(103, 232, 249, 0.75);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.grid {
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
gap: 10px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.row {
|
|
168
|
+
display: grid;
|
|
169
|
+
grid-template-columns: repeat(3, 1fr);
|
|
170
|
+
gap: 10px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.card {
|
|
174
|
+
min-height: 0;
|
|
175
|
+
padding: 12px 14px;
|
|
176
|
+
position: relative;
|
|
177
|
+
overflow: hidden;
|
|
178
|
+
border: 1px solid rgba(52, 211, 153, 0.28);
|
|
179
|
+
border-radius: 17px;
|
|
180
|
+
background: linear-gradient(145deg, rgba(16, 185, 129, 0.13), rgba(255, 255, 255, 0.045) 52%, rgba(99, 102, 241, 0.07));
|
|
181
|
+
box-shadow:
|
|
182
|
+
0 10px 25px rgba(0, 0, 0, 0.22),
|
|
183
|
+
0 0 18px rgba(52, 211, 153, 0.08),
|
|
184
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
|
185
|
+
backdrop-filter: blur(16px) saturate(1.2);
|
|
186
|
+
-webkit-backdrop-filter: blur(16px) saturate(1.2);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.card::before {
|
|
190
|
+
content: '';
|
|
191
|
+
position: absolute;
|
|
192
|
+
inset: 0;
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.12), transparent 45%);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.card::after {
|
|
198
|
+
content: '';
|
|
199
|
+
position: absolute;
|
|
200
|
+
right: -28px;
|
|
201
|
+
bottom: -38px;
|
|
202
|
+
width: 110px;
|
|
203
|
+
height: 90px;
|
|
204
|
+
border-radius: 50%;
|
|
205
|
+
pointer-events: none;
|
|
206
|
+
background: rgba(52, 211, 153, 0.13);
|
|
207
|
+
filter: blur(25px);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.name,
|
|
211
|
+
.server {
|
|
212
|
+
position: relative;
|
|
213
|
+
z-index: 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.member-info {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
gap: 8px;
|
|
220
|
+
position: relative;
|
|
221
|
+
z-index: 1;
|
|
222
|
+
min-width: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.name {
|
|
226
|
+
min-width: 0;
|
|
227
|
+
flex: 1;
|
|
228
|
+
color: #f8fafc;
|
|
229
|
+
font-size: 14px;
|
|
230
|
+
font-weight: 800;
|
|
231
|
+
line-height: 1.42;
|
|
232
|
+
overflow: hidden;
|
|
233
|
+
text-overflow: ellipsis;
|
|
234
|
+
white-space: nowrap;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.server {
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
gap: 5px;
|
|
241
|
+
flex: 0 1 auto;
|
|
242
|
+
max-width: 45%;
|
|
243
|
+
min-width: 0;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
color: #cbd5e1;
|
|
246
|
+
font-size: 10px;
|
|
247
|
+
white-space: nowrap;
|
|
248
|
+
text-overflow: ellipsis;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.server-icon {
|
|
252
|
+
color: #67e8f9;
|
|
253
|
+
font-size: 12px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.empty {
|
|
257
|
+
padding: 28px 20px;
|
|
258
|
+
border: 1px dashed rgba(148, 163, 184, 0.24);
|
|
259
|
+
border-radius: 17px;
|
|
260
|
+
color: #94a3b8;
|
|
261
|
+
text-align: center;
|
|
262
|
+
font-size: 13px;
|
|
263
|
+
background: rgba(255, 255, 255, 0.03);
|
|
264
|
+
}
|
|
265
|
+
</style>
|
|
266
|
+
</head>
|
|
267
|
+
<body>
|
|
268
|
+
<div id="container">
|
|
269
|
+
<div class="content">
|
|
270
|
+
<div class="header">
|
|
271
|
+
<div class="header-mark">🚚</div>
|
|
272
|
+
<div class="header-copy">
|
|
273
|
+
<div class="eyebrow">VTC LIVE MONITOR</div>
|
|
274
|
+
<div class="title">车队在线成员</div>
|
|
275
|
+
<div class="subtitle" id="subtitle"></div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div class="section-heading">在线成员</div>
|
|
280
|
+
<div class="grid" id="grid"></div>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<script>
|
|
285
|
+
function setData(data) {
|
|
286
|
+
const members = data.members || [];
|
|
287
|
+
const onlineMembers = data.onlineMembers || [];
|
|
288
|
+
const rows = data.rows || [];
|
|
289
|
+
|
|
290
|
+
document.getElementById('subtitle').textContent = `在线 ${onlineMembers.length} / ${members.length}`;
|
|
291
|
+
|
|
292
|
+
const grid = document.getElementById('grid');
|
|
293
|
+
if (!onlineMembers.length) {
|
|
294
|
+
grid.innerHTML = '<div class="empty">当前没有成员在线</div>';
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
grid.innerHTML = rows.map((row) => `
|
|
299
|
+
<div class="row">
|
|
300
|
+
${row.map((item) => `
|
|
301
|
+
<div class="card">
|
|
302
|
+
<div class="member-info">
|
|
303
|
+
<div class="name">${item.name}</div>
|
|
304
|
+
<div class="server"><span class="server-icon">◈</span>${item.serverName || '未知服务器'}</div>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
`).join('')}
|
|
308
|
+
</div>
|
|
309
|
+
`).join('');
|
|
310
|
+
}
|
|
311
|
+
</script>
|
|
312
|
+
</body>
|
|
313
|
+
</html>
|