koishi-plugin-ets2-tools-tmp 3.0.5 → 3.0.6
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
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const { segment } = require('koishi');
|
|
2
|
+
const { resolve } = require('path');
|
|
3
|
+
const common = require('../../../util/common');
|
|
4
|
+
|
|
5
|
+
module.exports = async (ctx, cfg, session) => {
|
|
6
|
+
// 群组限制检查
|
|
7
|
+
const mainGroups = cfg.tmpActivityService?.mainGroup?.groups || [];
|
|
8
|
+
const adminGroups = cfg.tmpActivityService?.admin?.groups || [];
|
|
9
|
+
const allowedGroups = [...new Set([...mainGroups, ...adminGroups])];
|
|
10
|
+
|
|
11
|
+
if (allowedGroups.length === 0) {
|
|
12
|
+
return '未配置允许使用此指令的群组';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 当前会话的群号(onebot 平台格式为 group:xxx)
|
|
16
|
+
const currentGroupId = session.channelId;
|
|
17
|
+
const isAllowed = allowedGroups.some(g => currentGroupId.includes(g));
|
|
18
|
+
|
|
19
|
+
if (!isAllowed) {
|
|
20
|
+
return '该指令在本群聊不可用';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!ctx.puppeteer) {
|
|
24
|
+
return '未启用 Puppeteer 功能';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const settings = cfg.mainSettings?.settings || {};
|
|
28
|
+
const baseUrl = settings.url || "open.vtcm.link";
|
|
29
|
+
const token = settings.token || "";
|
|
30
|
+
const logOutput = settings.logOutput;
|
|
31
|
+
|
|
32
|
+
const log = (msg) => logOutput && ctx.logger.info(msg);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
log('[积分排行] 开始查询车队成员积分信息');
|
|
36
|
+
|
|
37
|
+
// 分页获取全部成员数据(API page 参数为 1-indexed)
|
|
38
|
+
const allMembers = [];
|
|
39
|
+
let currentPage = 1;
|
|
40
|
+
let totalPage = 1;
|
|
41
|
+
|
|
42
|
+
while (currentPage <= totalPage) {
|
|
43
|
+
const queryUrl = `https://${baseUrl}/api/user/info/list?page=${currentPage}&limit=100&tmpId=&tmpName=&teamId=&qq=&state=0&teamRole=&token=${token}`;
|
|
44
|
+
log(`[积分排行] 请求第 ${currentPage} 页: ${queryUrl.replace(token, "***")}`);
|
|
45
|
+
|
|
46
|
+
const response = await ctx.http.get(queryUrl, { timeout: 10000 });
|
|
47
|
+
|
|
48
|
+
if (response.code !== 0) {
|
|
49
|
+
log(`[积分排行] 第 ${currentPage} 页请求失败: ${response.msg || '未知错误'}`);
|
|
50
|
+
return response.msg || '查询成员信息失败';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!response.page?.list) {
|
|
54
|
+
log(`[积分排行] 第 ${currentPage} 页数据为空`);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
allMembers.push(...response.page.list);
|
|
59
|
+
totalPage = response.page.totalPage || 1;
|
|
60
|
+
currentPage++;
|
|
61
|
+
|
|
62
|
+
log(`[积分排行] 已获取 ${allMembers.length}/${response.page.totalCount} 条记录, 总页数: ${totalPage}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (allMembers.length === 0) {
|
|
66
|
+
return '暂无成员数据';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 按 id 去重(防止分页返回重复数据)
|
|
70
|
+
const uniqueMembers = [];
|
|
71
|
+
const seenIds = new Set();
|
|
72
|
+
for (const m of allMembers) {
|
|
73
|
+
if (m.id && !seenIds.has(m.id)) {
|
|
74
|
+
seenIds.add(m.id);
|
|
75
|
+
uniqueMembers.push(m);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
log(`[积分排行] 去重前: ${allMembers.length}, 去重后: ${uniqueMembers.length}`);
|
|
79
|
+
|
|
80
|
+
// 按 rewardPoints 降序排序
|
|
81
|
+
const sortedMembers = uniqueMembers
|
|
82
|
+
.filter(m => typeof m.rewardPoints === 'number')
|
|
83
|
+
.sort((a, b) => b.rewardPoints - a.rewardPoints);
|
|
84
|
+
|
|
85
|
+
// 取前10
|
|
86
|
+
const top10 = sortedMembers
|
|
87
|
+
.slice(0, 10)
|
|
88
|
+
.map((player, index) => ({
|
|
89
|
+
rank: index + 1,
|
|
90
|
+
tmpName: player.tmpName || '未知',
|
|
91
|
+
tmpId: player.tmpId,
|
|
92
|
+
teamId: player.teamId,
|
|
93
|
+
rewardPoints: player.rewardPoints || 0,
|
|
94
|
+
teamRole: player.teamRole || '',
|
|
95
|
+
avatarUrl: player.avatarUrl || ''
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
log(`[积分排行] 排行榜前10名已生成, 第一名: ${top10[0]?.tmpName} (${top10[0]?.rewardPoints}积分)`);
|
|
99
|
+
|
|
100
|
+
// 查询当前用户的排名
|
|
101
|
+
let currentPlayer = null;
|
|
102
|
+
const currentUserQQ = String(session.userId || '');
|
|
103
|
+
if (currentUserQQ) {
|
|
104
|
+
const playerIndex = sortedMembers.findIndex(m => String(m.qq) === currentUserQQ);
|
|
105
|
+
if (playerIndex !== -1) {
|
|
106
|
+
const player = sortedMembers[playerIndex];
|
|
107
|
+
currentPlayer = {
|
|
108
|
+
rank: playerIndex + 1,
|
|
109
|
+
tmpName: player.tmpName || '未知',
|
|
110
|
+
tmpId: player.tmpId,
|
|
111
|
+
teamId: player.teamId,
|
|
112
|
+
rewardPoints: player.rewardPoints || 0,
|
|
113
|
+
avatarUrl: player.avatarUrl || ''
|
|
114
|
+
};
|
|
115
|
+
log(`[积分排行] 找到当前用户: ${currentPlayer.tmpName}, 排名: ${currentPlayer.rank}, 积分: ${currentPlayer.rewardPoints}`);
|
|
116
|
+
} else {
|
|
117
|
+
log(`[积分排行] 未在车队成员中找到当前用户QQ: ${currentUserQQ}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 拼接页面数据
|
|
122
|
+
const data = {
|
|
123
|
+
top10: top10,
|
|
124
|
+
totalCount: uniqueMembers.length,
|
|
125
|
+
currentPlayer: currentPlayer,
|
|
126
|
+
updateTime: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// 渲染图片
|
|
130
|
+
let page;
|
|
131
|
+
try {
|
|
132
|
+
page = await ctx.puppeteer.page();
|
|
133
|
+
await page.setViewport({ width: 800, height: 1200, deviceScaleFactor: 2 });
|
|
134
|
+
await page.goto(`file:///${resolve(__dirname, '../../../resource/point-leaderboard.html')}`);
|
|
135
|
+
await page.evaluate(`setData(${JSON.stringify(data)})`);
|
|
136
|
+
await page.waitForNetworkIdle();
|
|
137
|
+
await common.sleep(800);
|
|
138
|
+
const element = await page.$("#container");
|
|
139
|
+
return segment.image(await element.screenshot({
|
|
140
|
+
encoding: "binary"
|
|
141
|
+
}), "image/jpg");
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
log(`[积分排行] 渲染异常: ${e.message}`);
|
|
145
|
+
return '渲染异常,请重试';
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
if (page) {
|
|
149
|
+
await page.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
log(`[积分排行] 系统错误: ${error.message}`);
|
|
154
|
+
if (error.response) {
|
|
155
|
+
return `请求失败: ${error.response.status} ${error.response.statusText}`;
|
|
156
|
+
}
|
|
157
|
+
return '系统错误,请稍后重试';
|
|
158
|
+
}
|
|
159
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const commands = {
|
|
|
21
21
|
queryPoint: require('./command/ets-app/queryPoint/queryPoint'),
|
|
22
22
|
changePoint: require('./command/ets-app/changePoint/changePoint'),
|
|
23
23
|
addMember: require('./command/ets-app/addMember/addMemberV2'),
|
|
24
|
+
pointRanking: require('./command/ets-app/pointRanking/pointRanking'),
|
|
24
25
|
tmpVtc: require('./command/tmpVtc'),
|
|
25
26
|
tmpFootprint: require('./command/tmpFootprint')
|
|
26
27
|
};
|
|
@@ -75,6 +76,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
75
76
|
tmpVersion: koishi_1.Schema.boolean().default(true).description('是否启用版本查询'),
|
|
76
77
|
tmpDlcMap: koishi_1.Schema.boolean().default(true).description('是否启用DLC地图查询'),
|
|
77
78
|
tmpMileageRanking: koishi_1.Schema.boolean().default(true).description('是否启用里程排行榜'),
|
|
79
|
+
pointRanking: koishi_1.Schema.boolean().default(false).description('是否启用积分排行榜'),
|
|
78
80
|
tmpVtc: koishi_1.Schema.boolean().default(true).description('是否启用VTC查询'),
|
|
79
81
|
tmpFootprint: koishi_1.Schema.boolean().default(true).description('是否启用足迹查询'),
|
|
80
82
|
mainSettings: koishi_1.Schema.boolean().default(false).description('是否启用车队平台积分查询功能'),
|
|
@@ -189,7 +191,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
189
191
|
onlineCheck: koishi_1.Schema.object({
|
|
190
192
|
enable: koishi_1.Schema.boolean().description("启用今日有活动时的在线成员检查").default(false),
|
|
191
193
|
time: koishi_1.Schema.string().description("在线成员检查发送时间(HH:mm格式)").default("20:30"),
|
|
192
|
-
apiUrl: koishi_1.Schema.string().description("在线成员查询API地址(含vtcId参数)")
|
|
194
|
+
apiUrl: koishi_1.Schema.string().description("在线成员查询API地址(含vtcId参数)")
|
|
193
195
|
}).description("在线成员检查配置"),
|
|
194
196
|
mainGroup: koishi_1.Schema.object({
|
|
195
197
|
groups: koishi_1.Schema.array(koishi_1.Schema.string()).role("table").description("主群群号列表").default([]),
|
|
@@ -220,6 +222,7 @@ function logDisabledCommands(ctx, cfg) {
|
|
|
220
222
|
{ key: 'tmpVersion', label: 'tmp版本' },
|
|
221
223
|
{ key: 'tmpDlcMap', label: '地图dlc价格' },
|
|
222
224
|
{ key: 'tmpMileageRanking', label: '里程排行榜/今日里程排行榜' },
|
|
225
|
+
{ key: 'pointRanking', label: '积分排行榜' },
|
|
223
226
|
{ key: 'tmpVtc', label: 'vtc查询' },
|
|
224
227
|
{ key: 'tmpFootprint', label: '足迹查询' },
|
|
225
228
|
{ key: 'resetPassword', label: '重置密码' },
|
|
@@ -294,6 +297,12 @@ function registerBaseCommands(ctx, cfg) {
|
|
|
294
297
|
.action(async ({ session }) => await commands.tmpMileageRanking(ctx, session, MileageRankingType.today));
|
|
295
298
|
}
|
|
296
299
|
|
|
300
|
+
if (cfg.commands?.pointRanking) {
|
|
301
|
+
ctx.command('积分排行')
|
|
302
|
+
.usage("查询车队积分排行榜(仅限总群和管理群使用)")
|
|
303
|
+
.action(async ({ session }) => await commands.pointRanking(ctx, cfg, session));
|
|
304
|
+
}
|
|
305
|
+
|
|
297
306
|
if (cfg.commands?.tmpVtc) {
|
|
298
307
|
ctx.command('vtc查询 <vtcid>')
|
|
299
308
|
.usage("查询TruckersMP VTC信息")
|
|
@@ -0,0 +1,581 @@
|
|
|
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
|
+
body {
|
|
19
|
+
font-family: 'segui-emj', 'Microsoft YaHei', 'PingFang SC', sans-serif;
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 0;
|
|
22
|
+
background-color: #000;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#container {
|
|
26
|
+
width: 520px;
|
|
27
|
+
background: linear-gradient(160deg, #1a0b2e 0%, #16213e 35%, #0f3460 70%, #1a0b2e 100%);
|
|
28
|
+
padding: 0 0 20px 0;
|
|
29
|
+
position: relative;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* 装饰性背景光晕 */
|
|
35
|
+
#container::before {
|
|
36
|
+
content: '';
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: -100px;
|
|
39
|
+
left: -100px;
|
|
40
|
+
width: 300px;
|
|
41
|
+
height: 300px;
|
|
42
|
+
background: radial-gradient(circle, rgba(255, 215, 0, 0.15) 0%, transparent 70%);
|
|
43
|
+
pointer-events: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#container::after {
|
|
47
|
+
content: '';
|
|
48
|
+
position: absolute;
|
|
49
|
+
bottom: -100px;
|
|
50
|
+
right: -100px;
|
|
51
|
+
width: 300px;
|
|
52
|
+
height: 300px;
|
|
53
|
+
background: radial-gradient(circle, rgba(138, 43, 226, 0.15) 0%, transparent 70%);
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.header {
|
|
58
|
+
padding: 28px 24px 20px;
|
|
59
|
+
text-align: center;
|
|
60
|
+
position: relative;
|
|
61
|
+
z-index: 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.header-icon {
|
|
65
|
+
font-size: 36px;
|
|
66
|
+
margin-bottom: 8px;
|
|
67
|
+
filter: drop-shadow(0 0 12px rgba(255, 215, 0, 0.6));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.header-title {
|
|
71
|
+
color: transparent;
|
|
72
|
+
background: linear-gradient(135deg, #ffd700 0%, #ffaa00 50%, #ff6b35 100%);
|
|
73
|
+
-webkit-background-clip: text;
|
|
74
|
+
background-clip: text;
|
|
75
|
+
font-size: 26px;
|
|
76
|
+
font-weight: 800;
|
|
77
|
+
letter-spacing: 3px;
|
|
78
|
+
text-shadow: 0 2px 8px rgba(255, 215, 0, 0.3);
|
|
79
|
+
margin: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.header-subtitle {
|
|
83
|
+
color: #a8b2c8;
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
margin-top: 6px;
|
|
86
|
+
letter-spacing: 1px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.header-decoration {
|
|
90
|
+
width: 80px;
|
|
91
|
+
height: 2px;
|
|
92
|
+
background: linear-gradient(90deg, transparent, #ffd700, transparent);
|
|
93
|
+
margin: 12px auto 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* 前三名特殊区域 */
|
|
97
|
+
.top3-container {
|
|
98
|
+
padding: 8px 20px 20px;
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
align-items: flex-end;
|
|
102
|
+
gap: 12px;
|
|
103
|
+
position: relative;
|
|
104
|
+
z-index: 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.top3-item {
|
|
108
|
+
flex: 1;
|
|
109
|
+
max-width: 145px;
|
|
110
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%);
|
|
111
|
+
border-radius: 12px;
|
|
112
|
+
padding: 16px 8px 12px;
|
|
113
|
+
text-align: center;
|
|
114
|
+
position: relative;
|
|
115
|
+
backdrop-filter: blur(10px);
|
|
116
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.top3-item.rank-1 {
|
|
120
|
+
background: linear-gradient(180deg, rgba(255, 215, 0, 0.25) 0%, rgba(255, 165, 0, 0.1) 100%);
|
|
121
|
+
border-color: rgba(255, 215, 0, 0.5);
|
|
122
|
+
box-shadow: 0 0 24px rgba(255, 215, 0, 0.4), inset 0 0 12px rgba(255, 215, 0, 0.1);
|
|
123
|
+
transform: translateY(-12px);
|
|
124
|
+
order: 2;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.top3-item.rank-2 {
|
|
128
|
+
background: linear-gradient(180deg, rgba(192, 192, 192, 0.2) 0%, rgba(150, 150, 150, 0.08) 100%);
|
|
129
|
+
border-color: rgba(192, 192, 192, 0.4);
|
|
130
|
+
box-shadow: 0 0 16px rgba(192, 192, 192, 0.3);
|
|
131
|
+
order: 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.top3-item.rank-3 {
|
|
135
|
+
background: linear-gradient(180deg, rgba(205, 127, 50, 0.2) 0%, rgba(160, 100, 40, 0.08) 100%);
|
|
136
|
+
border-color: rgba(205, 127, 50, 0.4);
|
|
137
|
+
box-shadow: 0 0 16px rgba(205, 127, 50, 0.3);
|
|
138
|
+
order: 3;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.crown {
|
|
142
|
+
font-size: 22px;
|
|
143
|
+
margin-bottom: 4px;
|
|
144
|
+
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.rank-1 .crown { font-size: 28px; }
|
|
148
|
+
|
|
149
|
+
.top3-rank-num {
|
|
150
|
+
font-size: 32px;
|
|
151
|
+
font-weight: 900;
|
|
152
|
+
color: #ffffff;
|
|
153
|
+
margin: 4px 0;
|
|
154
|
+
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
|
155
|
+
line-height: 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.rank-1 .top3-rank-num { color: #ffd700; text-shadow: 0 0 12px rgba(255, 215, 0, 0.8); }
|
|
159
|
+
.rank-2 .top3-rank-num { color: #e0e0e0; text-shadow: 0 0 8px rgba(192, 192, 192, 0.6); }
|
|
160
|
+
.rank-3 .top3-rank-num { color: #cd7f32; text-shadow: 0 0 8px rgba(205, 127, 50, 0.6); }
|
|
161
|
+
|
|
162
|
+
.top3-avatar {
|
|
163
|
+
width: 56px;
|
|
164
|
+
height: 56px;
|
|
165
|
+
border-radius: 50%;
|
|
166
|
+
margin: 8px auto;
|
|
167
|
+
display: block;
|
|
168
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
169
|
+
object-fit: cover;
|
|
170
|
+
background: rgba(0, 0, 0, 0.3);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.rank-1 .top3-avatar { width: 64px; height: 64px; border-color: rgba(255, 215, 0, 0.6); }
|
|
174
|
+
|
|
175
|
+
.top3-name {
|
|
176
|
+
color: #ffffff;
|
|
177
|
+
font-size: 13px;
|
|
178
|
+
font-weight: 700;
|
|
179
|
+
margin: 6px 0 2px;
|
|
180
|
+
white-space: nowrap;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
text-overflow: ellipsis;
|
|
183
|
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.top3-teamid {
|
|
187
|
+
color: #a8b2c8;
|
|
188
|
+
font-size: 10px;
|
|
189
|
+
margin-bottom: 4px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.top3-teamid span {
|
|
193
|
+
color: #ffd700;
|
|
194
|
+
font-weight: 600;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.top3-points {
|
|
198
|
+
color: #ffd700;
|
|
199
|
+
font-size: 14px;
|
|
200
|
+
font-weight: 800;
|
|
201
|
+
text-shadow: 0 0 6px rgba(255, 215, 0, 0.4);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.rank-2 .top3-points { color: #e0e0e0; text-shadow: 0 0 6px rgba(192, 192, 192, 0.4); }
|
|
205
|
+
.rank-3 .top3-points { color: #cd7f32; text-shadow: 0 0 6px rgba(205, 127, 50, 0.4); }
|
|
206
|
+
|
|
207
|
+
.top3-points-label {
|
|
208
|
+
color: #888;
|
|
209
|
+
font-size: 10px;
|
|
210
|
+
margin-left: 2px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* 第4-10名列表 */
|
|
214
|
+
.rank-list {
|
|
215
|
+
padding: 0 20px;
|
|
216
|
+
position: relative;
|
|
217
|
+
z-index: 1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.rank-list-title {
|
|
221
|
+
color: #a8b2c8;
|
|
222
|
+
font-size: 12px;
|
|
223
|
+
text-align: center;
|
|
224
|
+
margin: 16px 0 12px;
|
|
225
|
+
letter-spacing: 2px;
|
|
226
|
+
position: relative;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.rank-list-title::before,
|
|
230
|
+
.rank-list-title::after {
|
|
231
|
+
content: '';
|
|
232
|
+
position: absolute;
|
|
233
|
+
top: 50%;
|
|
234
|
+
width: 60px;
|
|
235
|
+
height: 1px;
|
|
236
|
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.rank-list-title::before { left: 80px; }
|
|
240
|
+
.rank-list-title::after { right: 80px; }
|
|
241
|
+
|
|
242
|
+
.rank-item {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
padding: 10px 14px;
|
|
246
|
+
margin-bottom: 6px;
|
|
247
|
+
background: linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
|
|
248
|
+
border-radius: 8px;
|
|
249
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
250
|
+
transition: all 0.3s ease;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.rank-item:hover {
|
|
254
|
+
background: rgba(255, 255, 255, 0.08);
|
|
255
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.rank-num {
|
|
259
|
+
width: 36px;
|
|
260
|
+
height: 36px;
|
|
261
|
+
border-radius: 50%;
|
|
262
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05));
|
|
263
|
+
display: flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
justify-content: center;
|
|
266
|
+
color: #ffffff;
|
|
267
|
+
font-size: 14px;
|
|
268
|
+
font-weight: 700;
|
|
269
|
+
margin-right: 12px;
|
|
270
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
271
|
+
flex-shrink: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.rank-avatar {
|
|
275
|
+
width: 36px;
|
|
276
|
+
height: 36px;
|
|
277
|
+
border-radius: 50%;
|
|
278
|
+
margin-right: 12px;
|
|
279
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
280
|
+
object-fit: cover;
|
|
281
|
+
background: rgba(0, 0, 0, 0.3);
|
|
282
|
+
flex-shrink: 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.rank-info {
|
|
286
|
+
flex: 1;
|
|
287
|
+
min-width: 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.rank-name {
|
|
291
|
+
color: #ffffff;
|
|
292
|
+
font-size: 14px;
|
|
293
|
+
font-weight: 600;
|
|
294
|
+
white-space: nowrap;
|
|
295
|
+
overflow: hidden;
|
|
296
|
+
text-overflow: ellipsis;
|
|
297
|
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.rank-tmpid {
|
|
301
|
+
color: #666;
|
|
302
|
+
font-size: 10px;
|
|
303
|
+
margin-top: 2px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.rank-tmpid .team-id {
|
|
307
|
+
color: #ffd700;
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
margin-left: 6px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.rank-points {
|
|
313
|
+
color: #ffd700;
|
|
314
|
+
font-size: 16px;
|
|
315
|
+
font-weight: 800;
|
|
316
|
+
text-shadow: 0 0 6px rgba(255, 215, 0, 0.3);
|
|
317
|
+
white-space: nowrap;
|
|
318
|
+
flex-shrink: 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.rank-points-label {
|
|
322
|
+
color: #888;
|
|
323
|
+
font-size: 10px;
|
|
324
|
+
margin-left: 2px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* 底部信息 */
|
|
328
|
+
.footer {
|
|
329
|
+
margin-top: 20px;
|
|
330
|
+
padding: 12px 20px 0;
|
|
331
|
+
text-align: center;
|
|
332
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
333
|
+
position: relative;
|
|
334
|
+
z-index: 1;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.footer-total {
|
|
338
|
+
color: #a8b2c8;
|
|
339
|
+
font-size: 11px;
|
|
340
|
+
margin-bottom: 4px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.footer-total span {
|
|
344
|
+
color: #ffd700;
|
|
345
|
+
font-weight: 700;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.footer-update {
|
|
349
|
+
color: #555;
|
|
350
|
+
font-size: 10px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* 当前用户排名区域 */
|
|
354
|
+
.current-player-section {
|
|
355
|
+
margin: 16px 20px 0;
|
|
356
|
+
padding: 14px 16px;
|
|
357
|
+
background: linear-gradient(90deg, rgba(255, 215, 0, 0.15) 0%, rgba(255, 165, 0, 0.08) 100%);
|
|
358
|
+
border-radius: 10px;
|
|
359
|
+
border: 1px solid rgba(255, 215, 0, 0.3);
|
|
360
|
+
box-shadow: 0 0 12px rgba(255, 215, 0, 0.15);
|
|
361
|
+
position: relative;
|
|
362
|
+
z-index: 1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.current-player-title {
|
|
366
|
+
color: #ffd700;
|
|
367
|
+
font-size: 12px;
|
|
368
|
+
text-align: center;
|
|
369
|
+
margin-bottom: 10px;
|
|
370
|
+
letter-spacing: 2px;
|
|
371
|
+
font-weight: 600;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.current-player-content {
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: center;
|
|
377
|
+
gap: 12px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.current-player-rank {
|
|
381
|
+
width: 44px;
|
|
382
|
+
height: 44px;
|
|
383
|
+
border-radius: 50%;
|
|
384
|
+
background: linear-gradient(135deg, #ffd700, #ff8c00);
|
|
385
|
+
display: flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
justify-content: center;
|
|
388
|
+
color: #1a0b2e;
|
|
389
|
+
font-size: 16px;
|
|
390
|
+
font-weight: 800;
|
|
391
|
+
flex-shrink: 0;
|
|
392
|
+
box-shadow: 0 0 10px rgba(255, 215, 0, 0.4);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.current-player-info {
|
|
396
|
+
flex: 1;
|
|
397
|
+
min-width: 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.current-player-name {
|
|
401
|
+
color: #ffffff;
|
|
402
|
+
font-size: 14px;
|
|
403
|
+
font-weight: 700;
|
|
404
|
+
white-space: nowrap;
|
|
405
|
+
overflow: hidden;
|
|
406
|
+
text-overflow: ellipsis;
|
|
407
|
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.current-player-meta {
|
|
411
|
+
color: #a8b2c8;
|
|
412
|
+
font-size: 11px;
|
|
413
|
+
margin-top: 3px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.current-player-meta .team-id {
|
|
417
|
+
color: #ffd700;
|
|
418
|
+
margin-left: 6px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.current-player-points {
|
|
422
|
+
color: #ffd700;
|
|
423
|
+
font-size: 18px;
|
|
424
|
+
font-weight: 800;
|
|
425
|
+
text-shadow: 0 0 8px rgba(255, 215, 0, 0.4);
|
|
426
|
+
white-space: nowrap;
|
|
427
|
+
flex-shrink: 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.current-player-points-label {
|
|
431
|
+
color: #888;
|
|
432
|
+
font-size: 11px;
|
|
433
|
+
margin-left: 2px;
|
|
434
|
+
}
|
|
435
|
+
</style>
|
|
436
|
+
</head>
|
|
437
|
+
<body>
|
|
438
|
+
<div id="container">
|
|
439
|
+
<div class="header">
|
|
440
|
+
<div class="header-icon">🏆</div>
|
|
441
|
+
<h1 class="header-title">积 分 排 行 榜</h1>
|
|
442
|
+
<div class="header-subtitle">POINTS LEADERBOARD</div>
|
|
443
|
+
<div class="header-decoration"></div>
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<!-- 前三名展示 -->
|
|
447
|
+
<div class="top3-container" id="top3"></div>
|
|
448
|
+
|
|
449
|
+
<!-- 第4-10名列表 -->
|
|
450
|
+
<div class="rank-list" id="rankList" style="display:none;">
|
|
451
|
+
<div class="rank-list-title">— 其他排名 —</div>
|
|
452
|
+
<div id="rankListContent"></div>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<!-- 当前用户排名 -->
|
|
456
|
+
<div class="current-player-section" id="currentPlayerSection" style="display:none;">
|
|
457
|
+
<div class="current-player-title">— 你的排名 —</div>
|
|
458
|
+
<div class="current-player-content" id="currentPlayerContent"></div>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<!-- 底部信息 -->
|
|
462
|
+
<div class="footer">
|
|
463
|
+
<div class="footer-total">总成员数: <span id="totalCount">0</span> 人</div>
|
|
464
|
+
<div class="footer-update">数据更新时间: <span id="updateTime">-</span></div>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<script>
|
|
469
|
+
function escapeHtml(text) {
|
|
470
|
+
if (!text) return '';
|
|
471
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
472
|
+
return String(text).replace(/[&<>"']/g, m => map[m]);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function createAvatarHtml(player, sizeClass) {
|
|
476
|
+
if (!player.avatarUrl) {
|
|
477
|
+
return `<div class="${sizeClass}" style="display:flex; align-items:center; justify-content:center; color:#888;">${escapeHtml(player.tmpName.charAt(0))}</div>`;
|
|
478
|
+
}
|
|
479
|
+
return `<img class="${sizeClass}" src="${escapeHtml(player.avatarUrl)}" onerror="this.onerror=null; this.src=''; this.style.display='none'; this.parentElement.querySelector('.avatar-fallback').style.display='flex';">
|
|
480
|
+
<div class="${sizeClass} avatar-fallback" style="display:none; align-items:center; justify-content:center; color:#888;">${escapeHtml(player.tmpName.charAt(0))}</div>`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function createTop3Item(player) {
|
|
484
|
+
const item = document.createElement('div');
|
|
485
|
+
const rankClass = `rank-${player.rank}`;
|
|
486
|
+
item.className = `top3-item ${rankClass}`;
|
|
487
|
+
|
|
488
|
+
const crowns = { 1: '👑', 2: '🥈', 3: '🥉' };
|
|
489
|
+
|
|
490
|
+
item.innerHTML = `
|
|
491
|
+
<div class="crown">${crowns[player.rank]}</div>
|
|
492
|
+
<div class="top3-rank-num">${player.rank}</div>
|
|
493
|
+
<div class="avatar-wrapper">${createAvatarHtml(player, 'top3-avatar')}</div>
|
|
494
|
+
<div class="top3-name">${escapeHtml(player.tmpName)}</div>
|
|
495
|
+
<div class="top3-teamid">车队编号: <span>#${escapeHtml(player.teamId)}</span></div>
|
|
496
|
+
<div class="top3-points">${player.rewardPoints.toLocaleString()}<span class="top3-points-label">分</span></div>
|
|
497
|
+
`;
|
|
498
|
+
return item;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function createRankItem(player) {
|
|
502
|
+
const item = document.createElement('div');
|
|
503
|
+
item.className = 'rank-item';
|
|
504
|
+
|
|
505
|
+
item.innerHTML = `
|
|
506
|
+
<div class="rank-num">${player.rank}</div>
|
|
507
|
+
<div class="avatar-wrapper">${createAvatarHtml(player, 'rank-avatar')}</div>
|
|
508
|
+
<div class="rank-info">
|
|
509
|
+
<div class="rank-name">${escapeHtml(player.tmpName)}</div>
|
|
510
|
+
<div class="rank-tmpid">TMP: ${escapeHtml(player.tmpId)}<span class="team-id">车队 #${escapeHtml(player.teamId)}</span></div>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="rank-points">${player.rewardPoints.toLocaleString()}<span class="rank-points-label">分</span></div>
|
|
513
|
+
`;
|
|
514
|
+
return item;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function createCurrentPlayerItem(player) {
|
|
518
|
+
const content = document.createElement('div');
|
|
519
|
+
content.className = 'current-player-content';
|
|
520
|
+
|
|
521
|
+
content.innerHTML = `
|
|
522
|
+
<div class="current-player-rank">#${player.rank}</div>
|
|
523
|
+
<div class="current-player-info">
|
|
524
|
+
<div class="current-player-name">${escapeHtml(player.tmpName)}</div>
|
|
525
|
+
<div class="current-player-meta">TMP: ${escapeHtml(player.tmpId)}<span class="team-id">车队 #${escapeHtml(player.teamId)}</span></div>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="current-player-points">${player.rewardPoints.toLocaleString()}<span class="current-player-points-label">分</span></div>
|
|
528
|
+
`;
|
|
529
|
+
return content;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function setData(data) {
|
|
533
|
+
if (!data || !data.top10 || !Array.isArray(data.top10)) {
|
|
534
|
+
console.error('数据无效');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const top3Container = document.getElementById('top3');
|
|
539
|
+
const rankList = document.getElementById('rankList');
|
|
540
|
+
const rankListContent = document.getElementById('rankListContent');
|
|
541
|
+
const currentPlayerSection = document.getElementById('currentPlayerSection');
|
|
542
|
+
const currentPlayerContent = document.getElementById('currentPlayerContent');
|
|
543
|
+
const totalCountEl = document.getElementById('totalCount');
|
|
544
|
+
const updateTimeEl = document.getElementById('updateTime');
|
|
545
|
+
|
|
546
|
+
top3Container.innerHTML = '';
|
|
547
|
+
rankListContent.innerHTML = '';
|
|
548
|
+
currentPlayerContent.innerHTML = '';
|
|
549
|
+
|
|
550
|
+
// 渲染前三名
|
|
551
|
+
const top3 = data.top10.slice(0, 3);
|
|
552
|
+
top3.forEach(player => {
|
|
553
|
+
top3Container.appendChild(createTop3Item(player));
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// 渲染第4-10名
|
|
557
|
+
const rest = data.top10.slice(3);
|
|
558
|
+
if (rest.length > 0) {
|
|
559
|
+
rankList.style.display = 'block';
|
|
560
|
+
rest.forEach(player => {
|
|
561
|
+
rankListContent.appendChild(createRankItem(player));
|
|
562
|
+
});
|
|
563
|
+
} else {
|
|
564
|
+
rankList.style.display = 'none';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// 渲染当前用户排名(仅在找到用户数据时显示)
|
|
568
|
+
if (data.currentPlayer && data.currentPlayer.rank) {
|
|
569
|
+
currentPlayerSection.style.display = 'block';
|
|
570
|
+
currentPlayerContent.appendChild(createCurrentPlayerItem(data.currentPlayer));
|
|
571
|
+
} else {
|
|
572
|
+
currentPlayerSection.style.display = 'none';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// 底部信息
|
|
576
|
+
if (totalCountEl) totalCountEl.textContent = (data.totalCount || 0).toLocaleString();
|
|
577
|
+
if (updateTimeEl) updateTimeEl.textContent = data.updateTime || '-';
|
|
578
|
+
}
|
|
579
|
+
</script>
|
|
580
|
+
</body>
|
|
581
|
+
</html>
|