koishi-plugin-ets2-tools-tmp 3.3.1 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/command/tmpFootprint.js +86 -11
- package/lib/resource/footprint.html +219 -123
- package/package.json +1 -1
|
@@ -6,6 +6,51 @@ const { PromodsIds, ServerType } = require('../util/constant')
|
|
|
6
6
|
const evmOpenApi = require('../api/evmOpenApi')
|
|
7
7
|
const guildBind = require('../database/guildBind')
|
|
8
8
|
|
|
9
|
+
// 足迹统计数据的开始收集日期(仅用于近一年足迹图片中的提示展示)
|
|
10
|
+
const FOOTPRINT_DATA_START_DATE = '2026-08-19'
|
|
11
|
+
|
|
12
|
+
// 轨迹分段规则与原页面逻辑一致:距离 > 30km / 间隔 > 90s / 跨服 判定为断点
|
|
13
|
+
// 在 Node 侧完成分段与抽稀,避免百万级点位全量传入浏览器导致渲染崩溃
|
|
14
|
+
function buildTrackSegments(points, isValid, minGap) {
|
|
15
|
+
const segments = []
|
|
16
|
+
let current = null
|
|
17
|
+
let lastKept = null
|
|
18
|
+
let prev = null
|
|
19
|
+
let prevTime = NaN
|
|
20
|
+
|
|
21
|
+
for (const p of points) {
|
|
22
|
+
if (!isValid(p)) continue
|
|
23
|
+
|
|
24
|
+
const currTime = Date.parse(String(p.updateTime || '').replace(/-/g, '/'))
|
|
25
|
+
|
|
26
|
+
if (!prev) {
|
|
27
|
+
current = [{ x: p.axisX, y: p.axisY }]
|
|
28
|
+
lastKept = p
|
|
29
|
+
} else {
|
|
30
|
+
const dist = Math.sqrt(Math.pow(p.axisX - prev.axisX, 2) + Math.pow(p.axisY - prev.axisY, 2)) * 19
|
|
31
|
+
const timeDiff = isNaN(currTime) || isNaN(prevTime) ? 0 : (currTime - prevTime) / 1000
|
|
32
|
+
|
|
33
|
+
if (dist > 30000 || timeDiff > 90 || p.serverId !== prev.serverId) {
|
|
34
|
+
// 断点:若上一点被抽稀丢弃,补回作为本段终点,保证线段完整
|
|
35
|
+
if (lastKept !== prev) current.push({ x: prev.axisX, y: prev.axisY })
|
|
36
|
+
segments.push(current)
|
|
37
|
+
current = [{ x: p.axisX, y: p.axisY }]
|
|
38
|
+
lastKept = p
|
|
39
|
+
} else if (Math.sqrt(Math.pow(p.axisX - lastKept.axisX, 2) + Math.pow(p.axisY - lastKept.axisY, 2)) >= minGap) {
|
|
40
|
+
current.push({ x: p.axisX, y: p.axisY })
|
|
41
|
+
lastKept = p
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
prev = p
|
|
45
|
+
prevTime = currTime
|
|
46
|
+
}
|
|
47
|
+
if (current && current.length) {
|
|
48
|
+
if (prev && lastKept !== prev) current.push({ x: prev.axisX, y: prev.axisY })
|
|
49
|
+
segments.push(current)
|
|
50
|
+
}
|
|
51
|
+
return segments
|
|
52
|
+
}
|
|
53
|
+
|
|
9
54
|
module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
10
55
|
if (!ctx.puppeteer) {
|
|
11
56
|
return '未启用 puppeteer 服务'
|
|
@@ -91,17 +136,33 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
91
136
|
return '查询玩家历史位置数据失败,请稍后重试'
|
|
92
137
|
}
|
|
93
138
|
|
|
94
|
-
//
|
|
139
|
+
// 过滤非对应服务器数据(单次遍历统计轨迹范围与有效点数,避免百万级数据产生额外数组拷贝)
|
|
95
140
|
const promodsIdSet = new Set(PromodsIds)
|
|
96
|
-
const
|
|
141
|
+
const isTargetServer = item => {
|
|
97
142
|
if (ServerType.ets === serverType) {
|
|
98
143
|
return !promodsIdSet.has(item.serverId)
|
|
99
144
|
} else if (ServerType.promods === serverType) {
|
|
100
145
|
return promodsIdSet.has(item.serverId)
|
|
101
146
|
}
|
|
102
147
|
return false
|
|
103
|
-
}
|
|
104
|
-
|
|
148
|
+
}
|
|
149
|
+
const isValid = item => isTargetServer(item) && !(item.axisX === 0 && item.axisY === 0 && item.heading === 0)
|
|
150
|
+
|
|
151
|
+
const history = mapPlayerHistory.data || []
|
|
152
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
|
|
153
|
+
let validCount = 0
|
|
154
|
+
let intervalDistance = 0
|
|
155
|
+
for (const item of history) {
|
|
156
|
+
if (!isValid(item)) continue
|
|
157
|
+
validCount++
|
|
158
|
+
// 累加该区间内的行驶距离(API distance 字段,单位米)
|
|
159
|
+
intervalDistance += item.distance || 0
|
|
160
|
+
if (item.axisX < minX) minX = item.axisX
|
|
161
|
+
if (item.axisX > maxX) maxX = item.axisX
|
|
162
|
+
if (item.axisY < minY) minY = item.axisY
|
|
163
|
+
if (item.axisY > maxY) maxY = item.axisY
|
|
164
|
+
}
|
|
165
|
+
if (validCount === 0) {
|
|
105
166
|
if (date === 'yesterday') {
|
|
106
167
|
return `昨日暂无数据`;
|
|
107
168
|
}
|
|
@@ -111,22 +172,36 @@ module.exports = async (ctx, session, serverType, tmpId, date) => {
|
|
|
111
172
|
if (date === 'sevenday') {
|
|
112
173
|
return `近七日暂无数据`;
|
|
113
174
|
}
|
|
114
|
-
if (date === '
|
|
115
|
-
return
|
|
175
|
+
if (date === 'month') {
|
|
176
|
+
return `本月暂无数据`;
|
|
116
177
|
}
|
|
117
|
-
if (date === '
|
|
118
|
-
return
|
|
178
|
+
if (date === 'year') {
|
|
179
|
+
return `本年暂无数据`;
|
|
119
180
|
}
|
|
120
181
|
return '暂无数据';
|
|
121
182
|
}
|
|
122
183
|
|
|
123
|
-
//
|
|
184
|
+
// 抽稀阈值自适应:按轨迹对角线的 1/350 取值(800px 宽图片约 2px 精度),下限 5(约 95 米)
|
|
185
|
+
// 近一年足迹可达百万级点位,经此抽稀后传入浏览器的仅剩数千点,渲染不会崩溃
|
|
186
|
+
const diagonal = Math.sqrt(Math.pow(maxX - minX, 2) + Math.pow(maxY - minY, 2))
|
|
187
|
+
const minGap = Math.max(5, diagonal / 350)
|
|
188
|
+
|
|
189
|
+
// 分段 + 抽稀
|
|
190
|
+
const segments = buildTrackSegments(history, isValid, minGap)
|
|
191
|
+
|
|
192
|
+
// 拼接数据(今日足迹展示今日里程;其余展示该区间里程(distance 累加)与玩家总里程)
|
|
193
|
+
const isToday = date === 'today'
|
|
124
194
|
let data = {
|
|
125
195
|
mapType: ServerType.promods === serverType ? 'promods' : 'ets',
|
|
126
196
|
name: playerInfo.data.name,
|
|
127
197
|
smallAvatarUrl: playerInfo.data.smallAvatarUrl,
|
|
128
|
-
|
|
129
|
-
|
|
198
|
+
rangeText: { today: '今日', yesterday: '昨日', sevenday: '近七日', month: '近一个月', year: '近一年' }[date] || '',
|
|
199
|
+
isToday,
|
|
200
|
+
mileageLabel: isToday ? '今日里程' : '该区间里程',
|
|
201
|
+
mileage: isToday ? playerInfo.data.todayMileage : intervalDistance,
|
|
202
|
+
totalMileage: isToday ? null : playerInfo.data.mileage,
|
|
203
|
+
dataStartDate: date === 'year' ? FOOTPRINT_DATA_START_DATE : null,
|
|
204
|
+
segments
|
|
130
205
|
}
|
|
131
206
|
|
|
132
207
|
let page
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="zh-CN">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<title>玩家足迹展示</title>
|
|
@@ -19,77 +19,161 @@
|
|
|
19
19
|
}
|
|
20
20
|
#container {
|
|
21
21
|
width: 800px;
|
|
22
|
-
height:
|
|
23
|
-
background: #
|
|
22
|
+
height: 800px;
|
|
23
|
+
background: #0b0f1a;
|
|
24
24
|
overflow: hidden;
|
|
25
25
|
position: relative;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
#map-box, #map {
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
}
|
|
31
|
+
#map {
|
|
32
|
+
background-color: rgba(0, 0, 0, 0.25);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* 氛围层:暗角 + 上下压暗,纯装饰悬浮层,不参与坐标换算 */
|
|
36
|
+
.atmo {
|
|
28
37
|
position: absolute;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
inset: 0;
|
|
39
|
+
z-index: 500;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
background:
|
|
42
|
+
radial-gradient(140% 100% at 50% 40%, rgba(3, 7, 18, 0) 55%, rgba(3, 7, 18, .38) 100%),
|
|
43
|
+
linear-gradient(to top, rgba(3, 7, 18, .5), transparent 26%),
|
|
44
|
+
linear-gradient(to bottom, rgba(3, 7, 18, .35), transparent 18%);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.glass {
|
|
48
|
+
background: linear-gradient(135deg, rgba(13, 20, 38, .82), rgba(9, 13, 26, .66));
|
|
49
|
+
border: 1px solid rgba(125, 211, 252, .22);
|
|
50
|
+
backdrop-filter: blur(14px);
|
|
51
|
+
-webkit-backdrop-filter: blur(14px);
|
|
52
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .06);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* 顶部玩家卡片 */
|
|
56
|
+
.header {
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 16px;
|
|
59
|
+
left: 16px;
|
|
60
|
+
z-index: 1001;
|
|
36
61
|
display: flex;
|
|
37
62
|
align-items: center;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
42
|
-
font-size: 13px;
|
|
43
|
-
box-sizing: border-box;
|
|
63
|
+
gap: 12px;
|
|
64
|
+
padding: 10px 20px 10px 12px;
|
|
65
|
+
border-radius: 14px;
|
|
44
66
|
}
|
|
45
|
-
.
|
|
46
|
-
width:
|
|
47
|
-
height:
|
|
48
|
-
border-radius:
|
|
49
|
-
border: 1px solid rgba(
|
|
50
|
-
|
|
67
|
+
.header .avatar {
|
|
68
|
+
width: 46px;
|
|
69
|
+
height: 46px;
|
|
70
|
+
border-radius: 11px;
|
|
71
|
+
border: 1px solid rgba(148, 197, 255, .5);
|
|
72
|
+
box-shadow: 0 0 0 3px rgba(34, 211, 238, .12);
|
|
51
73
|
}
|
|
52
|
-
.
|
|
53
|
-
|
|
74
|
+
.header .kicker {
|
|
75
|
+
font-size: 11px;
|
|
76
|
+
letter-spacing: 3px;
|
|
77
|
+
color: #7dd3fc;
|
|
78
|
+
margin-bottom: 3px;
|
|
79
|
+
}
|
|
80
|
+
.header .name {
|
|
81
|
+
font-size: 19px;
|
|
82
|
+
font-weight: 800;
|
|
83
|
+
color: #eaf2ff;
|
|
84
|
+
line-height: 1.2;
|
|
85
|
+
max-width: 320px;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
text-overflow: ellipsis;
|
|
88
|
+
white-space: nowrap;
|
|
89
|
+
text-shadow: 0 2px 10px rgba(56, 189, 248, .35);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* 底部状态栏 */
|
|
93
|
+
.status-bar {
|
|
94
|
+
position: absolute;
|
|
95
|
+
left: 16px;
|
|
96
|
+
right: 16px;
|
|
97
|
+
bottom: 14px;
|
|
98
|
+
z-index: 1001;
|
|
54
99
|
display: flex;
|
|
55
100
|
align-items: center;
|
|
101
|
+
gap: 18px;
|
|
102
|
+
padding: 11px 20px;
|
|
103
|
+
border-radius: 14px;
|
|
104
|
+
box-sizing: border-box;
|
|
56
105
|
}
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
106
|
+
.mileage-block {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
gap: 2px;
|
|
110
|
+
min-width: 110px;
|
|
111
|
+
}
|
|
112
|
+
.mileage-block .label {
|
|
113
|
+
font-size: 11px;
|
|
114
|
+
letter-spacing: 2px;
|
|
115
|
+
color: #8fa3c8;
|
|
116
|
+
}
|
|
117
|
+
.mileage-block .value {
|
|
118
|
+
font-size: 21px;
|
|
119
|
+
font-weight: 800;
|
|
120
|
+
background: linear-gradient(90deg, #4ade80, #a3e635);
|
|
121
|
+
-webkit-background-clip: text;
|
|
122
|
+
background-clip: text;
|
|
123
|
+
color: transparent;
|
|
124
|
+
}
|
|
125
|
+
/* 区间里程用青色渐变,与轨迹线颜色呼应 */
|
|
126
|
+
.mileage-block .value.cyan {
|
|
127
|
+
background: linear-gradient(90deg, #22d3ee, #a5f3fc);
|
|
128
|
+
-webkit-background-clip: text;
|
|
129
|
+
background-clip: text;
|
|
60
130
|
}
|
|
61
|
-
.
|
|
131
|
+
.v-divider {
|
|
132
|
+
width: 1px;
|
|
133
|
+
height: 30px;
|
|
134
|
+
background: rgba(255, 255, 255, .1);
|
|
135
|
+
}
|
|
136
|
+
.legend {
|
|
62
137
|
display: flex;
|
|
63
138
|
align-items: center;
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
.status-bar .stats .label {
|
|
67
|
-
margin-right: 6px;
|
|
139
|
+
gap: 14px;
|
|
68
140
|
}
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
141
|
+
.legend .item {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 6px;
|
|
145
|
+
font-size: 11.5px;
|
|
146
|
+
color: #9fb3d9;
|
|
72
147
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
148
|
+
.legend .line-sample {
|
|
149
|
+
width: 20px;
|
|
150
|
+
height: 4px;
|
|
151
|
+
border-radius: 2px;
|
|
152
|
+
background: #22d3ee;
|
|
153
|
+
box-shadow: 0 0 8px rgba(34, 211, 238, .8);
|
|
77
154
|
}
|
|
78
|
-
|
|
79
|
-
width:
|
|
80
|
-
height:
|
|
81
|
-
|
|
155
|
+
.legend .dot-sample {
|
|
156
|
+
width: 9px;
|
|
157
|
+
height: 9px;
|
|
158
|
+
border-radius: 50%;
|
|
159
|
+
border: 2px solid #fff;
|
|
160
|
+
box-sizing: border-box;
|
|
82
161
|
}
|
|
162
|
+
.legend .start { background: #4ade80; }
|
|
163
|
+
.legend .end { background: #f87171; }
|
|
83
164
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
color: #
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
165
|
+
/* 数据说明(近一年足迹时显示统计开始日期) */
|
|
166
|
+
.note {
|
|
167
|
+
margin-left: auto;
|
|
168
|
+
font-size: 11px;
|
|
169
|
+
color: #fcd34d;
|
|
170
|
+
background: rgba(251, 191, 36, .1);
|
|
171
|
+
border: 1px solid rgba(251, 191, 36, .32);
|
|
172
|
+
padding: 5px 12px;
|
|
173
|
+
border-radius: 8px;
|
|
174
|
+
letter-spacing: .5px;
|
|
92
175
|
}
|
|
176
|
+
.hidden { display: none; }
|
|
93
177
|
</style>
|
|
94
178
|
</head>
|
|
95
179
|
<body>
|
|
@@ -97,84 +181,78 @@
|
|
|
97
181
|
<div id="map-box">
|
|
98
182
|
<div id="map"></div>
|
|
99
183
|
</div>
|
|
100
|
-
<div class="
|
|
184
|
+
<div class="atmo"></div>
|
|
185
|
+
|
|
186
|
+
<div class="header glass">
|
|
101
187
|
<img class="avatar" id="avatar" src="https://static.truckersmp.com/avatarsN/small/defaultavatar.png" alt="avatar"/>
|
|
102
|
-
<div
|
|
188
|
+
<div>
|
|
189
|
+
<div class="kicker" id="range-title">行驶足迹</div>
|
|
103
190
|
<div class="name" id="username">测试玩家</div>
|
|
104
191
|
</div>
|
|
105
|
-
|
|
106
|
-
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div class="status-bar glass">
|
|
195
|
+
<div class="mileage-block">
|
|
196
|
+
<span class="label" id="mileage-label">今日里程</span>
|
|
107
197
|
<span class="value" id="distance">0.0 km</span>
|
|
108
198
|
</div>
|
|
199
|
+
<div class="v-divider" id="total-divider"></div>
|
|
200
|
+
<div class="mileage-block" id="total-mileage-block">
|
|
201
|
+
<span class="label">总里程</span>
|
|
202
|
+
<span class="value" id="total-distance">0.0 km</span>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="v-divider"></div>
|
|
205
|
+
<div class="legend">
|
|
206
|
+
<div class="item"><span class="line-sample"></span>行驶轨迹</div>
|
|
207
|
+
<div class="item"><span class="dot-sample start"></span>起点</div>
|
|
208
|
+
<div class="item"><span class="dot-sample end"></span>终点</div>
|
|
209
|
+
</div>
|
|
210
|
+
<div class="note hidden" id="stats-note">足迹统计开始日期</div>
|
|
109
211
|
</div>
|
|
110
212
|
</div>
|
|
111
213
|
|
|
112
214
|
<script src="./package/ets-map.js"></script>
|
|
113
215
|
<script>
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
}
|
|
216
|
+
function init(data) {
|
|
217
|
+
if (!data) return;
|
|
117
218
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
219
|
+
document.getElementById('username').innerText = data.name || 'Unknown';
|
|
220
|
+
document.getElementById('range-title').innerText = `${data.rangeText || ''} · 行驶足迹`;
|
|
221
|
+
if (data.smallAvatarUrl) {
|
|
222
|
+
document.getElementById('avatar').src = data.smallAvatarUrl;
|
|
223
|
+
}
|
|
154
224
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
225
|
+
// 里程(米转千米):今日足迹显示今日里程;
|
|
226
|
+
// 其余显示该区间里程(API distance 字段累加)与玩家总里程
|
|
227
|
+
document.getElementById('mileage-label').innerText = data.mileageLabel || '今日里程';
|
|
228
|
+
const mileage = data.mileage || 0;
|
|
229
|
+
const km = Number((mileage / 1000).toFixed(1)).toLocaleString('zh-CN');
|
|
230
|
+
document.getElementById('distance').innerText = `${km} km`;
|
|
159
231
|
|
|
160
|
-
|
|
161
|
-
|
|
232
|
+
if (data.isToday === false) {
|
|
233
|
+
document.getElementById('distance').classList.add('cyan');
|
|
234
|
+
const totalKm = Number(((data.totalMileage || 0) / 1000).toFixed(1)).toLocaleString('zh-CN');
|
|
235
|
+
document.getElementById('total-distance').innerText = `${totalKm} km`;
|
|
236
|
+
} else {
|
|
237
|
+
// 今日足迹不显示总里程块
|
|
238
|
+
document.getElementById('total-mileage-block').classList.add('hidden');
|
|
239
|
+
document.getElementById('total-divider').classList.add('hidden');
|
|
240
|
+
}
|
|
162
241
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (currentLine.length > 0) lines.push(currentLine);
|
|
170
|
-
}
|
|
242
|
+
// 近一年足迹显示统计开始日期
|
|
243
|
+
const noteEl = document.getElementById('stats-note');
|
|
244
|
+
if (data.dataStartDate) {
|
|
245
|
+
noteEl.innerText = `足迹统计开始日期:${data.dataStartDate}`;
|
|
246
|
+
noteEl.classList.remove('hidden');
|
|
247
|
+
}
|
|
171
248
|
|
|
172
|
-
|
|
173
|
-
|
|
249
|
+
// 轨迹的分段(断点判定)与抽稀已在 Node 侧完成,这里直接渲染
|
|
250
|
+
render(data.segments || [], data.mapType);
|
|
251
|
+
}
|
|
174
252
|
function render(lines, mapType) {
|
|
175
253
|
const config = mapConfig[mapType];
|
|
176
254
|
|
|
177
|
-
//
|
|
255
|
+
// 边界(坐标系换算逻辑保持原样,勿动)
|
|
178
256
|
let bounds = L.latLngBounds(
|
|
179
257
|
map.unproject([0, config.bounds.y], config.maxZoom),
|
|
180
258
|
map.unproject([config.bounds.x, 0], config.maxZoom)
|
|
@@ -192,6 +270,9 @@
|
|
|
192
270
|
reuseTiles: true
|
|
193
271
|
}).addTo(map);
|
|
194
272
|
|
|
273
|
+
// Canvas 渲染器:大量分段与标记时性能远优于默认 SVG
|
|
274
|
+
const canvasRenderer = L.canvas({ padding: 0.3 });
|
|
275
|
+
|
|
195
276
|
let allLatlngs = [];
|
|
196
277
|
|
|
197
278
|
lines.forEach(points => {
|
|
@@ -204,18 +285,29 @@
|
|
|
204
285
|
allLatlngs.push([unprojected.lat, unprojected.lng]);
|
|
205
286
|
});
|
|
206
287
|
|
|
207
|
-
//
|
|
288
|
+
// 底层光晕线 + 主线,双层描边实现发光效果
|
|
289
|
+
L.polyline(latlngs, {
|
|
290
|
+
renderer: canvasRenderer,
|
|
291
|
+
color: '#0e7490',
|
|
292
|
+
weight: 9,
|
|
293
|
+
opacity: 0.35,
|
|
294
|
+
lineJoin: 'round',
|
|
295
|
+
lineCap: 'round'
|
|
296
|
+
}).addTo(map);
|
|
208
297
|
L.polyline(latlngs, {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
298
|
+
renderer: canvasRenderer,
|
|
299
|
+
color: '#22d3ee',
|
|
300
|
+
weight: 3.5,
|
|
301
|
+
opacity: 1,
|
|
302
|
+
lineJoin: 'round',
|
|
303
|
+
lineCap: 'round'
|
|
213
304
|
}).addTo(map);
|
|
214
305
|
|
|
215
306
|
// 起点
|
|
216
307
|
L.circleMarker(latlngs[0], {
|
|
217
|
-
|
|
218
|
-
|
|
308
|
+
renderer: canvasRenderer,
|
|
309
|
+
radius: 4.5,
|
|
310
|
+
fillColor: '#4ade80',
|
|
219
311
|
color: '#fff',
|
|
220
312
|
weight: 2,
|
|
221
313
|
fillOpacity: 1
|
|
@@ -223,17 +315,21 @@
|
|
|
223
315
|
|
|
224
316
|
// 终点
|
|
225
317
|
L.circleMarker(latlngs[latlngs.length - 1], {
|
|
226
|
-
|
|
227
|
-
|
|
318
|
+
renderer: canvasRenderer,
|
|
319
|
+
radius: 4.5,
|
|
320
|
+
fillColor: '#f87171',
|
|
228
321
|
color: '#fff',
|
|
229
322
|
weight: 2,
|
|
230
323
|
fillOpacity: 1
|
|
231
324
|
}).addTo(map);
|
|
232
325
|
});
|
|
233
326
|
|
|
234
|
-
//
|
|
327
|
+
// 自动适应(预留上下 UI 卡片区域;容器为正方形,尽量让正方形瓦片图占满)
|
|
235
328
|
if (allLatlngs.length > 0) {
|
|
236
|
-
map.fitBounds(L.latLngBounds(allLatlngs), {
|
|
329
|
+
map.fitBounds(L.latLngBounds(allLatlngs), {
|
|
330
|
+
paddingTopLeft: [60, 95],
|
|
331
|
+
paddingBottomRight: [60, 65]
|
|
332
|
+
});
|
|
237
333
|
}
|
|
238
334
|
}
|
|
239
335
|
</script>
|