uni-oaview 1.9.5 → 1.9.10
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.
|
@@ -1,46 +1,74 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
2
|
+
<view class="toolbar">
|
|
3
|
+
<view class="toolbar-input-wrap">
|
|
4
|
+
<input v-model="keyword" class="toolbar-input" placeholder="搜索事件名 / 参数 / 响应" confirm-type="search" />
|
|
5
|
+
<view v-if="keyword" class="toolbar-input-clear" @click="handleClearKeyword">×</view>
|
|
6
|
+
</view>
|
|
7
|
+
<view class="toolbar-clear" @click="handleClear">清除</view>
|
|
8
|
+
</view>
|
|
9
|
+
<uni-collapse v-if="filteredNativeEventLogMetas.length" ref="collapseRef" v-model="activeNames">
|
|
10
|
+
<uni-collapse-item v-for="meta in filteredNativeEventLogMetas" :key="meta.id" :name="meta.id" :title="meta.title">
|
|
4
11
|
<view style="font-size: 10px">
|
|
5
12
|
<view style="text-align: right; margin-bottom: 8px; padding: 0 15px">
|
|
6
|
-
<text style="color: #007aff; font-size: 12px" @click="handleCopy(
|
|
7
|
-
</view>
|
|
8
|
-
<view style="word-break: break-all; padding: 0 15px">
|
|
9
|
-
<text :selectable="true" style="font-weight: bolder">发送数据:</text>
|
|
10
|
-
<awesome-display-info :log="log.params" />
|
|
11
|
-
</view>
|
|
12
|
-
<view style="word-break: break-all; padding: 0 15px">
|
|
13
|
-
<text :selectable="true" style="font-weight: bolder">接收数据:</text>
|
|
14
|
-
<awesome-display-info :log="log.response" />
|
|
13
|
+
<text style="color: #007aff; font-size: 12px" @click="handleCopy(meta)">复制</text>
|
|
15
14
|
</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>
|
|
16
25
|
</view>
|
|
17
26
|
</uni-collapse-item>
|
|
18
27
|
</uni-collapse>
|
|
28
|
+
<view v-else class="empty-state">
|
|
29
|
+
{{ emptyText }}
|
|
30
|
+
</view>
|
|
19
31
|
</template>
|
|
20
32
|
<script lang="ts" setup>
|
|
21
|
-
import {
|
|
33
|
+
import { computed, nextTick, onBeforeUnmount, ref, shallowRef, watch } from 'vue';
|
|
22
34
|
import awesomeDisplayInfo from './awesome-display-info.vue';
|
|
23
35
|
import { nativeEventSubject, getNativeEventLogs } from 'uniapp-log-sdk';
|
|
24
36
|
|
|
25
37
|
interface NativeEventLog {
|
|
26
38
|
key: string;
|
|
27
|
-
params:
|
|
28
|
-
response:
|
|
39
|
+
params: unknown;
|
|
40
|
+
response: unknown;
|
|
29
41
|
startTime: number;
|
|
30
42
|
endTime: number;
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
interface NativeEventLogMeta {
|
|
46
|
+
id: string;
|
|
47
|
+
key: string;
|
|
48
|
+
title: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
const MAX_NATIVE_EVENT_LOGS = 50;
|
|
52
|
+
const FLUSH_DELAY = 16;
|
|
34
53
|
|
|
35
|
-
const
|
|
54
|
+
const collapseRef = ref<{ resize?: () => void } | null>(null);
|
|
55
|
+
const activeNames = ref<string[] | string>([]);
|
|
56
|
+
const keyword = ref('');
|
|
57
|
+
const clearTimestamp = ref<number | null>(null);
|
|
58
|
+
const hasCleared = ref(false);
|
|
59
|
+
const nativeEventDetailMap = shallowRef<Map<string, NativeEventLog>>(new Map());
|
|
60
|
+
const nativeEventLogMetas = ref<NativeEventLogMeta[]>([]);
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
let pendingLogs: NativeEventLog[] | null = null;
|
|
63
|
+
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
64
|
+
|
|
65
|
+
const buildNativeEventLogId = (log: NativeEventLog): string => {
|
|
66
|
+
return `${log.startTime}|${log.endTime}|${log.key}`;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const buildNativeEventLogMatchKey = (log: NativeEventLog): string => {
|
|
70
|
+
return `${log.startTime}|${log.key}`;
|
|
71
|
+
};
|
|
44
72
|
|
|
45
73
|
const formatStartTime = (timestamp: number): string => {
|
|
46
74
|
const date = new Date(timestamp);
|
|
@@ -57,18 +85,206 @@
|
|
|
57
85
|
return key;
|
|
58
86
|
};
|
|
59
87
|
|
|
88
|
+
const buildMetaFromLog = (log: NativeEventLog): NativeEventLogMeta => {
|
|
89
|
+
return {
|
|
90
|
+
id: buildNativeEventLogId(log),
|
|
91
|
+
key: log.key,
|
|
92
|
+
title: getTitle(log),
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const migrateActiveNames = (
|
|
97
|
+
previousDetailMap: Map<string, NativeEventLog>,
|
|
98
|
+
nextMetas: NativeEventLogMeta[],
|
|
99
|
+
): string[] | string => {
|
|
100
|
+
const activeList = getActiveNameList();
|
|
101
|
+
if (!activeList.length) return activeNames.value;
|
|
102
|
+
|
|
103
|
+
const nextMetaIdByMatchKey = new Map<string, string>();
|
|
104
|
+
for (const meta of nextMetas) {
|
|
105
|
+
const detail = nativeEventDetailMap.value.get(meta.id);
|
|
106
|
+
if (!detail) continue;
|
|
107
|
+
nextMetaIdByMatchKey.set(buildNativeEventLogMatchKey(detail), meta.id);
|
|
108
|
+
}
|
|
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] ?? '';
|
|
121
|
+
};
|
|
122
|
+
|
|
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
|
+
const getNativeEventDetail = (id: string): NativeEventLog | null => {
|
|
131
|
+
return nativeEventDetailMap.value.get(id) ?? null;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const normalizeKeyword = (value: string): string => value.trim().toLowerCase();
|
|
135
|
+
|
|
136
|
+
const safeStringify = (value: unknown): string => {
|
|
137
|
+
if (value === null || value === undefined) return '';
|
|
138
|
+
if (typeof value === 'string') return value;
|
|
139
|
+
try {
|
|
140
|
+
return JSON.stringify(value);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return String(value);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const matchesNativeEventLog = (meta: NativeEventLogMeta): boolean => {
|
|
147
|
+
const normalizedKeyword = normalizeKeyword(keyword.value);
|
|
148
|
+
if (!normalizedKeyword) return true;
|
|
149
|
+
const detail = getNativeEventDetail(meta.id);
|
|
150
|
+
return [meta.title, meta.key, safeStringify(detail?.params), safeStringify(detail?.response)]
|
|
151
|
+
.join(' ')
|
|
152
|
+
.toLowerCase()
|
|
153
|
+
.includes(normalizedKeyword);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const filteredNativeEventLogMetas = computed(() => nativeEventLogMetas.value.filter(matchesNativeEventLog));
|
|
157
|
+
|
|
158
|
+
const emptyText = computed(() => {
|
|
159
|
+
if (keyword.value) return '无匹配结果';
|
|
160
|
+
if (hasCleared.value) return '已清空';
|
|
161
|
+
return '暂无日志';
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const applyLogs = (logs: NativeEventLog[]): void => {
|
|
165
|
+
const visibleLogs = clearTimestamp.value ? logs.filter((log) => log.startTime > clearTimestamp.value!) : logs;
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
!visibleLogs.length &&
|
|
169
|
+
clearTimestamp.value &&
|
|
170
|
+
logs.length &&
|
|
171
|
+
logs[logs.length - 1].startTime < clearTimestamp.value
|
|
172
|
+
) {
|
|
173
|
+
clearTimestamp.value = null;
|
|
174
|
+
hasCleared.value = false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!visibleLogs.length && logs.length && clearTimestamp.value) {
|
|
178
|
+
nativeEventDetailMap.value = new Map();
|
|
179
|
+
nativeEventLogMetas.value = [];
|
|
180
|
+
activeNames.value = [];
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const nextLogs = visibleLogs.slice(-MAX_NATIVE_EVENT_LOGS);
|
|
185
|
+
const previousDetailMap = new Map(nativeEventDetailMap.value);
|
|
186
|
+
const detailMap = new Map<string, NativeEventLog>();
|
|
187
|
+
const nextMetas: NativeEventLogMeta[] = [];
|
|
188
|
+
|
|
189
|
+
for (const log of nextLogs) {
|
|
190
|
+
const id = buildNativeEventLogId(log);
|
|
191
|
+
detailMap.set(id, log);
|
|
192
|
+
nextMetas.push(buildMetaFromLog(log));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
nativeEventDetailMap.value = detailMap;
|
|
196
|
+
nativeEventLogMetas.value = nextMetas;
|
|
197
|
+
activeNames.value = migrateActiveNames(previousDetailMap, nextMetas);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const flushPendingLogs = (): void => {
|
|
201
|
+
flushTimer = null;
|
|
202
|
+
if (!pendingLogs) return;
|
|
203
|
+
applyLogs(pendingLogs);
|
|
204
|
+
pendingLogs = null;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const scheduleFlush = (): void => {
|
|
208
|
+
if (flushTimer) return;
|
|
209
|
+
flushTimer = setTimeout(() => {
|
|
210
|
+
flushPendingLogs();
|
|
211
|
+
}, FLUSH_DELAY);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const enqueueLogs = (logs: NativeEventLog[]): void => {
|
|
215
|
+
pendingLogs = logs;
|
|
216
|
+
scheduleFlush();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const handleClear = (): void => {
|
|
220
|
+
clearTimestamp.value = Date.now();
|
|
221
|
+
hasCleared.value = true;
|
|
222
|
+
nativeEventLogMetas.value = [];
|
|
223
|
+
nativeEventDetailMap.value = new Map();
|
|
224
|
+
activeNames.value = [];
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const handleClearKeyword = (): void => {
|
|
228
|
+
keyword.value = '';
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
applyLogs(getNativeEventLogs().filter((log): log is NativeEventLog => Boolean(log)));
|
|
232
|
+
|
|
233
|
+
const stop = nativeEventSubject.subscribe((data: NativeEventLog[]) => {
|
|
234
|
+
enqueueLogs(data);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
watch(
|
|
238
|
+
() => getActiveNameList().join('|'),
|
|
239
|
+
() => {
|
|
240
|
+
// #ifdef MP
|
|
241
|
+
nextTick(() => collapseRef.value?.resize?.());
|
|
242
|
+
// #endif
|
|
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 = '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// #ifdef MP
|
|
266
|
+
if (getActiveNameList().length) nextTick(() => collapseRef.value?.resize?.());
|
|
267
|
+
// #endif
|
|
268
|
+
});
|
|
269
|
+
|
|
60
270
|
onBeforeUnmount(() => {
|
|
271
|
+
if (flushTimer) {
|
|
272
|
+
clearTimeout(flushTimer);
|
|
273
|
+
flushTimer = null;
|
|
274
|
+
}
|
|
275
|
+
pendingLogs = null;
|
|
61
276
|
stop?.unsubscribe();
|
|
62
277
|
});
|
|
63
278
|
|
|
64
|
-
const handleCopy = (
|
|
279
|
+
const handleCopy = (meta: NativeEventLogMeta) => {
|
|
280
|
+
const detail = getNativeEventDetail(meta.id);
|
|
65
281
|
const text = JSON.stringify(
|
|
66
282
|
{
|
|
67
|
-
key:
|
|
68
|
-
params:
|
|
69
|
-
response:
|
|
70
|
-
startTime:
|
|
71
|
-
endTime:
|
|
283
|
+
key: detail?.key ?? meta.key,
|
|
284
|
+
params: detail?.params,
|
|
285
|
+
response: detail?.response,
|
|
286
|
+
startTime: detail?.startTime,
|
|
287
|
+
endTime: detail?.endTime,
|
|
72
288
|
},
|
|
73
289
|
null,
|
|
74
290
|
2,
|
|
@@ -83,6 +299,65 @@
|
|
|
83
299
|
</script>
|
|
84
300
|
|
|
85
301
|
<style lang="scss" scoped>
|
|
302
|
+
.toolbar {
|
|
303
|
+
position: sticky;
|
|
304
|
+
top: 0;
|
|
305
|
+
z-index: 1;
|
|
306
|
+
display: flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: 8px;
|
|
309
|
+
margin-bottom: 8px;
|
|
310
|
+
padding-bottom: 8px;
|
|
311
|
+
background-color: #fff;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.toolbar-input {
|
|
315
|
+
width: 100%;
|
|
316
|
+
height: 32px;
|
|
317
|
+
padding: 0 28px 0 10px;
|
|
318
|
+
font-size: 12px;
|
|
319
|
+
border: 1px solid #dcdfe6;
|
|
320
|
+
border-radius: 4px;
|
|
321
|
+
background-color: #fff;
|
|
322
|
+
box-sizing: border-box;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.toolbar-input-wrap {
|
|
326
|
+
position: relative;
|
|
327
|
+
flex: 1;
|
|
328
|
+
min-width: 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.toolbar-input-clear {
|
|
332
|
+
position: absolute;
|
|
333
|
+
top: 50%;
|
|
334
|
+
right: 8px;
|
|
335
|
+
transform: translateY(-50%);
|
|
336
|
+
width: 16px;
|
|
337
|
+
height: 16px;
|
|
338
|
+
line-height: 16px;
|
|
339
|
+
font-size: 12px;
|
|
340
|
+
color: #999;
|
|
341
|
+
text-align: center;
|
|
342
|
+
border-radius: 50%;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.toolbar-clear {
|
|
346
|
+
flex-shrink: 0;
|
|
347
|
+
padding: 6px 10px;
|
|
348
|
+
font-size: 12px;
|
|
349
|
+
color: #119af5;
|
|
350
|
+
border: 1px solid #119af5;
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.empty-state {
|
|
355
|
+
padding: 16px 0;
|
|
356
|
+
font-size: 12px;
|
|
357
|
+
color: #999;
|
|
358
|
+
text-align: center;
|
|
359
|
+
}
|
|
360
|
+
|
|
86
361
|
.uni-collapse-content {
|
|
87
362
|
font-size: 12px;
|
|
88
363
|
}
|