uni-oaview 1.9.12 → 1.9.13
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/components/oa-vconsole/native-event.vue +246 -157
- package/package.json +1 -1
|
@@ -6,31 +6,37 @@
|
|
|
6
6
|
</view>
|
|
7
7
|
<view class="toolbar-clear" @click="handleClear">清除</view>
|
|
8
8
|
</view>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
|
|
10
|
+
<scroll-view v-if="filteredNativeEventLogMetas.length" scroll-y class="log-list">
|
|
11
|
+
<view v-for="meta in filteredNativeEventLogMetas" :key="meta.id" class="log-item">
|
|
12
|
+
<view class="log-header" @click="toggleExpand(meta.id)">
|
|
13
|
+
<text class="log-title">{{ meta.title }}</text>
|
|
14
|
+
<text class="log-arrow">{{ isExpanded(meta.id) ? '▼' : '▶' }}</text>
|
|
15
|
+
</view>
|
|
16
|
+
|
|
17
|
+
<view v-if="isExpanded(meta.id)" class="log-content">
|
|
18
|
+
<view class="log-actions">
|
|
19
|
+
<text class="log-copy" @click="handleCopy(meta)">复制</text>
|
|
20
|
+
</view>
|
|
21
|
+
<view class="log-section">
|
|
22
|
+
<text class="log-label">发送数据:</text>
|
|
23
|
+
<awesome-display-info :log="getNativeEventDetail(meta.id)?.params" />
|
|
24
|
+
</view>
|
|
25
|
+
<view class="log-section">
|
|
26
|
+
<text class="log-label">接收数据:</text>
|
|
27
|
+
<awesome-display-info :log="getNativeEventDetail(meta.id)?.response" />
|
|
14
28
|
</view>
|
|
15
|
-
<template v-if="isExpanded(meta.id)">
|
|
16
|
-
<view style="word-break: break-all; padding: 0 15px">
|
|
17
|
-
<text :selectable="true" style="font-weight: bolder">发送数据:</text>
|
|
18
|
-
<awesome-display-info :log="getNativeEventDetail(meta.id)?.params" />
|
|
19
|
-
</view>
|
|
20
|
-
<view style="word-break: break-all; padding: 0 15px">
|
|
21
|
-
<text :selectable="true" style="font-weight: bolder">接收数据:</text>
|
|
22
|
-
<awesome-display-info :log="getNativeEventDetail(meta.id)?.response" />
|
|
23
|
-
</view>
|
|
24
|
-
</template>
|
|
25
29
|
</view>
|
|
26
|
-
</
|
|
27
|
-
</
|
|
30
|
+
</view>
|
|
31
|
+
</scroll-view>
|
|
32
|
+
|
|
28
33
|
<view v-else class="empty-state">
|
|
29
34
|
{{ emptyText }}
|
|
30
35
|
</view>
|
|
31
36
|
</template>
|
|
37
|
+
|
|
32
38
|
<script lang="ts" setup>
|
|
33
|
-
import { computed,
|
|
39
|
+
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue';
|
|
34
40
|
import awesomeDisplayInfo from './awesome-display-info.vue';
|
|
35
41
|
import { nativeEventSubject, getNativeEventLogs } from 'uniapp-log-sdk';
|
|
36
42
|
|
|
@@ -48,28 +54,31 @@
|
|
|
48
54
|
title: string;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
const MAX_NATIVE_EVENT_LOGS =
|
|
52
|
-
const
|
|
57
|
+
const MAX_NATIVE_EVENT_LOGS = 200;
|
|
58
|
+
const FLUSH_DELAY_FOREGROUND = 16;
|
|
59
|
+
const FLUSH_DELAY_BACKGROUND = 100;
|
|
60
|
+
const MAX_EXPANDED_ITEMS = 20; // 限制最大展开项数
|
|
53
61
|
|
|
54
|
-
const collapseRef = ref<{ resize?: () => void } | null>(null);
|
|
55
|
-
const activeNames = ref<string[] | string>([]);
|
|
56
62
|
const keyword = ref('');
|
|
57
63
|
const clearTimestamp = ref<number | null>(null);
|
|
58
64
|
const hasCleared = ref(false);
|
|
65
|
+
const isInBackground = ref(false);
|
|
66
|
+
|
|
59
67
|
const nativeEventDetailMap = shallowRef<Map<string, NativeEventLog>>(new Map());
|
|
60
68
|
const nativeEventLogMetas = ref<NativeEventLogMeta[]>([]);
|
|
69
|
+
const expandedIds = ref<Set<string>>(new Set());
|
|
61
70
|
|
|
62
|
-
let pendingLogs: NativeEventLog[] | null = null;
|
|
63
71
|
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
72
|
+
let pendingLogs: NativeEventLog[] | null = null;
|
|
73
|
+
|
|
74
|
+
// 订阅和监听器取消函数
|
|
75
|
+
let unsubscribeLogs: (() => void) | null = null;
|
|
76
|
+
const cleanupFunctions: (() => void)[] = [];
|
|
64
77
|
|
|
65
78
|
const buildNativeEventLogId = (log: NativeEventLog): string => {
|
|
66
79
|
return `${log.startTime}|${log.endTime}|${log.key}`;
|
|
67
80
|
};
|
|
68
81
|
|
|
69
|
-
const buildNativeEventLogMatchKey = (log: NativeEventLog): string => {
|
|
70
|
-
return `${log.startTime}|${log.key}`;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
82
|
const formatStartTime = (timestamp: number): string => {
|
|
74
83
|
const date = new Date(timestamp);
|
|
75
84
|
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(
|
|
@@ -93,40 +102,23 @@
|
|
|
93
102
|
};
|
|
94
103
|
};
|
|
95
104
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const isExpanded = (id: string): boolean => expandedIds.value.has(id);
|
|
106
|
+
|
|
107
|
+
const toggleExpand = (id: string): void => {
|
|
108
|
+
const newSet = new Set(expandedIds.value);
|
|
109
|
+
if (newSet.has(id)) {
|
|
110
|
+
newSet.delete(id);
|
|
111
|
+
} else {
|
|
112
|
+
newSet.add(id);
|
|
113
|
+
// 限制展开项数量,防止内存泄漏
|
|
114
|
+
if (newSet.size > MAX_EXPANDED_ITEMS) {
|
|
115
|
+
const firstId = newSet.values().next().value;
|
|
116
|
+
newSet.delete(firstId);
|
|
117
|
+
}
|
|
108
118
|
}
|
|
109
|
-
|
|
110
|
-
const migratedIds = activeList
|
|
111
|
-
.map((id) => {
|
|
112
|
-
if (nextMetaIdByMatchKey.has(id)) return id;
|
|
113
|
-
const previousDetail = previousDetailMap.get(id);
|
|
114
|
-
if (!previousDetail) return null;
|
|
115
|
-
return nextMetaIdByMatchKey.get(buildNativeEventLogMatchKey(previousDetail)) ?? null;
|
|
116
|
-
})
|
|
117
|
-
.filter((id): id is string => Boolean(id));
|
|
118
|
-
|
|
119
|
-
if (Array.isArray(activeNames.value)) return migratedIds;
|
|
120
|
-
return migratedIds[0] ?? '';
|
|
119
|
+
expandedIds.value = newSet;
|
|
121
120
|
};
|
|
122
121
|
|
|
123
|
-
const getActiveNameList = (): string[] => {
|
|
124
|
-
const value = activeNames.value;
|
|
125
|
-
return Array.isArray(value) ? value : value ? [value] : [];
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const isExpanded = (id: string): boolean => getActiveNameList().includes(id);
|
|
129
|
-
|
|
130
122
|
const getNativeEventDetail = (id: string): NativeEventLog | null => {
|
|
131
123
|
return nativeEventDetailMap.value.get(id) ?? null;
|
|
132
124
|
};
|
|
@@ -143,17 +135,20 @@
|
|
|
143
135
|
}
|
|
144
136
|
};
|
|
145
137
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
if (!normalizedKeyword) return true;
|
|
138
|
+
const matchesKeyword = (meta: NativeEventLogMeta, keywordValue: string): boolean => {
|
|
139
|
+
if (!keywordValue) return true;
|
|
149
140
|
const detail = getNativeEventDetail(meta.id);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
141
|
+
const searchText = `${meta.title} ${meta.key} ${safeStringify(detail?.params)} ${safeStringify(
|
|
142
|
+
detail?.response,
|
|
143
|
+
)}`.toLowerCase();
|
|
144
|
+
return searchText.includes(keywordValue);
|
|
154
145
|
};
|
|
155
146
|
|
|
156
|
-
const filteredNativeEventLogMetas = computed(() =>
|
|
147
|
+
const filteredNativeEventLogMetas = computed(() => {
|
|
148
|
+
const normalizedKeyword = normalizeKeyword(keyword.value);
|
|
149
|
+
if (!normalizedKeyword) return nativeEventLogMetas.value;
|
|
150
|
+
return nativeEventLogMetas.value.filter((meta) => matchesKeyword(meta, normalizedKeyword));
|
|
151
|
+
});
|
|
157
152
|
|
|
158
153
|
const emptyText = computed(() => {
|
|
159
154
|
if (keyword.value) return '无匹配结果';
|
|
@@ -161,7 +156,28 @@
|
|
|
161
156
|
return '暂无日志';
|
|
162
157
|
});
|
|
163
158
|
|
|
164
|
-
const
|
|
159
|
+
const handleCopy = (meta: NativeEventLogMeta) => {
|
|
160
|
+
const detail = getNativeEventDetail(meta.id);
|
|
161
|
+
const text = JSON.stringify(
|
|
162
|
+
{
|
|
163
|
+
key: detail?.key ?? meta.key,
|
|
164
|
+
params: detail?.params,
|
|
165
|
+
response: detail?.response,
|
|
166
|
+
startTime: detail?.startTime,
|
|
167
|
+
endTime: detail?.endTime,
|
|
168
|
+
},
|
|
169
|
+
null,
|
|
170
|
+
2,
|
|
171
|
+
);
|
|
172
|
+
uni.setClipboardData({
|
|
173
|
+
data: text,
|
|
174
|
+
success: () => {
|
|
175
|
+
uni.showToast({ title: '已复制', icon: 'success' });
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const upsertLogs = (logs: NativeEventLog[]): void => {
|
|
165
181
|
const visibleLogs = clearTimestamp.value ? logs.filter((log) => log.startTime > clearTimestamp.value!) : logs;
|
|
166
182
|
|
|
167
183
|
if (
|
|
@@ -177,43 +193,65 @@
|
|
|
177
193
|
if (!visibleLogs.length && logs.length && clearTimestamp.value) {
|
|
178
194
|
nativeEventDetailMap.value = new Map();
|
|
179
195
|
nativeEventLogMetas.value = [];
|
|
180
|
-
|
|
196
|
+
expandedIds.value = new Set();
|
|
181
197
|
return;
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
const nextLogs = visibleLogs.slice(-MAX_NATIVE_EVENT_LOGS);
|
|
185
|
-
const
|
|
186
|
-
const detailMap = new Map<string, NativeEventLog>();
|
|
187
|
-
const nextMetas: NativeEventLogMeta[] = [];
|
|
201
|
+
const detailMap = nativeEventDetailMap.value;
|
|
188
202
|
|
|
203
|
+
const nextMetas: NativeEventLogMeta[] = [];
|
|
189
204
|
for (const log of nextLogs) {
|
|
190
205
|
const id = buildNativeEventLogId(log);
|
|
191
206
|
detailMap.set(id, log);
|
|
192
207
|
nextMetas.push(buildMetaFromLog(log));
|
|
193
208
|
}
|
|
194
209
|
|
|
195
|
-
|
|
210
|
+
// 创建新的 Map 触发响应式更新
|
|
211
|
+
nativeEventDetailMap.value = new Map(detailMap);
|
|
196
212
|
nativeEventLogMetas.value = nextMetas;
|
|
197
|
-
|
|
213
|
+
|
|
214
|
+
// 清理过期的详情数据
|
|
215
|
+
const keepIds = new Set(nextMetas.map((m) => m.id));
|
|
216
|
+
for (const key of detailMap.keys()) {
|
|
217
|
+
if (!keepIds.has(key)) detailMap.delete(key);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 清理已不存在的展开项
|
|
221
|
+
const newExpandedIds = new Set(expandedIds.value);
|
|
222
|
+
for (const id of newExpandedIds) {
|
|
223
|
+
if (!keepIds.has(id)) {
|
|
224
|
+
newExpandedIds.delete(id);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
expandedIds.value = newExpandedIds;
|
|
198
228
|
};
|
|
199
229
|
|
|
200
230
|
const flushPendingLogs = (): void => {
|
|
201
231
|
flushTimer = null;
|
|
202
232
|
if (!pendingLogs) return;
|
|
203
|
-
applyLogs(pendingLogs);
|
|
204
|
-
pendingLogs = null;
|
|
205
|
-
};
|
|
206
233
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
234
|
+
if (isInBackground.value) {
|
|
235
|
+
flushTimer = setTimeout(flushPendingLogs, FLUSH_DELAY_BACKGROUND);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const logs = pendingLogs;
|
|
240
|
+
pendingLogs = null;
|
|
241
|
+
upsertLogs(logs);
|
|
212
242
|
};
|
|
213
243
|
|
|
214
244
|
const enqueueLogs = (logs: NativeEventLog[]): void => {
|
|
215
245
|
pendingLogs = logs;
|
|
216
|
-
|
|
246
|
+
|
|
247
|
+
if (!flushTimer) {
|
|
248
|
+
const delay = isInBackground.value ? FLUSH_DELAY_BACKGROUND : FLUSH_DELAY_FOREGROUND;
|
|
249
|
+
flushTimer = setTimeout(flushPendingLogs, delay);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const handleClearKeyword = (): void => {
|
|
254
|
+
keyword.value = '';
|
|
217
255
|
};
|
|
218
256
|
|
|
219
257
|
const handleClear = (): void => {
|
|
@@ -221,81 +259,91 @@
|
|
|
221
259
|
hasCleared.value = true;
|
|
222
260
|
nativeEventLogMetas.value = [];
|
|
223
261
|
nativeEventDetailMap.value = new Map();
|
|
224
|
-
|
|
262
|
+
expandedIds.value = new Set();
|
|
263
|
+
keyword.value = '';
|
|
225
264
|
};
|
|
226
265
|
|
|
227
|
-
const
|
|
228
|
-
|
|
266
|
+
const cleanupTimers = (): void => {
|
|
267
|
+
if (flushTimer) {
|
|
268
|
+
clearTimeout(flushTimer);
|
|
269
|
+
flushTimer = null;
|
|
270
|
+
}
|
|
271
|
+
pendingLogs = null;
|
|
229
272
|
};
|
|
230
273
|
|
|
231
|
-
|
|
274
|
+
const setupLifecycleListeners = () => {
|
|
275
|
+
// #ifdef MP
|
|
276
|
+
const handleAppShow = () => {
|
|
277
|
+
if (isInBackground.value) {
|
|
278
|
+
isInBackground.value = false;
|
|
279
|
+
enqueueLogs(getNativeEventLogs());
|
|
280
|
+
}
|
|
281
|
+
};
|
|
232
282
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
283
|
+
const handleAppHide = () => {
|
|
284
|
+
isInBackground.value = true;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// #ifdef MP-WEIXIN
|
|
288
|
+
if (typeof wx !== 'undefined' && wx.onAppShow) {
|
|
289
|
+
wx.onAppShow(handleAppShow);
|
|
290
|
+
}
|
|
291
|
+
if (typeof wx !== 'undefined' && wx.onAppHide) {
|
|
292
|
+
wx.onAppHide(handleAppHide);
|
|
293
|
+
}
|
|
294
|
+
// #endif
|
|
236
295
|
|
|
237
|
-
|
|
238
|
-
(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
},
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
watch(
|
|
247
|
-
() => nativeEventLogMetas.value,
|
|
248
|
-
() => {
|
|
249
|
-
// #ifdef MP
|
|
250
|
-
if (getActiveNameList().length) nextTick(() => collapseRef.value?.resize?.());
|
|
251
|
-
// #endif
|
|
252
|
-
},
|
|
253
|
-
{ deep: false },
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
watch(keyword, () => {
|
|
257
|
-
const keepIds = new Set(filteredNativeEventLogMetas.value.map((meta) => meta.id));
|
|
258
|
-
const currentActive = activeNames.value;
|
|
259
|
-
if (Array.isArray(currentActive)) {
|
|
260
|
-
activeNames.value = currentActive.filter((id) => keepIds.has(id));
|
|
261
|
-
} else if (currentActive && !keepIds.has(currentActive)) {
|
|
262
|
-
activeNames.value = '';
|
|
296
|
+
// #ifdef MP-ALIPAY
|
|
297
|
+
if (typeof my !== 'undefined' && my.onAppShow) {
|
|
298
|
+
my.onAppShow(handleAppShow);
|
|
299
|
+
}
|
|
300
|
+
if (typeof my !== 'undefined' && my.onAppHide) {
|
|
301
|
+
my.onAppHide(handleAppHide);
|
|
263
302
|
}
|
|
303
|
+
// #endif
|
|
264
304
|
|
|
305
|
+
// uni.onAppShow 返回取消订阅函数
|
|
265
306
|
// #ifdef MP
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
307
|
+
try {
|
|
308
|
+
const offAppShow = uni.onAppShow?.(handleAppShow);
|
|
309
|
+
const offAppHide = uni.onAppHide?.(handleAppHide);
|
|
269
310
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
311
|
+
if (offAppShow) cleanupFunctions.push(offAppShow);
|
|
312
|
+
if (offAppHide) cleanupFunctions.push(offAppHide);
|
|
313
|
+
} catch (e) {
|
|
314
|
+
// 某些平台可能不支持,忽略错误
|
|
274
315
|
}
|
|
275
|
-
|
|
276
|
-
|
|
316
|
+
// #endif
|
|
317
|
+
// #endif
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// 初始化
|
|
321
|
+
enqueueLogs(getNativeEventLogs().filter((log): log is NativeEventLog => Boolean(log)));
|
|
322
|
+
|
|
323
|
+
// 订阅日志更新
|
|
324
|
+
const sub = nativeEventSubject.subscribe((data: NativeEventLog[]) => {
|
|
325
|
+
enqueueLogs(data);
|
|
277
326
|
});
|
|
278
327
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
uni.showToast({ title: '已复制', icon: 'success' });
|
|
296
|
-
},
|
|
328
|
+
unsubscribeLogs = () => sub.unsubscribe();
|
|
329
|
+
|
|
330
|
+
setupLifecycleListeners();
|
|
331
|
+
|
|
332
|
+
onBeforeUnmount(() => {
|
|
333
|
+
// 清理所有订阅和监听
|
|
334
|
+
unsubscribeLogs?.();
|
|
335
|
+
cleanupTimers();
|
|
336
|
+
|
|
337
|
+
// 清理小程序生命周期监听
|
|
338
|
+
cleanupFunctions.forEach((fn) => {
|
|
339
|
+
try {
|
|
340
|
+
fn();
|
|
341
|
+
} catch (e) {
|
|
342
|
+
// 忽略清理错误
|
|
343
|
+
}
|
|
297
344
|
});
|
|
298
|
-
|
|
345
|
+
cleanupFunctions.length = 0;
|
|
346
|
+
});
|
|
299
347
|
</script>
|
|
300
348
|
|
|
301
349
|
<style lang="scss" scoped>
|
|
@@ -351,25 +399,66 @@
|
|
|
351
399
|
border-radius: 4px;
|
|
352
400
|
}
|
|
353
401
|
|
|
354
|
-
.
|
|
355
|
-
|
|
402
|
+
.log-list {
|
|
403
|
+
flex: 1;
|
|
404
|
+
max-height: 70vh;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.log-item {
|
|
408
|
+
border-bottom: 1px solid #e5e5e5;
|
|
409
|
+
background-color: #fff;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.log-header {
|
|
413
|
+
display: flex;
|
|
414
|
+
justify-content: space-between;
|
|
415
|
+
align-items: center;
|
|
416
|
+
padding: 12px 15px;
|
|
417
|
+
background-color: #f9f9f9;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.log-title {
|
|
421
|
+
font-size: 10px;
|
|
422
|
+
color: #333;
|
|
423
|
+
flex: 1;
|
|
424
|
+
word-break: break-all;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.log-arrow {
|
|
356
428
|
font-size: 12px;
|
|
357
429
|
color: #999;
|
|
358
|
-
|
|
430
|
+
margin-left: 8px;
|
|
359
431
|
}
|
|
360
432
|
|
|
361
|
-
.
|
|
433
|
+
.log-content {
|
|
434
|
+
padding: 10px 15px;
|
|
435
|
+
font-size: 10px;
|
|
436
|
+
background-color: #fff;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.log-actions {
|
|
440
|
+
text-align: right;
|
|
441
|
+
margin-bottom: 8px;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.log-copy {
|
|
445
|
+
color: #007aff;
|
|
362
446
|
font-size: 12px;
|
|
363
447
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
448
|
+
|
|
449
|
+
.log-section {
|
|
450
|
+
margin-bottom: 8px;
|
|
451
|
+
word-break: break-all;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.log-label {
|
|
455
|
+
font-weight: bolder;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.empty-state {
|
|
459
|
+
padding: 16px 0;
|
|
460
|
+
font-size: 12px;
|
|
461
|
+
color: #999;
|
|
462
|
+
text-align: center;
|
|
374
463
|
}
|
|
375
464
|
</style>
|