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.
Files changed (63) hide show
  1. package/lib/api/evmOpenApi.d.ts +45 -0
  2. package/lib/api/evmOpenApi.js +158 -0
  3. package/lib/api/truckersMpApi.d.ts +34 -0
  4. package/lib/api/truckersMpApi.js +110 -0
  5. package/lib/api/truckersMpMapApi.d.ts +6 -0
  6. package/lib/api/truckersMpMapApi.js +25 -0
  7. package/lib/api/truckyAppApi.d.ts +12 -0
  8. package/lib/api/truckyAppApi.js +32 -0
  9. package/lib/command/tmpBind.d.ts +2 -0
  10. package/lib/command/tmpBind.js +19 -0
  11. package/lib/command/tmpDlcMap.d.ts +3 -0
  12. package/lib/command/tmpDlcMap.js +33 -0
  13. package/lib/command/tmpFootprint.d.ts +3 -0
  14. package/lib/command/tmpFootprint.js +82 -0
  15. package/lib/command/tmpMileageRanking.d.ts +3 -0
  16. package/lib/command/tmpMileageRanking.js +55 -0
  17. package/lib/command/tmpPosition.d.ts +3 -0
  18. package/lib/command/tmpPosition.js +95 -0
  19. package/lib/command/tmpQuery.d.ts +2 -0
  20. package/lib/command/tmpQuery.js +148 -0
  21. package/lib/command/tmpServer/tmpServer.d.ts +2 -0
  22. package/lib/command/tmpServer/tmpServer.js +15 -0
  23. package/lib/command/tmpServer/tmpServerImg.d.ts +3 -0
  24. package/lib/command/tmpServer/tmpServerImg.js +35 -0
  25. package/lib/command/tmpServer/tmpServerText.d.ts +2 -0
  26. package/lib/command/tmpServer/tmpServerText.js +40 -0
  27. package/lib/command/tmpTraffic/tmpTraffic.d.ts +2 -0
  28. package/lib/command/tmpTraffic/tmpTraffic.js +15 -0
  29. package/lib/command/tmpTraffic/tmpTrafficMap.d.ts +3 -0
  30. package/lib/command/tmpTraffic/tmpTrafficMap.js +119 -0
  31. package/lib/command/tmpTraffic/tmpTrafficText.d.ts +2 -0
  32. package/lib/command/tmpTraffic/tmpTrafficText.js +122 -0
  33. package/lib/command/tmpUnbind.js +17 -0
  34. package/lib/command/tmpVersion.d.ts +2 -0
  35. package/lib/command/tmpVersion.js +31 -0
  36. package/lib/database/guildBind.d.ts +22 -0
  37. package/lib/database/guildBind.js +55 -0
  38. package/lib/database/model.d.ts +2 -0
  39. package/lib/database/model.js +65 -0
  40. package/lib/database/translateCache.d.ts +14 -0
  41. package/lib/database/translateCache.js +31 -0
  42. package/lib/index.d.ts +14 -0
  43. package/lib/index.js +59 -0
  44. package/lib/resource/dlc.html +115 -0
  45. package/lib/resource/footprint.html +241 -0
  46. package/lib/resource/mileage-leaderboard.html +363 -0
  47. package/lib/resource/package/SEGUIEMJ.TTF +0 -0
  48. package/lib/resource/package/ets-map.js +63 -0
  49. package/lib/resource/package/leaflet/heatmap.min.js +9 -0
  50. package/lib/resource/package/leaflet/leaflet-heatmap.js +246 -0
  51. package/lib/resource/package/leaflet/leaflet.min.css +1 -0
  52. package/lib/resource/package/leaflet/leaflet.min.js +1 -0
  53. package/lib/resource/position.html +229 -0
  54. package/lib/resource/server-list.html +309 -0
  55. package/lib/resource/traffic.html +207 -0
  56. package/lib/util/baiduTranslate.d.ts +2 -0
  57. package/lib/util/baiduTranslate.js +30 -0
  58. package/lib/util/common.d.ts +1 -0
  59. package/lib/util/common.js +5 -0
  60. package/lib/util/constant.d.ts +19 -0
  61. package/lib/util/constant.js +36 -0
  62. package/package.json +48 -0
  63. 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,2 @@
1
+ declare function _exports(ctx: any): void;
2
+ export = _exports;
@@ -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>