w-web-api 1.0.74 → 1.0.75
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/dist/css/{app.3a6c6dd2.css → app.e140ee15.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.955c81f0.js +1 -0
- package/dist/js/app.955c81f0.js +1 -0
- package/dist/js/{chunk-vendors.516b8a46.js → chunk-vendors-legacy.cc9f5247.js} +2 -2
- package/dist/js/{chunk-vendors-legacy.516b8a46.js → chunk-vendors.cc9f5247.js} +2 -2
- package/dist/w-web-api.umd.js +1 -1
- package/docs/WWebApi.html +1 -1
- package/docs/index.html +1 -1
- package/docs/server_WWebApi.mjs.html +1 -1
- package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
- package/package.json +3 -3
- package/src/components/LayoutContentStats.vue +485 -485
- package/dist/js/app-legacy.0999553a.js +0 -1
- package/dist/js/app.0999553a.js +0 -1
|
@@ -1,485 +1,485 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="stats-panel" :style="`height:${height}px; overflow-y:auto; padding:20px 24px; box-sizing:border-box;`">
|
|
3
|
-
|
|
4
|
-
<!-- 標題列 -->
|
|
5
|
-
<div style="margin-bottom:16px;">
|
|
6
|
-
<div style="font-size:1.4rem; font-weight:600; color:var(--c-1);">{{$t('statisticsInformation')}}</div>
|
|
7
|
-
<div style="font-size:0.875rem; color:var(--c-3); margin-top:4px;">{{$t('statisticsInformationDescription')}}</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<!-- 控制區(事件選擇 + 時間範圍;把各事件分出來、可區分選取,供趨勢辨認 / 危險識別) -->
|
|
11
|
-
<div class="ctrl">
|
|
12
|
-
|
|
13
|
-
<!-- 第一列:時間範圍 -->
|
|
14
|
-
<div class="ctrl-row">
|
|
15
|
-
<span class="ctrl-label">{{$t('timeRange')}}</span>
|
|
16
|
-
<select id="timeGroupSel" v-model="timeGroup" class="ctrl-select">
|
|
17
|
-
<option value="1hr">{{$t('selectItem1hr')}}</option>
|
|
18
|
-
<option value="4hr">{{$t('selectItem4hr')}}</option>
|
|
19
|
-
<option value="8hr">{{$t('selectItem8hr')}}</option>
|
|
20
|
-
<option value="1day">{{$t('selectItem1day')}}</option>
|
|
21
|
-
</select>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<!-- 第二列:選擇事件 標題 + 全選/清除 -->
|
|
25
|
-
<div class="ctrl-row" style="margin-top:12px;">
|
|
26
|
-
<span class="ctrl-label">{{$t('selectEvents')}}</span>
|
|
27
|
-
<button class="evt-sel-btn stats-sel-all" @click="selectAllEvents">{{$t('selectAll')}}</button>
|
|
28
|
-
<button class="evt-sel-btn stats-sel-none" @click="selectNoneEvents">{{$t('selectNone')}}</button>
|
|
29
|
-
<span class="evt-count">{{selectedEvents.length}}/{{allEvents.length}}</span>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<!-- 第三列:事件 chip(CSS grid 等寬欄對齊,不 ragged wrap) -->
|
|
33
|
-
<div class="evt-grid">
|
|
34
|
-
<label
|
|
35
|
-
v-for="ev in allEvents"
|
|
36
|
-
:key="ev"
|
|
37
|
-
class="evt-chip"
|
|
38
|
-
:class="{on: selectedEvents.includes(ev)}"
|
|
39
|
-
:data-event="ev"
|
|
40
|
-
>
|
|
41
|
-
<input type="checkbox" class="stats-evt-cb" :value="ev" v-model="selectedEvents" />
|
|
42
|
-
<span class="evt-dot" :style="`background:${colorOf(ev)};`"></span>
|
|
43
|
-
<span class="evt-name">{{ev}}</span>
|
|
44
|
-
</label>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<!-- loading -->
|
|
50
|
-
<div v-if="loading" style="padding:12px 0; font-size:0.875rem; color:var(--c-3);">
|
|
51
|
-
{{$t('waitingData')}}
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<!-- 錯誤 -->
|
|
55
|
-
<div v-else-if="errMsg" style="padding:12px 0; font-size:0.875rem; color:var(--danger);">
|
|
56
|
-
{{errMsg}}
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<template v-else>
|
|
60
|
-
|
|
61
|
-
<!-- 圖表區(event 展示區;各選取事件各一系列、各自顏色,可區分趨勢) -->
|
|
62
|
-
<div class="stats-chart-area">
|
|
63
|
-
<div
|
|
64
|
-
v-if="hasNoData"
|
|
65
|
-
style="display:flex; align-items:center; justify-content:center; color:var(--c-3); font-size:0.875rem;"
|
|
66
|
-
:style="`height:${chartHeight}px;`"
|
|
67
|
-
>
|
|
68
|
-
{{$t('noStaData')}}
|
|
69
|
-
</div>
|
|
70
|
-
<WEchartsVue
|
|
71
|
-
v-else
|
|
72
|
-
:options="chartOption"
|
|
73
|
-
:style="`width:100%; height:${chartHeight}px;`"
|
|
74
|
-
></WEchartsVue>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<!-- 事件統計表(各事件為列,依最近1日總數多→少排序;欄位 1日/8時/4時/1時) -->
|
|
78
|
-
<div class="stats-table-area">
|
|
79
|
-
<div style="font-size:1rem; font-weight:600; color:var(--c-1); margin:18px 0 8px;">{{$t('eventStatsTable')}}</div>
|
|
80
|
-
<div v-if="eventRows.length === 0" style="font-size:0.875rem; color:var(--c-3); padding:8px 0;">
|
|
81
|
-
{{$t('noStaData')}}
|
|
82
|
-
</div>
|
|
83
|
-
<table v-else class="stats-table">
|
|
84
|
-
<thead>
|
|
85
|
-
<tr>
|
|
86
|
-
<th style="text-align:left;">{{$t('colEvent')}}</th>
|
|
87
|
-
<th>{{$t('colRecent1day')}}</th>
|
|
88
|
-
<th>{{$t('colRecent8hr')}}</th>
|
|
89
|
-
<th>{{$t('colRecent4hr')}}</th>
|
|
90
|
-
<th>{{$t('colRecent1hr')}}</th>
|
|
91
|
-
</tr>
|
|
92
|
-
</thead>
|
|
93
|
-
<tbody>
|
|
94
|
-
<tr v-for="row in eventRows" :key="row.event" :data-event="row.event">
|
|
95
|
-
<td style="text-align:left;">
|
|
96
|
-
<span class="evt-dot" :style="`background:${colorOf(row.event)};`"></span>{{row.event}}
|
|
97
|
-
</td>
|
|
98
|
-
<td>{{row.d1day}}</td>
|
|
99
|
-
<td>{{row.d8hr}}</td>
|
|
100
|
-
<td>{{row.d4hr}}</td>
|
|
101
|
-
<td>{{row.d1hr}}</td>
|
|
102
|
-
</tr>
|
|
103
|
-
</tbody>
|
|
104
|
-
</table>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
</template>
|
|
108
|
-
|
|
109
|
-
</div>
|
|
110
|
-
</template>
|
|
111
|
-
|
|
112
|
-
<script>
|
|
113
|
-
import ot from 'dayjs'
|
|
114
|
-
import get from 'lodash-es/get.js'
|
|
115
|
-
import map from 'lodash-es/map.js'
|
|
116
|
-
import keys from 'lodash-es/keys.js'
|
|
117
|
-
import groupBy from 'lodash-es/groupBy.js'
|
|
118
|
-
import sumBy from 'lodash-es/sumBy.js'
|
|
119
|
-
import reduce from 'lodash-es/reduce.js'
|
|
120
|
-
import size from 'lodash-es/size.js'
|
|
121
|
-
import isearr from 'wsemi/src/isearr.mjs'
|
|
122
|
-
import WEchartsVue from 'w-echarts-vue/src/components/WEchartsVue.vue'
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
//固定色票(依事件在 allEvents 之索引取色,使同一事件顏色穩定、圖表與表格與 chip 一致)
|
|
126
|
-
let COLORS = [
|
|
127
|
-
'#3b82f6', '#ef4444', '#10b981', '#f97316', '#8b5cf6',
|
|
128
|
-
'#6366f1', '#eab308', '#ec4899', '#14b8a6', '#a855f7',
|
|
129
|
-
'#0ea5e9', '#f43f5e', '#22c55e', '#d97706', '#7c3aed',
|
|
130
|
-
]
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
export default {
|
|
134
|
-
components: {
|
|
135
|
-
WEchartsVue,
|
|
136
|
-
},
|
|
137
|
-
props: {
|
|
138
|
-
height: {
|
|
139
|
-
type: Number,
|
|
140
|
-
default: 400,
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
data: function() {
|
|
144
|
-
return {
|
|
145
|
-
freq: [],
|
|
146
|
-
selectedEvents: [], //使用者選取要畫的事件(預設載入後全選)
|
|
147
|
-
eventsInited: false, //是否已用實際資料初始化過 selectedEvents
|
|
148
|
-
timeGroup: '1hr',
|
|
149
|
-
loading: false,
|
|
150
|
-
errMsg: '',
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
mounted: function() {
|
|
154
|
-
let vo = this
|
|
155
|
-
vo.load()
|
|
156
|
-
},
|
|
157
|
-
computed: {
|
|
158
|
-
|
|
159
|
-
chartHeight: function() {
|
|
160
|
-
let vo = this
|
|
161
|
-
let h = vo.height - 360 //扣標題 + 控制列(含事件選擇) + 表格區估算
|
|
162
|
-
return h < 220 ? 220 : h
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
//所有出現過的事件名(union,排除 count),固定排序
|
|
166
|
-
allEvents: function() {
|
|
167
|
-
let vo = this
|
|
168
|
-
let kp = reduce(vo.freq, function(acc, item) {
|
|
169
|
-
let d = get(item, 'data', {})
|
|
170
|
-
keys(d).forEach(function(k) {
|
|
171
|
-
if (k !== 'count') {
|
|
172
|
-
acc[k] = true
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
return acc
|
|
176
|
-
}, {})
|
|
177
|
-
return keys(kp).sort()
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
//時間重採樣(1hr 不變;4hr/8hr/1day 同日合併)— 供圖表 x 軸
|
|
181
|
-
resampledData: function() {
|
|
182
|
-
let vo = this
|
|
183
|
-
let arr = vo.freq
|
|
184
|
-
if (!isearr(arr)) {
|
|
185
|
-
return []
|
|
186
|
-
}
|
|
187
|
-
let tg = vo.timeGroup
|
|
188
|
-
if (tg === '1hr') {
|
|
189
|
-
return arr
|
|
190
|
-
}
|
|
191
|
-
let groupHours = 4
|
|
192
|
-
if (tg === '8hr') {
|
|
193
|
-
groupHours = 8
|
|
194
|
-
}
|
|
195
|
-
else if (tg === '1day') {
|
|
196
|
-
groupHours = 24
|
|
197
|
-
}
|
|
198
|
-
let dataKeyList = vo.allEvents.concat(['count'])
|
|
199
|
-
let gs = groupBy(arr, function(item) {
|
|
200
|
-
let t = ot(item.time)
|
|
201
|
-
let h = t.hour()
|
|
202
|
-
let hGroup = Math.floor(h / groupHours)
|
|
203
|
-
let tNew = t.startOf('day').add(hGroup * groupHours, 'hour')
|
|
204
|
-
return tNew.format()
|
|
205
|
-
})
|
|
206
|
-
return map(gs, function(vs, timeKey) {
|
|
207
|
-
let merged = { time: timeKey, data: {} }
|
|
208
|
-
dataKeyList.forEach(function(k) {
|
|
209
|
-
merged.data[k] = sumBy(vs, function(item) {
|
|
210
|
-
return get(item, `data.${k}`, 0)
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
return merged
|
|
214
|
-
})
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
//實際要畫的系列 = 選取且存在於資料中的事件
|
|
218
|
-
seriesKeys: function() {
|
|
219
|
-
let vo = this
|
|
220
|
-
return vo.selectedEvents.filter(function(e) {
|
|
221
|
-
return vo.allEvents.includes(e)
|
|
222
|
-
})
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
hasNoData: function() {
|
|
226
|
-
let vo = this
|
|
227
|
-
let arr = vo.freq
|
|
228
|
-
if (!isearr(arr) || size(arr) === 0) {
|
|
229
|
-
return true
|
|
230
|
-
}
|
|
231
|
-
let total = reduce(arr, function(sum, item) {
|
|
232
|
-
return sum + get(item, 'data.count', 0)
|
|
233
|
-
}, 0)
|
|
234
|
-
return total === 0
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
chartOption: function() {
|
|
238
|
-
let vo = this
|
|
239
|
-
let data = vo.resampledData
|
|
240
|
-
let tg = vo.timeGroup
|
|
241
|
-
let sKeys = vo.seriesKeys
|
|
242
|
-
|
|
243
|
-
let formatX = tg === '1day' ? 'MM/DD' : 'MM/DD\nHH:mm'
|
|
244
|
-
let totalBars = size(sKeys) || 1
|
|
245
|
-
let barWidthPct = Math.max(3, Math.floor(100 / (totalBars * 1.5))) + '%'
|
|
246
|
-
|
|
247
|
-
let series = map(sKeys, function(valueKey) {
|
|
248
|
-
return {
|
|
249
|
-
name: valueKey,
|
|
250
|
-
type: 'bar',
|
|
251
|
-
barWidth: barWidthPct,
|
|
252
|
-
data: map(data, function(item) {
|
|
253
|
-
return get(item, `data.${valueKey}`, 0)
|
|
254
|
-
}),
|
|
255
|
-
itemStyle: { color: vo.colorOf(valueKey) },
|
|
256
|
-
emphasis: { focus: 'series' },
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
262
|
-
legend: { show: true, type: 'scroll' },
|
|
263
|
-
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
|
264
|
-
xAxis: [{
|
|
265
|
-
type: 'category',
|
|
266
|
-
data: map(data, function(item) { return ot(item.time).format(formatX) }),
|
|
267
|
-
axisTick: { alignWithLabel: true },
|
|
268
|
-
}],
|
|
269
|
-
yAxis: [{ type: 'value' }],
|
|
270
|
-
series: series,
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
//事件統計表:各事件之最近 1日/8時/4時/1時 計數(由小時 bucket 加總最後 N 桶),依 1日 多→少排序
|
|
275
|
-
eventRows: function() {
|
|
276
|
-
let vo = this
|
|
277
|
-
let arr = isearr(vo.freq) ? vo.freq : []
|
|
278
|
-
//freq 由後端依時間遞增排序;取最後 N 個小時 bucket 對應「最近 N 小時」
|
|
279
|
-
let sumLastN = function(ev, n) {
|
|
280
|
-
let bs = arr.slice(-n)
|
|
281
|
-
return reduce(bs, function(s, item) {
|
|
282
|
-
return s + get(item, `data.${ev}`, 0)
|
|
283
|
-
}, 0)
|
|
284
|
-
}
|
|
285
|
-
let rows = vo.allEvents.map(function(ev) {
|
|
286
|
-
return {
|
|
287
|
-
event: ev,
|
|
288
|
-
d1day: sumLastN(ev, 24),
|
|
289
|
-
d8hr: sumLastN(ev, 8),
|
|
290
|
-
d4hr: sumLastN(ev, 4),
|
|
291
|
-
d1hr: sumLastN(ev, 1),
|
|
292
|
-
}
|
|
293
|
-
})
|
|
294
|
-
//依最近1日總數多→少排序(同數則 8時、4時、1時 依序 tie-break)
|
|
295
|
-
rows.sort(function(a, b) {
|
|
296
|
-
return (b.d1day - a.d1day) || (b.d8hr - a.d8hr) || (b.d4hr - a.d4hr) || (b.d1hr - a.d1hr)
|
|
297
|
-
})
|
|
298
|
-
return rows
|
|
299
|
-
},
|
|
300
|
-
|
|
301
|
-
},
|
|
302
|
-
watch: {
|
|
303
|
-
freq: function() {
|
|
304
|
-
let vo = this
|
|
305
|
-
//首次取得資料時,預設全選所有事件(使各事件一開始即分系列、可區分)
|
|
306
|
-
if (!vo.eventsInited && vo.allEvents.length > 0) {
|
|
307
|
-
vo.selectedEvents = vo.allEvents.slice()
|
|
308
|
-
vo.eventsInited = true
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
methods: {
|
|
313
|
-
|
|
314
|
-
colorOf: function(ev) {
|
|
315
|
-
let vo = this
|
|
316
|
-
let idx = vo.allEvents.indexOf(ev)
|
|
317
|
-
if (idx < 0) {
|
|
318
|
-
idx = 0
|
|
319
|
-
}
|
|
320
|
-
return COLORS[idx % COLORS.length]
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
selectAllEvents: function() {
|
|
324
|
-
let vo = this
|
|
325
|
-
vo.selectedEvents = vo.allEvents.slice()
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
selectNoneEvents: function() {
|
|
329
|
-
let vo = this
|
|
330
|
-
vo.selectedEvents = []
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
load: function() {
|
|
334
|
-
let vo = this
|
|
335
|
-
vo.loading = true
|
|
336
|
-
vo.errMsg = ''
|
|
337
|
-
vo.$fapi.getStaEvent(7, 'hr')
|
|
338
|
-
.then(function(rs) {
|
|
339
|
-
vo.freq = isearr(rs) ? rs : []
|
|
340
|
-
})
|
|
341
|
-
.catch(function(err) {
|
|
342
|
-
console.log('getStaEvent catch', err)
|
|
343
|
-
vo.errMsg = vo.$t('getDataError')
|
|
344
|
-
})
|
|
345
|
-
.finally(function() {
|
|
346
|
-
vo.loading = false
|
|
347
|
-
})
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
},
|
|
351
|
-
}
|
|
352
|
-
</script>
|
|
353
|
-
|
|
354
|
-
<style scoped>
|
|
355
|
-
|
|
356
|
-
.ctrl {
|
|
357
|
-
margin-bottom: 16px;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
.ctrl-row {
|
|
361
|
-
display: flex;
|
|
362
|
-
align-items: center;
|
|
363
|
-
gap: 8px;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
.ctrl-label {
|
|
367
|
-
font-size: 0.875rem;
|
|
368
|
-
color: var(--c-2);
|
|
369
|
-
width: 64px;
|
|
370
|
-
flex-shrink: 0;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
.ctrl-select {
|
|
374
|
-
border: 1px solid var(--border);
|
|
375
|
-
border-radius: var(--radius);
|
|
376
|
-
background: var(--bg-1);
|
|
377
|
-
color: var(--c-1);
|
|
378
|
-
font-size: 0.875rem;
|
|
379
|
-
padding: 4px 8px;
|
|
380
|
-
outline: none;
|
|
381
|
-
cursor: pointer;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.ctrl-select:focus {
|
|
385
|
-
border-color: var(--accent);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.evt-sel-btn {
|
|
389
|
-
border: 1px solid var(--border);
|
|
390
|
-
border-radius: var(--radius);
|
|
391
|
-
background: var(--bg-1);
|
|
392
|
-
color: var(--c-2);
|
|
393
|
-
font-size: 0.8rem;
|
|
394
|
-
padding: 3px 12px;
|
|
395
|
-
cursor: pointer;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
.evt-sel-btn:hover {
|
|
399
|
-
border-color: var(--accent);
|
|
400
|
-
color: var(--accent);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
.evt-count {
|
|
404
|
-
font-size: 0.78rem;
|
|
405
|
-
color: var(--c-3);
|
|
406
|
-
margin-left: 4px;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/* 事件 chip:等寬欄 grid 對齊,避免 ragged wrap 之縮排錯落 */
|
|
410
|
-
.evt-grid {
|
|
411
|
-
margin-top: 10px;
|
|
412
|
-
margin-left: 72px;
|
|
413
|
-
display: grid;
|
|
414
|
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
415
|
-
gap: 8px;
|
|
416
|
-
max-width: 1040px;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
.evt-chip {
|
|
420
|
-
display: flex;
|
|
421
|
-
align-items: center;
|
|
422
|
-
box-sizing: border-box;
|
|
423
|
-
border: 1px solid var(--border);
|
|
424
|
-
border-radius: var(--radius);
|
|
425
|
-
background: var(--bg-1);
|
|
426
|
-
padding: 5px 10px;
|
|
427
|
-
font-size: 0.82rem;
|
|
428
|
-
color: var(--c-2);
|
|
429
|
-
cursor: pointer;
|
|
430
|
-
transition: border-color .12s, background .12s, color .12s;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
.evt-chip:hover {
|
|
434
|
-
border-color: var(--accent);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
.evt-chip.on {
|
|
438
|
-
color: var(--c-1);
|
|
439
|
-
border-color: var(--accent);
|
|
440
|
-
background: rgba(0, 153, 255, 0.06);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.evt-chip .stats-evt-cb {
|
|
444
|
-
cursor: pointer;
|
|
445
|
-
margin: 0 6px 0 0;
|
|
446
|
-
flex-shrink: 0;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.evt-dot {
|
|
450
|
-
display: inline-block;
|
|
451
|
-
width: 9px;
|
|
452
|
-
height: 9px;
|
|
453
|
-
border-radius: 2px;
|
|
454
|
-
margin-right: 6px;
|
|
455
|
-
flex-shrink: 0;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.evt-name {
|
|
459
|
-
overflow: hidden;
|
|
460
|
-
text-overflow: ellipsis;
|
|
461
|
-
white-space: nowrap;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
.stats-table {
|
|
465
|
-
width: 100%;
|
|
466
|
-
border-collapse: collapse;
|
|
467
|
-
font-size: 0.85rem;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
.stats-table th,
|
|
471
|
-
.stats-table td {
|
|
472
|
-
border-bottom: 1px solid var(--border);
|
|
473
|
-
padding: 7px 10px;
|
|
474
|
-
text-align: right;
|
|
475
|
-
color: var(--c-1);
|
|
476
|
-
white-space: nowrap;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
.stats-table th {
|
|
480
|
-
color: var(--c-2);
|
|
481
|
-
font-weight: 600;
|
|
482
|
-
background: var(--bg-2);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="stats-panel" :style="`height:${height}px; overflow-y:auto; padding:20px 24px; box-sizing:border-box;`">
|
|
3
|
+
|
|
4
|
+
<!-- 標題列 -->
|
|
5
|
+
<div style="margin-bottom:16px;">
|
|
6
|
+
<div style="font-size:1.4rem; font-weight:600; color:var(--c-1);">{{$t('statisticsInformation')}}</div>
|
|
7
|
+
<div style="font-size:0.875rem; color:var(--c-3); margin-top:4px;">{{$t('statisticsInformationDescription')}}</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- 控制區(事件選擇 + 時間範圍;把各事件分出來、可區分選取,供趨勢辨認 / 危險識別) -->
|
|
11
|
+
<div class="ctrl">
|
|
12
|
+
|
|
13
|
+
<!-- 第一列:時間範圍 -->
|
|
14
|
+
<div class="ctrl-row">
|
|
15
|
+
<span class="ctrl-label">{{$t('timeRange')}}</span>
|
|
16
|
+
<select id="timeGroupSel" v-model="timeGroup" class="ctrl-select">
|
|
17
|
+
<option value="1hr">{{$t('selectItem1hr')}}</option>
|
|
18
|
+
<option value="4hr">{{$t('selectItem4hr')}}</option>
|
|
19
|
+
<option value="8hr">{{$t('selectItem8hr')}}</option>
|
|
20
|
+
<option value="1day">{{$t('selectItem1day')}}</option>
|
|
21
|
+
</select>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- 第二列:選擇事件 標題 + 全選/清除 -->
|
|
25
|
+
<div class="ctrl-row" style="margin-top:12px;">
|
|
26
|
+
<span class="ctrl-label">{{$t('selectEvents')}}</span>
|
|
27
|
+
<button class="evt-sel-btn stats-sel-all" @click="selectAllEvents">{{$t('selectAll')}}</button>
|
|
28
|
+
<button class="evt-sel-btn stats-sel-none" @click="selectNoneEvents">{{$t('selectNone')}}</button>
|
|
29
|
+
<span class="evt-count">{{selectedEvents.length}}/{{allEvents.length}}</span>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- 第三列:事件 chip(CSS grid 等寬欄對齊,不 ragged wrap) -->
|
|
33
|
+
<div class="evt-grid">
|
|
34
|
+
<label
|
|
35
|
+
v-for="ev in allEvents"
|
|
36
|
+
:key="ev"
|
|
37
|
+
class="evt-chip"
|
|
38
|
+
:class="{on: selectedEvents.includes(ev)}"
|
|
39
|
+
:data-event="ev"
|
|
40
|
+
>
|
|
41
|
+
<input type="checkbox" class="stats-evt-cb" :value="ev" v-model="selectedEvents" />
|
|
42
|
+
<span class="evt-dot" :style="`background:${colorOf(ev)};`"></span>
|
|
43
|
+
<span class="evt-name">{{ev}}</span>
|
|
44
|
+
</label>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- loading -->
|
|
50
|
+
<div v-if="loading" style="padding:12px 0; font-size:0.875rem; color:var(--c-3);">
|
|
51
|
+
{{$t('waitingData')}}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!-- 錯誤 -->
|
|
55
|
+
<div v-else-if="errMsg" style="padding:12px 0; font-size:0.875rem; color:var(--danger);">
|
|
56
|
+
{{errMsg}}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<template v-else>
|
|
60
|
+
|
|
61
|
+
<!-- 圖表區(event 展示區;各選取事件各一系列、各自顏色,可區分趨勢) -->
|
|
62
|
+
<div class="stats-chart-area">
|
|
63
|
+
<div
|
|
64
|
+
v-if="hasNoData"
|
|
65
|
+
style="display:flex; align-items:center; justify-content:center; color:var(--c-3); font-size:0.875rem;"
|
|
66
|
+
:style="`height:${chartHeight}px;`"
|
|
67
|
+
>
|
|
68
|
+
{{$t('noStaData')}}
|
|
69
|
+
</div>
|
|
70
|
+
<WEchartsVue
|
|
71
|
+
v-else
|
|
72
|
+
:options="chartOption"
|
|
73
|
+
:style="`width:100%; height:${chartHeight}px;`"
|
|
74
|
+
></WEchartsVue>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- 事件統計表(各事件為列,依最近1日總數多→少排序;欄位 1日/8時/4時/1時) -->
|
|
78
|
+
<div class="stats-table-area">
|
|
79
|
+
<div style="font-size:1rem; font-weight:600; color:var(--c-1); margin:18px 0 8px;">{{$t('eventStatsTable')}}</div>
|
|
80
|
+
<div v-if="eventRows.length === 0" style="font-size:0.875rem; color:var(--c-3); padding:8px 0;">
|
|
81
|
+
{{$t('noStaData')}}
|
|
82
|
+
</div>
|
|
83
|
+
<table v-else class="stats-table">
|
|
84
|
+
<thead>
|
|
85
|
+
<tr>
|
|
86
|
+
<th style="text-align:left;">{{$t('colEvent')}}</th>
|
|
87
|
+
<th>{{$t('colRecent1day')}}</th>
|
|
88
|
+
<th>{{$t('colRecent8hr')}}</th>
|
|
89
|
+
<th>{{$t('colRecent4hr')}}</th>
|
|
90
|
+
<th>{{$t('colRecent1hr')}}</th>
|
|
91
|
+
</tr>
|
|
92
|
+
</thead>
|
|
93
|
+
<tbody>
|
|
94
|
+
<tr v-for="row in eventRows" :key="row.event" :data-event="row.event">
|
|
95
|
+
<td style="text-align:left;">
|
|
96
|
+
<span class="evt-dot" :style="`background:${colorOf(row.event)};`"></span>{{row.event}}
|
|
97
|
+
</td>
|
|
98
|
+
<td>{{row.d1day}}</td>
|
|
99
|
+
<td>{{row.d8hr}}</td>
|
|
100
|
+
<td>{{row.d4hr}}</td>
|
|
101
|
+
<td>{{row.d1hr}}</td>
|
|
102
|
+
</tr>
|
|
103
|
+
</tbody>
|
|
104
|
+
</table>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
import ot from 'dayjs'
|
|
114
|
+
import get from 'lodash-es/get.js'
|
|
115
|
+
import map from 'lodash-es/map.js'
|
|
116
|
+
import keys from 'lodash-es/keys.js'
|
|
117
|
+
import groupBy from 'lodash-es/groupBy.js'
|
|
118
|
+
import sumBy from 'lodash-es/sumBy.js'
|
|
119
|
+
import reduce from 'lodash-es/reduce.js'
|
|
120
|
+
import size from 'lodash-es/size.js'
|
|
121
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
122
|
+
import WEchartsVue from 'w-echarts-vue/src/components/WEchartsVue.vue'
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
//固定色票(依事件在 allEvents 之索引取色,使同一事件顏色穩定、圖表與表格與 chip 一致)
|
|
126
|
+
let COLORS = [
|
|
127
|
+
'#3b82f6', '#ef4444', '#10b981', '#f97316', '#8b5cf6',
|
|
128
|
+
'#6366f1', '#eab308', '#ec4899', '#14b8a6', '#a855f7',
|
|
129
|
+
'#0ea5e9', '#f43f5e', '#22c55e', '#d97706', '#7c3aed',
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
export default {
|
|
134
|
+
components: {
|
|
135
|
+
WEchartsVue,
|
|
136
|
+
},
|
|
137
|
+
props: {
|
|
138
|
+
height: {
|
|
139
|
+
type: Number,
|
|
140
|
+
default: 400,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
data: function() {
|
|
144
|
+
return {
|
|
145
|
+
freq: [],
|
|
146
|
+
selectedEvents: [], //使用者選取要畫的事件(預設載入後全選)
|
|
147
|
+
eventsInited: false, //是否已用實際資料初始化過 selectedEvents
|
|
148
|
+
timeGroup: '1hr',
|
|
149
|
+
loading: false,
|
|
150
|
+
errMsg: '',
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
mounted: function() {
|
|
154
|
+
let vo = this
|
|
155
|
+
vo.load()
|
|
156
|
+
},
|
|
157
|
+
computed: {
|
|
158
|
+
|
|
159
|
+
chartHeight: function() {
|
|
160
|
+
let vo = this
|
|
161
|
+
let h = vo.height - 360 //扣標題 + 控制列(含事件選擇) + 表格區估算
|
|
162
|
+
return h < 220 ? 220 : h
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
//所有出現過的事件名(union,排除 count),固定排序
|
|
166
|
+
allEvents: function() {
|
|
167
|
+
let vo = this
|
|
168
|
+
let kp = reduce(vo.freq, function(acc, item) {
|
|
169
|
+
let d = get(item, 'data', {})
|
|
170
|
+
keys(d).forEach(function(k) {
|
|
171
|
+
if (k !== 'count') {
|
|
172
|
+
acc[k] = true
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
return acc
|
|
176
|
+
}, {})
|
|
177
|
+
return keys(kp).sort()
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
//時間重採樣(1hr 不變;4hr/8hr/1day 同日合併)— 供圖表 x 軸
|
|
181
|
+
resampledData: function() {
|
|
182
|
+
let vo = this
|
|
183
|
+
let arr = vo.freq
|
|
184
|
+
if (!isearr(arr)) {
|
|
185
|
+
return []
|
|
186
|
+
}
|
|
187
|
+
let tg = vo.timeGroup
|
|
188
|
+
if (tg === '1hr') {
|
|
189
|
+
return arr
|
|
190
|
+
}
|
|
191
|
+
let groupHours = 4
|
|
192
|
+
if (tg === '8hr') {
|
|
193
|
+
groupHours = 8
|
|
194
|
+
}
|
|
195
|
+
else if (tg === '1day') {
|
|
196
|
+
groupHours = 24
|
|
197
|
+
}
|
|
198
|
+
let dataKeyList = vo.allEvents.concat(['count'])
|
|
199
|
+
let gs = groupBy(arr, function(item) {
|
|
200
|
+
let t = ot(item.time)
|
|
201
|
+
let h = t.hour()
|
|
202
|
+
let hGroup = Math.floor(h / groupHours)
|
|
203
|
+
let tNew = t.startOf('day').add(hGroup * groupHours, 'hour')
|
|
204
|
+
return tNew.format()
|
|
205
|
+
})
|
|
206
|
+
return map(gs, function(vs, timeKey) {
|
|
207
|
+
let merged = { time: timeKey, data: {} }
|
|
208
|
+
dataKeyList.forEach(function(k) {
|
|
209
|
+
merged.data[k] = sumBy(vs, function(item) {
|
|
210
|
+
return get(item, `data.${k}`, 0)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
return merged
|
|
214
|
+
})
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
//實際要畫的系列 = 選取且存在於資料中的事件
|
|
218
|
+
seriesKeys: function() {
|
|
219
|
+
let vo = this
|
|
220
|
+
return vo.selectedEvents.filter(function(e) {
|
|
221
|
+
return vo.allEvents.includes(e)
|
|
222
|
+
})
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
hasNoData: function() {
|
|
226
|
+
let vo = this
|
|
227
|
+
let arr = vo.freq
|
|
228
|
+
if (!isearr(arr) || size(arr) === 0) {
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
let total = reduce(arr, function(sum, item) {
|
|
232
|
+
return sum + get(item, 'data.count', 0)
|
|
233
|
+
}, 0)
|
|
234
|
+
return total === 0
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
chartOption: function() {
|
|
238
|
+
let vo = this
|
|
239
|
+
let data = vo.resampledData
|
|
240
|
+
let tg = vo.timeGroup
|
|
241
|
+
let sKeys = vo.seriesKeys
|
|
242
|
+
|
|
243
|
+
let formatX = tg === '1day' ? 'MM/DD' : 'MM/DD\nHH:mm'
|
|
244
|
+
let totalBars = size(sKeys) || 1
|
|
245
|
+
let barWidthPct = Math.max(3, Math.floor(100 / (totalBars * 1.5))) + '%'
|
|
246
|
+
|
|
247
|
+
let series = map(sKeys, function(valueKey) {
|
|
248
|
+
return {
|
|
249
|
+
name: valueKey,
|
|
250
|
+
type: 'bar',
|
|
251
|
+
barWidth: barWidthPct,
|
|
252
|
+
data: map(data, function(item) {
|
|
253
|
+
return get(item, `data.${valueKey}`, 0)
|
|
254
|
+
}),
|
|
255
|
+
itemStyle: { color: vo.colorOf(valueKey) },
|
|
256
|
+
emphasis: { focus: 'series' },
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
262
|
+
legend: { show: true, type: 'scroll', top: 0 }, //top須明給: echarts 6 起 legend 預設由 top:0 改為 bottom, 不給會落到底部壓住 x 軸時間標籤
|
|
263
|
+
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
|
264
|
+
xAxis: [{
|
|
265
|
+
type: 'category',
|
|
266
|
+
data: map(data, function(item) { return ot(item.time).format(formatX) }),
|
|
267
|
+
axisTick: { alignWithLabel: true },
|
|
268
|
+
}],
|
|
269
|
+
yAxis: [{ type: 'value' }],
|
|
270
|
+
series: series,
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
//事件統計表:各事件之最近 1日/8時/4時/1時 計數(由小時 bucket 加總最後 N 桶),依 1日 多→少排序
|
|
275
|
+
eventRows: function() {
|
|
276
|
+
let vo = this
|
|
277
|
+
let arr = isearr(vo.freq) ? vo.freq : []
|
|
278
|
+
//freq 由後端依時間遞增排序;取最後 N 個小時 bucket 對應「最近 N 小時」
|
|
279
|
+
let sumLastN = function(ev, n) {
|
|
280
|
+
let bs = arr.slice(-n)
|
|
281
|
+
return reduce(bs, function(s, item) {
|
|
282
|
+
return s + get(item, `data.${ev}`, 0)
|
|
283
|
+
}, 0)
|
|
284
|
+
}
|
|
285
|
+
let rows = vo.allEvents.map(function(ev) {
|
|
286
|
+
return {
|
|
287
|
+
event: ev,
|
|
288
|
+
d1day: sumLastN(ev, 24),
|
|
289
|
+
d8hr: sumLastN(ev, 8),
|
|
290
|
+
d4hr: sumLastN(ev, 4),
|
|
291
|
+
d1hr: sumLastN(ev, 1),
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
//依最近1日總數多→少排序(同數則 8時、4時、1時 依序 tie-break)
|
|
295
|
+
rows.sort(function(a, b) {
|
|
296
|
+
return (b.d1day - a.d1day) || (b.d8hr - a.d8hr) || (b.d4hr - a.d4hr) || (b.d1hr - a.d1hr)
|
|
297
|
+
})
|
|
298
|
+
return rows
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
},
|
|
302
|
+
watch: {
|
|
303
|
+
freq: function() {
|
|
304
|
+
let vo = this
|
|
305
|
+
//首次取得資料時,預設全選所有事件(使各事件一開始即分系列、可區分)
|
|
306
|
+
if (!vo.eventsInited && vo.allEvents.length > 0) {
|
|
307
|
+
vo.selectedEvents = vo.allEvents.slice()
|
|
308
|
+
vo.eventsInited = true
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
methods: {
|
|
313
|
+
|
|
314
|
+
colorOf: function(ev) {
|
|
315
|
+
let vo = this
|
|
316
|
+
let idx = vo.allEvents.indexOf(ev)
|
|
317
|
+
if (idx < 0) {
|
|
318
|
+
idx = 0
|
|
319
|
+
}
|
|
320
|
+
return COLORS[idx % COLORS.length]
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
selectAllEvents: function() {
|
|
324
|
+
let vo = this
|
|
325
|
+
vo.selectedEvents = vo.allEvents.slice()
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
selectNoneEvents: function() {
|
|
329
|
+
let vo = this
|
|
330
|
+
vo.selectedEvents = []
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
load: function() {
|
|
334
|
+
let vo = this
|
|
335
|
+
vo.loading = true
|
|
336
|
+
vo.errMsg = ''
|
|
337
|
+
vo.$fapi.getStaEvent(7, 'hr')
|
|
338
|
+
.then(function(rs) {
|
|
339
|
+
vo.freq = isearr(rs) ? rs : []
|
|
340
|
+
})
|
|
341
|
+
.catch(function(err) {
|
|
342
|
+
console.log('getStaEvent catch', err)
|
|
343
|
+
vo.errMsg = vo.$t('getDataError')
|
|
344
|
+
})
|
|
345
|
+
.finally(function() {
|
|
346
|
+
vo.loading = false
|
|
347
|
+
})
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
},
|
|
351
|
+
}
|
|
352
|
+
</script>
|
|
353
|
+
|
|
354
|
+
<style scoped>
|
|
355
|
+
|
|
356
|
+
.ctrl {
|
|
357
|
+
margin-bottom: 16px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.ctrl-row {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: 8px;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.ctrl-label {
|
|
367
|
+
font-size: 0.875rem;
|
|
368
|
+
color: var(--c-2);
|
|
369
|
+
width: 64px;
|
|
370
|
+
flex-shrink: 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.ctrl-select {
|
|
374
|
+
border: 1px solid var(--border);
|
|
375
|
+
border-radius: var(--radius);
|
|
376
|
+
background: var(--bg-1);
|
|
377
|
+
color: var(--c-1);
|
|
378
|
+
font-size: 0.875rem;
|
|
379
|
+
padding: 4px 8px;
|
|
380
|
+
outline: none;
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.ctrl-select:focus {
|
|
385
|
+
border-color: var(--accent);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.evt-sel-btn {
|
|
389
|
+
border: 1px solid var(--border);
|
|
390
|
+
border-radius: var(--radius);
|
|
391
|
+
background: var(--bg-1);
|
|
392
|
+
color: var(--c-2);
|
|
393
|
+
font-size: 0.8rem;
|
|
394
|
+
padding: 3px 12px;
|
|
395
|
+
cursor: pointer;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.evt-sel-btn:hover {
|
|
399
|
+
border-color: var(--accent);
|
|
400
|
+
color: var(--accent);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.evt-count {
|
|
404
|
+
font-size: 0.78rem;
|
|
405
|
+
color: var(--c-3);
|
|
406
|
+
margin-left: 4px;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* 事件 chip:等寬欄 grid 對齊,避免 ragged wrap 之縮排錯落 */
|
|
410
|
+
.evt-grid {
|
|
411
|
+
margin-top: 10px;
|
|
412
|
+
margin-left: 72px;
|
|
413
|
+
display: grid;
|
|
414
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
415
|
+
gap: 8px;
|
|
416
|
+
max-width: 1040px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.evt-chip {
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
box-sizing: border-box;
|
|
423
|
+
border: 1px solid var(--border);
|
|
424
|
+
border-radius: var(--radius);
|
|
425
|
+
background: var(--bg-1);
|
|
426
|
+
padding: 5px 10px;
|
|
427
|
+
font-size: 0.82rem;
|
|
428
|
+
color: var(--c-2);
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
transition: border-color .12s, background .12s, color .12s;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.evt-chip:hover {
|
|
434
|
+
border-color: var(--accent);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.evt-chip.on {
|
|
438
|
+
color: var(--c-1);
|
|
439
|
+
border-color: var(--accent);
|
|
440
|
+
background: rgba(0, 153, 255, 0.06);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.evt-chip .stats-evt-cb {
|
|
444
|
+
cursor: pointer;
|
|
445
|
+
margin: 0 6px 0 0;
|
|
446
|
+
flex-shrink: 0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.evt-dot {
|
|
450
|
+
display: inline-block;
|
|
451
|
+
width: 9px;
|
|
452
|
+
height: 9px;
|
|
453
|
+
border-radius: 2px;
|
|
454
|
+
margin-right: 6px;
|
|
455
|
+
flex-shrink: 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.evt-name {
|
|
459
|
+
overflow: hidden;
|
|
460
|
+
text-overflow: ellipsis;
|
|
461
|
+
white-space: nowrap;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.stats-table {
|
|
465
|
+
width: 100%;
|
|
466
|
+
border-collapse: collapse;
|
|
467
|
+
font-size: 0.85rem;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.stats-table th,
|
|
471
|
+
.stats-table td {
|
|
472
|
+
border-bottom: 1px solid var(--border);
|
|
473
|
+
padding: 7px 10px;
|
|
474
|
+
text-align: right;
|
|
475
|
+
color: var(--c-1);
|
|
476
|
+
white-space: nowrap;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.stats-table th {
|
|
480
|
+
color: var(--c-2);
|
|
481
|
+
font-weight: 600;
|
|
482
|
+
background: var(--bg-2);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
</style>
|