koishi-plugin-aktmp 1.0.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/api/evmOpenApi.d.ts +45 -0
- package/lib/api/evmOpenApi.js +158 -0
- package/lib/api/truckersMpApi.d.ts +34 -0
- package/lib/api/truckersMpApi.js +110 -0
- package/lib/api/truckersMpMapApi.d.ts +6 -0
- package/lib/api/truckersMpMapApi.js +25 -0
- package/lib/api/truckyAppApi.d.ts +12 -0
- package/lib/api/truckyAppApi.js +32 -0
- package/lib/command/tmpBind.d.ts +2 -0
- package/lib/command/tmpBind.js +19 -0
- package/lib/command/tmpDlcMap.d.ts +3 -0
- package/lib/command/tmpDlcMap.js +33 -0
- package/lib/command/tmpFootprint.d.ts +3 -0
- package/lib/command/tmpFootprint.js +82 -0
- package/lib/command/tmpMileageRanking.d.ts +3 -0
- package/lib/command/tmpMileageRanking.js +55 -0
- package/lib/command/tmpPosition.d.ts +3 -0
- package/lib/command/tmpPosition.js +95 -0
- package/lib/command/tmpQuery.d.ts +2 -0
- package/lib/command/tmpQuery.js +148 -0
- 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 +40 -0
- package/lib/command/tmpTraffic/tmpTraffic.d.ts +2 -0
- package/lib/command/tmpTraffic/tmpTraffic.js +15 -0
- package/lib/command/tmpTraffic/tmpTrafficMap.d.ts +3 -0
- package/lib/command/tmpTraffic/tmpTrafficMap.js +119 -0
- package/lib/command/tmpTraffic/tmpTrafficText.d.ts +2 -0
- package/lib/command/tmpTraffic/tmpTrafficText.js +122 -0
- package/lib/command/tmpUnbind.js +17 -0
- package/lib/command/tmpVersion.d.ts +2 -0
- package/lib/command/tmpVersion.js +31 -0
- package/lib/database/guildBind.d.ts +22 -0
- package/lib/database/guildBind.js +55 -0
- package/lib/database/model.d.ts +2 -0
- package/lib/database/model.js +65 -0
- package/lib/database/translateCache.d.ts +14 -0
- package/lib/database/translateCache.js +31 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +59 -0
- package/lib/resource/dlc.html +115 -0
- package/lib/resource/footprint.html +241 -0
- package/lib/resource/mileage-leaderboard.html +363 -0
- package/lib/resource/package/SEGUIEMJ.TTF +0 -0
- package/lib/resource/package/ets-map.js +63 -0
- package/lib/resource/package/leaflet/heatmap.min.js +9 -0
- package/lib/resource/package/leaflet/leaflet-heatmap.js +246 -0
- package/lib/resource/package/leaflet/leaflet.min.css +1 -0
- package/lib/resource/package/leaflet/leaflet.min.js +1 -0
- package/lib/resource/position.html +229 -0
- package/lib/resource/server-list.html +309 -0
- package/lib/resource/traffic.html +207 -0
- package/lib/util/baiduTranslate.d.ts +2 -0
- package/lib/util/baiduTranslate.js +30 -0
- package/lib/util/common.d.ts +1 -0
- package/lib/util/common.js +5 -0
- package/lib/util/constant.d.ts +19 -0
- package/lib/util/constant.js +36 -0
- package/package.json +48 -0
- package/readme.md +86 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const evmOpenApi = require('../api/evmOpenApi');
|
|
2
|
+
module.exports = async (ctx) => {
|
|
3
|
+
// 查询版本信息
|
|
4
|
+
let result = await evmOpenApi.tmpVersion(ctx.http);
|
|
5
|
+
if (result.error) {
|
|
6
|
+
return '❌查询数据失败,请稍后再试';
|
|
7
|
+
}
|
|
8
|
+
// 固定宽度填充函数
|
|
9
|
+
const padRight = (str, len) => str + ' '.repeat(Math.max(0, len - str.length));
|
|
10
|
+
// 构建消息返回
|
|
11
|
+
let message = '📶 TruckersMP 版本信息\n';
|
|
12
|
+
message += '✦─────────────────✦\n';
|
|
13
|
+
if (result.data.tmpVersion) {
|
|
14
|
+
message += `◈ 联机插件 ${padRight(result.data.tmpVersion, 8)}\n`;
|
|
15
|
+
}
|
|
16
|
+
if (result.data.supportGameVersion) {
|
|
17
|
+
message += `◈ 欧卡支持 ${padRight(result.data.supportGameVersion, 8)}\n`;
|
|
18
|
+
}
|
|
19
|
+
if (result.data.officialGameVersion) {
|
|
20
|
+
message += `◈ 官方欧卡 ${padRight(result.data.officialGameVersion, 8)}\n`;
|
|
21
|
+
}
|
|
22
|
+
if (result.data.supportGameVersion && result.data.officialGameVersion) {
|
|
23
|
+
if (result.data.supportGameVersion === result.data.officialGameVersion) {
|
|
24
|
+
message += `◈ 兼容游戏 ✅\n`;
|
|
25
|
+
} else {
|
|
26
|
+
message += `◈ 兼容游戏 ❌\n`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
message += '✦─────────────────✦';
|
|
30
|
+
return message;
|
|
31
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取绑定信息
|
|
3
|
+
* @param db 数据源
|
|
4
|
+
* @param platform 平台
|
|
5
|
+
* @param userId 用户编号
|
|
6
|
+
*/
|
|
7
|
+
export function get(db: any, platform: any, userId: any): Promise<any>;
|
|
8
|
+
/**
|
|
9
|
+
* 新增或更新绑定信息
|
|
10
|
+
* @param db 数据源
|
|
11
|
+
* @param platform 平台
|
|
12
|
+
* @param userId 用户编号
|
|
13
|
+
* @param tmpId TMP ID
|
|
14
|
+
*/
|
|
15
|
+
export function saveOrUpdate(db: any, platform: any, userId: any, tmpId: any): void;
|
|
16
|
+
/**
|
|
17
|
+
* 删除绑定信息
|
|
18
|
+
* @param db 数据源
|
|
19
|
+
* @param platform 平台
|
|
20
|
+
* @param userId 用户编号
|
|
21
|
+
*/
|
|
22
|
+
export function remove(db: any, platform: any, userId: any): Promise<boolean>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
/**
|
|
3
|
+
* 获取绑定信息
|
|
4
|
+
* @param db 数据源
|
|
5
|
+
* @param platform 平台
|
|
6
|
+
* @param userId 用户编号
|
|
7
|
+
*/
|
|
8
|
+
async get(db, platform, userId) {
|
|
9
|
+
const guildBindList = await db.get('tmp_guild_bind', {
|
|
10
|
+
platform,
|
|
11
|
+
user_id: userId
|
|
12
|
+
});
|
|
13
|
+
if (guildBindList && guildBindList.length > 0) {
|
|
14
|
+
return guildBindList[0];
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
},
|
|
18
|
+
/**
|
|
19
|
+
* 新增或更新绑定信息
|
|
20
|
+
* @param db 数据源
|
|
21
|
+
* @param platform 平台
|
|
22
|
+
* @param userId 用户编号
|
|
23
|
+
* @param tmpId TMP ID
|
|
24
|
+
*/
|
|
25
|
+
saveOrUpdate(db, platform, userId, tmpId) {
|
|
26
|
+
this.get(db, platform, userId).then((data) => {
|
|
27
|
+
if (data) {
|
|
28
|
+
db.set('tmp_guild_bind', data.id, {
|
|
29
|
+
tmp_id: tmpId
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
db.create('tmp_guild_bind', {
|
|
34
|
+
platform: platform,
|
|
35
|
+
user_id: userId,
|
|
36
|
+
tmp_id: tmpId
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* 删除绑定信息
|
|
43
|
+
* @param db 数据源
|
|
44
|
+
* @param platform 平台
|
|
45
|
+
* @param userId 用户编号
|
|
46
|
+
*/
|
|
47
|
+
async remove(db, platform, userId) {
|
|
48
|
+
const data = await this.get(db, platform, userId);
|
|
49
|
+
if (data) {
|
|
50
|
+
await db.remove('tmp_guild_bind', data.id);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据表声明
|
|
3
|
+
*/
|
|
4
|
+
const modelArray = {
|
|
5
|
+
tmp_guild_bind: {
|
|
6
|
+
id: {
|
|
7
|
+
type: 'unsigned',
|
|
8
|
+
length: 10,
|
|
9
|
+
nullable: false,
|
|
10
|
+
comment: '主键'
|
|
11
|
+
},
|
|
12
|
+
platform: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
length: 50,
|
|
15
|
+
nullable: false,
|
|
16
|
+
comment: '所属平台'
|
|
17
|
+
},
|
|
18
|
+
user_id: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
length: 50,
|
|
21
|
+
nullable: false,
|
|
22
|
+
comment: '用户编号'
|
|
23
|
+
},
|
|
24
|
+
tmp_id: {
|
|
25
|
+
type: 'unsigned',
|
|
26
|
+
length: 50,
|
|
27
|
+
nullable: false,
|
|
28
|
+
comment: 'TMP ID'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
tmp_translate_cache: {
|
|
32
|
+
id: {
|
|
33
|
+
type: 'unsigned',
|
|
34
|
+
length: 10,
|
|
35
|
+
nullable: false,
|
|
36
|
+
comment: '主键'
|
|
37
|
+
},
|
|
38
|
+
content: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
nullable: false,
|
|
41
|
+
length: 200,
|
|
42
|
+
comment: '原文文本'
|
|
43
|
+
},
|
|
44
|
+
content_md5: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
nullable: false,
|
|
47
|
+
length: 32,
|
|
48
|
+
comment: '原文文本md5'
|
|
49
|
+
},
|
|
50
|
+
translate_content: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
nullable: false,
|
|
53
|
+
length: 200,
|
|
54
|
+
comment: '翻译文本'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* 初始化数据库
|
|
60
|
+
*/
|
|
61
|
+
module.exports = (ctx) => {
|
|
62
|
+
for (let modelName in modelArray) {
|
|
63
|
+
ctx.model.extend(modelName, modelArray[modelName], { autoInc: true });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 查询翻译
|
|
3
|
+
* @param db 数据源
|
|
4
|
+
* @param contentMd5 文本MD5
|
|
5
|
+
*/
|
|
6
|
+
export function getTranslate(db: any, contentMd5: any): Promise<any>;
|
|
7
|
+
/**
|
|
8
|
+
* 保存翻译缓存信息
|
|
9
|
+
* @param db 数据源
|
|
10
|
+
* @param contentMd5 原文文本MD5
|
|
11
|
+
* @param content 原文文本
|
|
12
|
+
* @param translateContent 翻译文本
|
|
13
|
+
*/
|
|
14
|
+
export function save(db: any, contentMd5: any, content: any, translateContent: any): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
/**
|
|
3
|
+
* 查询翻译
|
|
4
|
+
* @param db 数据源
|
|
5
|
+
* @param contentMd5 文本MD5
|
|
6
|
+
*/
|
|
7
|
+
async getTranslate(db, contentMd5) {
|
|
8
|
+
const translateCacheList = await db.get('tmp_translate_cache', {
|
|
9
|
+
content_md5: contentMd5
|
|
10
|
+
});
|
|
11
|
+
// 如果查询到了缓存,直接返回翻译文本
|
|
12
|
+
if (translateCacheList && translateCacheList.length > 0) {
|
|
13
|
+
return translateCacheList[0].translate_content;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
},
|
|
17
|
+
/**
|
|
18
|
+
* 保存翻译缓存信息
|
|
19
|
+
* @param db 数据源
|
|
20
|
+
* @param contentMd5 原文文本MD5
|
|
21
|
+
* @param content 原文文本
|
|
22
|
+
* @param translateContent 翻译文本
|
|
23
|
+
*/
|
|
24
|
+
save(db, contentMd5, content, translateContent) {
|
|
25
|
+
db.create('tmp_translate_cache', {
|
|
26
|
+
content,
|
|
27
|
+
content_md5: contentMd5,
|
|
28
|
+
translate_content: translateContent
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "aktmp";
|
|
3
|
+
export declare const inject: {
|
|
4
|
+
required: string[];
|
|
5
|
+
optional: string[];
|
|
6
|
+
};
|
|
7
|
+
export interface Config {
|
|
8
|
+
baiduTranslateEnable: boolean;
|
|
9
|
+
baiduTranslateAppId: string;
|
|
10
|
+
baiduTranslateKey: string;
|
|
11
|
+
baiduTranslateCacheEnable: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const Config: Schema<Config>;
|
|
14
|
+
export declare function apply(ctx: Context, cfg: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Config = exports.inject = exports.name = void 0;
|
|
4
|
+
exports.apply = apply;
|
|
5
|
+
const koishi_1 = require("koishi");
|
|
6
|
+
const model = require('./database/model');
|
|
7
|
+
const { MileageRankingType } = require('./util/constant');
|
|
8
|
+
const tmpQuery = require('./command/tmpQuery');
|
|
9
|
+
const tmpServer = require('./command/tmpServer/tmpServer');
|
|
10
|
+
const tmpBind = require('./command/tmpBind');
|
|
11
|
+
const tmpUnbind = require('./command/tmpUnbind');
|
|
12
|
+
const tmpTraffic = require('./command/tmpTraffic/tmpTraffic');
|
|
13
|
+
const tmpPosition = require('./command/tmpPosition');
|
|
14
|
+
const tmpVersion = require('./command/tmpVersion');
|
|
15
|
+
const tmpDlcMap = require('./command/tmpDlcMap');
|
|
16
|
+
const tmpMileageRanking = require('./command/tmpMileageRanking');
|
|
17
|
+
const tmpFootprint = require('./command/tmpFootprint');
|
|
18
|
+
const { ServerType } = require('./util/constant');
|
|
19
|
+
exports.name = 'aktmp';
|
|
20
|
+
exports.inject = {
|
|
21
|
+
required: ['database'],
|
|
22
|
+
optional: ['puppeteer']
|
|
23
|
+
};
|
|
24
|
+
exports.Config = koishi_1.Schema.intersect([
|
|
25
|
+
koishi_1.Schema.object({
|
|
26
|
+
baiduTranslateEnable: koishi_1.Schema.boolean().default(false).description('启用百度翻译'),
|
|
27
|
+
baiduTranslateAppId: koishi_1.Schema.string().description('百度翻译APP ID'),
|
|
28
|
+
baiduTranslateKey: koishi_1.Schema.string().description('百度翻译秘钥'),
|
|
29
|
+
baiduTranslateCacheEnable: koishi_1.Schema.boolean().default(false).description('启用百度翻译缓存')
|
|
30
|
+
}).description('基本配置'),
|
|
31
|
+
koishi_1.Schema.object({
|
|
32
|
+
queryShowAvatarEnable: koishi_1.Schema.boolean().default(false).description('查询指令展示头像'),
|
|
33
|
+
tmpTrafficType: koishi_1.Schema.union([
|
|
34
|
+
koishi_1.Schema.const(1).description('文字'),
|
|
35
|
+
koishi_1.Schema.const(2).description('热力图')
|
|
36
|
+
]).default(1).description('路况信息展示方式'),
|
|
37
|
+
tmpServerType: koishi_1.Schema.union([
|
|
38
|
+
koishi_1.Schema.const(1).description('文字'),
|
|
39
|
+
koishi_1.Schema.const(2).description('图片')
|
|
40
|
+
]).default(1).description('服务器信息展示方式')
|
|
41
|
+
}).description('指令配置'),
|
|
42
|
+
]);
|
|
43
|
+
function apply(ctx, cfg) {
|
|
44
|
+
// 初始化数据表
|
|
45
|
+
model(ctx);
|
|
46
|
+
// 注册指令
|
|
47
|
+
ctx.command('绑定 <tmpId>', '绑定TMP账号').action(async ({ session }, tmpId) => await tmpBind(ctx, cfg, session, tmpId));
|
|
48
|
+
ctx.command('解绑', '解绑TMP账号').action(async ({ session }) => await tmpUnbind(ctx, session));
|
|
49
|
+
ctx.command('查询 <tmpId>', '查询TMP玩家信息').action(async ({ session }, tmpId) => await tmpQuery(ctx, cfg, session, tmpId));
|
|
50
|
+
ctx.command('定位 <tmpId>', '定位玩家位置').action(async ({ session }, tmpId) => await tmpPosition(ctx, cfg, session, tmpId));
|
|
51
|
+
ctx.command('路况 <serverName>', '查看路况信息').action(async ({ session }, serverName) => await tmpTraffic(ctx, cfg, serverName));
|
|
52
|
+
ctx.command('服务器', '查看ETS2服务器状态').action(async () => await tmpServer(ctx, cfg));
|
|
53
|
+
ctx.command('地图dlc', '查看地图DLC').action(async ({ session }) => await tmpDlcMap(ctx, session));
|
|
54
|
+
ctx.command('插件版本', '查看插件版本').action(async () => await tmpVersion(ctx));
|
|
55
|
+
ctx.command('今日里程排行', '查看今日里程排行').action(async ({ session }) => await tmpMileageRanking(ctx, session, MileageRankingType.today));
|
|
56
|
+
ctx.command('总里程排行', '查看总里程排行').action(async ({ session }) => await tmpMileageRanking(ctx, session, MileageRankingType.total));
|
|
57
|
+
ctx.command('足迹 <tmpId>', '查看ETS2玩家足迹').action(async ({ session }, tmpId) => await tmpFootprint(ctx, session, ServerType.ets, tmpId));
|
|
58
|
+
ctx.command('足迹p <tmpId>', '查看ProMods玩家足迹').action(async ({ session }, tmpId) => await tmpFootprint(ctx, session, ServerType.promods, tmpId));
|
|
59
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>DLC</title>
|
|
6
|
+
<style>
|
|
7
|
+
#dlc-info-container {
|
|
8
|
+
width: 600px;
|
|
9
|
+
background-color: #222d33;
|
|
10
|
+
padding: 14px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dlc-box {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: row;
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
padding: 12px;
|
|
18
|
+
margin-top: 12px;
|
|
19
|
+
background-size: cover;
|
|
20
|
+
background-repeat: no-repeat;
|
|
21
|
+
background-position: center;
|
|
22
|
+
}
|
|
23
|
+
.dlc-box:nth-of-type(1) {
|
|
24
|
+
margin-top: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.dlc-box .header-image {
|
|
28
|
+
width: 210px;
|
|
29
|
+
display: inline-block;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.dlc-box .dlc-info {
|
|
33
|
+
flex: 1;
|
|
34
|
+
width: 0;
|
|
35
|
+
//border: 1px solid red;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
padding: 2px 12px;
|
|
38
|
+
}
|
|
39
|
+
.dlc-info .name {
|
|
40
|
+
color: #ffffff;
|
|
41
|
+
font-size: 18px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
.dlc-info .desc {
|
|
48
|
+
color: #e5e5e5;
|
|
49
|
+
font-size: 14px;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
text-overflow: ellipsis;
|
|
52
|
+
display: -webkit-box;
|
|
53
|
+
-webkit-line-clamp: 2;
|
|
54
|
+
-webkit-box-orient: vertical;
|
|
55
|
+
}
|
|
56
|
+
.dlc-info .price-box {
|
|
57
|
+
margin-top: 8px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.dlc-info .price-box span {
|
|
61
|
+
display: inline-block;
|
|
62
|
+
color: #BEEE11;
|
|
63
|
+
font-size: 16px;
|
|
64
|
+
}
|
|
65
|
+
.dlc-info .price-box .discount-price {
|
|
66
|
+
color: #cbcbcb;
|
|
67
|
+
text-decoration: line-through;
|
|
68
|
+
}
|
|
69
|
+
.dlc-info .price-box .discount {
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
color: #BEEE11;
|
|
72
|
+
background-color: #4c6b22;
|
|
73
|
+
padding: 2px 6px;
|
|
74
|
+
margin-left: 4px;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
77
|
+
</head>
|
|
78
|
+
<body>
|
|
79
|
+
<div id="dlc-info-container">
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<script>
|
|
83
|
+
function setData(dlcList) {
|
|
84
|
+
// 遍历渲染DLC列表
|
|
85
|
+
for (let dlc of dlcList) {
|
|
86
|
+
let dom = document.createElement(`div`);
|
|
87
|
+
dom.className = 'dlc-box';
|
|
88
|
+
dom.style.backgroundImage = `url('${dlc.backgroundImageUrl}')`;
|
|
89
|
+
|
|
90
|
+
let priceDiscountDom = ''
|
|
91
|
+
if (dlc.discount > 0) {
|
|
92
|
+
priceDiscountDom = `
|
|
93
|
+
<span class="discount-price">¥${Math.ceil(dlc.originalPrice / 100)}</span>
|
|
94
|
+
<span class="discount">-${dlc.discount}%</span>
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
dom.innerHTML = `
|
|
99
|
+
<img class="header-image" src="${dlc.headerImageUrl}"/>
|
|
100
|
+
<div class="dlc-info">
|
|
101
|
+
<div class="name">${dlc.name}</div>
|
|
102
|
+
<div class="desc">${dlc.desc}</div>
|
|
103
|
+
<div class="price-box">
|
|
104
|
+
<span>¥${Math.ceil(dlc.finalPrice / 100)}</span>
|
|
105
|
+
${priceDiscountDom}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
document.querySelector('#dlc-info-container').appendChild(dom);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>玩家足迹展示</title>
|
|
6
|
+
<link href="./package/leaflet/leaflet.min.css" rel="stylesheet">
|
|
7
|
+
<script src="./package/leaflet/leaflet.min.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
@font-face {
|
|
10
|
+
font-family: 'segui-emj';
|
|
11
|
+
src: url('./package/SEGUIEMJ.TTF');
|
|
12
|
+
font-weight: normal;
|
|
13
|
+
font-style: normal;
|
|
14
|
+
}
|
|
15
|
+
body, html {
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
font-family: 'segui-emj', "微软雅黑", serif;
|
|
19
|
+
}
|
|
20
|
+
#container {
|
|
21
|
+
width: 800px;
|
|
22
|
+
height: 600px;
|
|
23
|
+
background: #1a1a1a;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
.status-bar {
|
|
28
|
+
position: absolute;
|
|
29
|
+
bottom: 0;
|
|
30
|
+
left: 0;
|
|
31
|
+
right: 0;
|
|
32
|
+
height: 32px;
|
|
33
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
34
|
+
backdrop-filter: blur(10px);
|
|
35
|
+
-webkit-backdrop-filter: blur(10px);
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
padding: 0 12px;
|
|
39
|
+
box-shadow: 0 -2px 10px rgba(0, 0, 0, .5);
|
|
40
|
+
z-index: 1001;
|
|
41
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
42
|
+
font-size: 13px;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
}
|
|
45
|
+
.status-bar .avatar {
|
|
46
|
+
width: 20px;
|
|
47
|
+
height: 20px;
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
50
|
+
margin-right: 8px;
|
|
51
|
+
}
|
|
52
|
+
.status-bar .info {
|
|
53
|
+
flex: 1;
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
}
|
|
57
|
+
.status-bar .info .name {
|
|
58
|
+
color: #b0c7ff;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
}
|
|
61
|
+
.status-bar .stats {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
color: #aaaaaa;
|
|
65
|
+
}
|
|
66
|
+
.status-bar .stats .label {
|
|
67
|
+
margin-right: 6px;
|
|
68
|
+
}
|
|
69
|
+
.status-bar .stats .value {
|
|
70
|
+
font-weight: bold;
|
|
71
|
+
color: #54d354;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#map-box {
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: 100%;
|
|
77
|
+
}
|
|
78
|
+
#map {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
background-color: rgba(0, 0, 0, 0.25);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.marker-label {
|
|
85
|
+
background: rgba(0, 0, 0, 0.6);
|
|
86
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
color: #fff;
|
|
89
|
+
padding: 2px 6px;
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
96
|
+
<div id="container">
|
|
97
|
+
<div id="map-box">
|
|
98
|
+
<div id="map"></div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="status-bar">
|
|
101
|
+
<img class="avatar" id="avatar" src="https://static.truckersmp.com/avatarsN/small/defaultavatar.png" alt="avatar"/>
|
|
102
|
+
<div class="info">
|
|
103
|
+
<div class="name" id="username">测试玩家</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="stats" id="stats-box">
|
|
106
|
+
<span class="label">今日里程</span>
|
|
107
|
+
<span class="value" id="distance">0.0 km</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<script src="./package/ets-map.js"></script>
|
|
113
|
+
<script>
|
|
114
|
+
function calculateDistance(p1, p2) {
|
|
115
|
+
return Math.sqrt(Math.pow(p1.axisX - p2.axisX, 2) + Math.pow(p1.axisY - p2.axisY, 2));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseTime(timeStr) {
|
|
119
|
+
return new Date(timeStr.replace(/-/g, '/')).getTime();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function init(data) {
|
|
123
|
+
if (!data) return;
|
|
124
|
+
|
|
125
|
+
document.getElementById('username').innerText = (data.name || 'Unknown') + ' 的今日行驶足迹';
|
|
126
|
+
if (data.smallAvatarUrl) {
|
|
127
|
+
document.getElementById('avatar').src = data.smallAvatarUrl;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const points = (data.points || []).filter(p => !(p.axisX === 0 && p.axisY === 0 && p.heading === 0));
|
|
131
|
+
// 使用传入的今日里程数据(米转千米)
|
|
132
|
+
const mileage = data.todayMileage || 0;
|
|
133
|
+
const km = (mileage / 1000).toFixed(1);
|
|
134
|
+
document.getElementById('distance').innerText = `${km} km`;
|
|
135
|
+
|
|
136
|
+
if (points.length === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const lines = [];
|
|
141
|
+
let currentLine = [];
|
|
142
|
+
|
|
143
|
+
if (points.length > 0) {
|
|
144
|
+
let first = points[0];
|
|
145
|
+
currentLine.push({ x: first.axisX, y: first.axisY });
|
|
146
|
+
|
|
147
|
+
for (let i = 1; i < points.length; i++) {
|
|
148
|
+
const prev = points[i - 1];
|
|
149
|
+
const curr = points[i];
|
|
150
|
+
|
|
151
|
+
let dist = calculateDistance(prev, curr);
|
|
152
|
+
dist = dist * 19;
|
|
153
|
+
const isDistJump = dist > 30000; // > 30km
|
|
154
|
+
|
|
155
|
+
let timeDiff = 0;
|
|
156
|
+
try {
|
|
157
|
+
timeDiff = (parseTime(curr.updateTime) - parseTime(prev.updateTime)) / 1000;
|
|
158
|
+
} catch (e) { }
|
|
159
|
+
|
|
160
|
+
const isTimeJump = timeDiff > 90;
|
|
161
|
+
const isServerJump = prev.serverId !== curr.serverId;
|
|
162
|
+
|
|
163
|
+
if (isDistJump || isTimeJump || isServerJump) {
|
|
164
|
+
if (currentLine.length > 0) lines.push(currentLine);
|
|
165
|
+
currentLine = [];
|
|
166
|
+
}
|
|
167
|
+
currentLine.push({ x: curr.axisX, y: curr.axisY });
|
|
168
|
+
}
|
|
169
|
+
if (currentLine.length > 0) lines.push(currentLine);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
render(lines, data.mapType);
|
|
173
|
+
}
|
|
174
|
+
function render(lines, mapType) {
|
|
175
|
+
const config = mapConfig[mapType];
|
|
176
|
+
|
|
177
|
+
// 边界
|
|
178
|
+
let bounds = L.latLngBounds(
|
|
179
|
+
map.unproject([0, config.bounds.y], config.maxZoom),
|
|
180
|
+
map.unproject([config.bounds.x, 0], config.maxZoom)
|
|
181
|
+
);
|
|
182
|
+
map.setMaxBounds(bounds);
|
|
183
|
+
|
|
184
|
+
// 瓦片
|
|
185
|
+
L.tileLayer(config.tileUrl, {
|
|
186
|
+
minZoom: 0,
|
|
187
|
+
maxZoom: 8,
|
|
188
|
+
minNativeZoom: 2,
|
|
189
|
+
maxNativeZoom: 8,
|
|
190
|
+
tileSize: 512,
|
|
191
|
+
bounds: bounds,
|
|
192
|
+
reuseTiles: true
|
|
193
|
+
}).addTo(map);
|
|
194
|
+
|
|
195
|
+
let allLatlngs = [];
|
|
196
|
+
|
|
197
|
+
lines.forEach(points => {
|
|
198
|
+
if (!points || points.length === 0) return;
|
|
199
|
+
|
|
200
|
+
let latlngs = [];
|
|
201
|
+
points.forEach(xy => {
|
|
202
|
+
let unprojected = map.unproject(config.calculateMapCoordinate(xy.x, xy.y), 8);
|
|
203
|
+
latlngs.push([unprojected.lat, unprojected.lng]);
|
|
204
|
+
allLatlngs.push([unprojected.lat, unprojected.lng]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 轨迹线
|
|
208
|
+
L.polyline(latlngs, {
|
|
209
|
+
color: '#3498db',
|
|
210
|
+
weight: 4,
|
|
211
|
+
opacity: 0.8,
|
|
212
|
+
lineJoin: 'round'
|
|
213
|
+
}).addTo(map);
|
|
214
|
+
|
|
215
|
+
// 起点
|
|
216
|
+
L.circleMarker(latlngs[0], {
|
|
217
|
+
radius: 4,
|
|
218
|
+
fillColor: '#2ecc71',
|
|
219
|
+
color: '#fff',
|
|
220
|
+
weight: 2,
|
|
221
|
+
fillOpacity: 1
|
|
222
|
+
}).addTo(map);
|
|
223
|
+
|
|
224
|
+
// 终点
|
|
225
|
+
L.circleMarker(latlngs[latlngs.length - 1], {
|
|
226
|
+
radius: 4,
|
|
227
|
+
fillColor: '#e74c3c',
|
|
228
|
+
color: '#fff',
|
|
229
|
+
weight: 2,
|
|
230
|
+
fillOpacity: 1
|
|
231
|
+
}).addTo(map);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 自动适应
|
|
235
|
+
if (allLatlngs.length > 0) {
|
|
236
|
+
map.fitBounds(L.latLngBounds(allLatlngs), { padding: [50, 50] });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
</script>
|
|
240
|
+
</body>
|
|
241
|
+
</html>
|