uni-oaview 1.9.13 → 1.9.15
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.
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export interface NativeEventLog {
|
|
2
|
+
key: string;
|
|
3
|
+
params: unknown;
|
|
4
|
+
response: unknown;
|
|
5
|
+
startTime: number;
|
|
6
|
+
endTime: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface NativeEventLogMeta {
|
|
10
|
+
id: string;
|
|
11
|
+
key: string;
|
|
12
|
+
title: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NativeEventState {
|
|
16
|
+
detailMap: Map<string, NativeEventLog>;
|
|
17
|
+
metas: NativeEventLogMeta[];
|
|
18
|
+
expandedIds: Set<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NativeEventIdStore {
|
|
22
|
+
ids: WeakMap<NativeEventLog, string>;
|
|
23
|
+
nextId: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const MAX_NATIVE_EVENT_LOGS = 200;
|
|
27
|
+
|
|
28
|
+
export const createNativeEventIdStore = (): NativeEventIdStore => {
|
|
29
|
+
return {
|
|
30
|
+
ids: new WeakMap<NativeEventLog, string>(),
|
|
31
|
+
nextId: 0,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const buildNativeEventLogId = (log: NativeEventLog): string => {
|
|
36
|
+
return `${log.startTime}|${log.endTime}|${log.key}`;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getStableNativeEventLogId = (log: NativeEventLog, idStore?: NativeEventIdStore): string => {
|
|
40
|
+
if (!idStore) {
|
|
41
|
+
return buildNativeEventLogId(log);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cachedId = idStore.ids.get(log);
|
|
45
|
+
if (cachedId) {
|
|
46
|
+
return cachedId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
idStore.nextId += 1;
|
|
50
|
+
const nextId = `native-event-${idStore.nextId}`;
|
|
51
|
+
idStore.ids.set(log, nextId);
|
|
52
|
+
return nextId;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const formatStartTime = (timestamp: number): string => {
|
|
56
|
+
const date = new Date(timestamp);
|
|
57
|
+
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(
|
|
58
|
+
date.getSeconds(),
|
|
59
|
+
).padStart(2, '0')}`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getTitle = (log: NativeEventLog): string => {
|
|
63
|
+
const { startTime, endTime, key } = log;
|
|
64
|
+
if (startTime && endTime) {
|
|
65
|
+
return `${formatStartTime(startTime)} ${key};${(endTime - startTime) / 1000}s`;
|
|
66
|
+
}
|
|
67
|
+
return key;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const buildMetaFromLog = (log: NativeEventLog, id: string): NativeEventLogMeta => {
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
key: log.key,
|
|
74
|
+
title: getTitle(log),
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const buildNativeEventState = (
|
|
79
|
+
logs: NativeEventLog[],
|
|
80
|
+
expandedIds: Iterable<string>,
|
|
81
|
+
_idStore?: NativeEventIdStore,
|
|
82
|
+
): NativeEventState => {
|
|
83
|
+
const nextLogs = logs.slice(-MAX_NATIVE_EVENT_LOGS);
|
|
84
|
+
const detailMap = new Map<string, NativeEventLog>();
|
|
85
|
+
const metas = nextLogs.map((log) => {
|
|
86
|
+
const id = getStableNativeEventLogId(log, _idStore);
|
|
87
|
+
detailMap.set(id, log);
|
|
88
|
+
return buildMetaFromLog(log, id);
|
|
89
|
+
});
|
|
90
|
+
const keepIds = new Set(metas.map((meta) => meta.id));
|
|
91
|
+
const nextExpandedIds = new Set<string>();
|
|
92
|
+
|
|
93
|
+
for (const id of expandedIds) {
|
|
94
|
+
if (keepIds.has(id)) {
|
|
95
|
+
nextExpandedIds.add(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
detailMap,
|
|
101
|
+
metas,
|
|
102
|
+
expandedIds: nextExpandedIds,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -38,23 +38,14 @@
|
|
|
38
38
|
<script lang="ts" setup>
|
|
39
39
|
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue';
|
|
40
40
|
import awesomeDisplayInfo from './awesome-display-info.vue';
|
|
41
|
+
import {
|
|
42
|
+
buildNativeEventState,
|
|
43
|
+
createNativeEventIdStore,
|
|
44
|
+
type NativeEventLog,
|
|
45
|
+
type NativeEventLogMeta,
|
|
46
|
+
} from './native-event-state';
|
|
47
|
+
import { stringifyForSearch } from './utils';
|
|
41
48
|
import { nativeEventSubject, getNativeEventLogs } from 'uniapp-log-sdk';
|
|
42
|
-
|
|
43
|
-
interface NativeEventLog {
|
|
44
|
-
key: string;
|
|
45
|
-
params: unknown;
|
|
46
|
-
response: unknown;
|
|
47
|
-
startTime: number;
|
|
48
|
-
endTime: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface NativeEventLogMeta {
|
|
52
|
-
id: string;
|
|
53
|
-
key: string;
|
|
54
|
-
title: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const MAX_NATIVE_EVENT_LOGS = 200;
|
|
58
49
|
const FLUSH_DELAY_FOREGROUND = 16;
|
|
59
50
|
const FLUSH_DELAY_BACKGROUND = 100;
|
|
60
51
|
const MAX_EXPANDED_ITEMS = 20; // 限制最大展开项数
|
|
@@ -70,38 +61,12 @@
|
|
|
70
61
|
|
|
71
62
|
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
72
63
|
let pendingLogs: NativeEventLog[] | null = null;
|
|
64
|
+
const nativeEventIdStore = createNativeEventIdStore();
|
|
73
65
|
|
|
74
66
|
// 订阅和监听器取消函数
|
|
75
67
|
let unsubscribeLogs: (() => void) | null = null;
|
|
76
68
|
const cleanupFunctions: (() => void)[] = [];
|
|
77
69
|
|
|
78
|
-
const buildNativeEventLogId = (log: NativeEventLog): string => {
|
|
79
|
-
return `${log.startTime}|${log.endTime}|${log.key}`;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const formatStartTime = (timestamp: number): string => {
|
|
83
|
-
const date = new Date(timestamp);
|
|
84
|
-
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(
|
|
85
|
-
date.getSeconds(),
|
|
86
|
-
).padStart(2, '0')}`;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const getTitle = (log: NativeEventLog): string => {
|
|
90
|
-
const { startTime, endTime, key } = log;
|
|
91
|
-
if (startTime && endTime) {
|
|
92
|
-
return `${formatStartTime(startTime)} ${key};${(endTime - startTime) / 1000}s`;
|
|
93
|
-
}
|
|
94
|
-
return key;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const buildMetaFromLog = (log: NativeEventLog): NativeEventLogMeta => {
|
|
98
|
-
return {
|
|
99
|
-
id: buildNativeEventLogId(log),
|
|
100
|
-
key: log.key,
|
|
101
|
-
title: getTitle(log),
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
|
|
105
70
|
const isExpanded = (id: string): boolean => expandedIds.value.has(id);
|
|
106
71
|
|
|
107
72
|
const toggleExpand = (id: string): void => {
|
|
@@ -125,20 +90,10 @@
|
|
|
125
90
|
|
|
126
91
|
const normalizeKeyword = (value: string): string => value.trim().toLowerCase();
|
|
127
92
|
|
|
128
|
-
const safeStringify = (value: unknown): string => {
|
|
129
|
-
if (value === null || value === undefined) return '';
|
|
130
|
-
if (typeof value === 'string') return value;
|
|
131
|
-
try {
|
|
132
|
-
return JSON.stringify(value);
|
|
133
|
-
} catch (error) {
|
|
134
|
-
return String(value);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
93
|
const matchesKeyword = (meta: NativeEventLogMeta, keywordValue: string): boolean => {
|
|
139
94
|
if (!keywordValue) return true;
|
|
140
95
|
const detail = getNativeEventDetail(meta.id);
|
|
141
|
-
const searchText = `${meta.title} ${meta.key} ${
|
|
96
|
+
const searchText = `${meta.title} ${meta.key} ${stringifyForSearch(detail?.params)} ${stringifyForSearch(
|
|
142
97
|
detail?.response,
|
|
143
98
|
)}`.toLowerCase();
|
|
144
99
|
return searchText.includes(keywordValue);
|
|
@@ -197,34 +152,10 @@
|
|
|
197
152
|
return;
|
|
198
153
|
}
|
|
199
154
|
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
for (const log of nextLogs) {
|
|
205
|
-
const id = buildNativeEventLogId(log);
|
|
206
|
-
detailMap.set(id, log);
|
|
207
|
-
nextMetas.push(buildMetaFromLog(log));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// 创建新的 Map 触发响应式更新
|
|
211
|
-
nativeEventDetailMap.value = new Map(detailMap);
|
|
212
|
-
nativeEventLogMetas.value = nextMetas;
|
|
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;
|
|
155
|
+
const nextState = buildNativeEventState(visibleLogs, expandedIds.value, nativeEventIdStore);
|
|
156
|
+
nativeEventDetailMap.value = nextState.detailMap;
|
|
157
|
+
nativeEventLogMetas.value = nextState.metas;
|
|
158
|
+
expandedIds.value = nextState.expandedIds;
|
|
228
159
|
};
|
|
229
160
|
|
|
230
161
|
const flushPendingLogs = (): void => {
|
|
@@ -1,12 +1,121 @@
|
|
|
1
1
|
const MAX_DEPTH = 10;
|
|
2
2
|
const MAX_ARRAY_LENGTH = 100;
|
|
3
|
-
const
|
|
3
|
+
const MAX_OBJECT_KEYS = 100;
|
|
4
|
+
const MAX_STRING_LENGTH = 200;
|
|
5
|
+
const MAX_READABLE_PREVIEW_LENGTH = 200;
|
|
6
|
+
const MAX_UNREADABLE_PREVIEW_LENGTH = 50;
|
|
7
|
+
const MAX_SEARCH_DEPTH = 4;
|
|
8
|
+
const MAX_SEARCH_ARRAY_LENGTH = 20;
|
|
9
|
+
const MAX_SEARCH_OBJECT_KEYS = 20;
|
|
4
10
|
|
|
5
11
|
interface RichTextNode {
|
|
6
|
-
type:
|
|
12
|
+
type: 'text';
|
|
7
13
|
text: string;
|
|
8
14
|
}
|
|
9
15
|
|
|
16
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
17
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const isLikelyEncodedText = (value: string): boolean => {
|
|
21
|
+
if (value.length < 32 || /\s/.test(value)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return /^[A-Za-z0-9+/=_-]+$/.test(value);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getControlCharacterCount = (value: string): number => {
|
|
29
|
+
let count = 0;
|
|
30
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
31
|
+
const code = value.charCodeAt(index);
|
|
32
|
+
const isControlChar = (code >= 0 && code <= 8) || (code >= 14 && code <= 31) || code === 127;
|
|
33
|
+
if (isControlChar) {
|
|
34
|
+
count += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return count;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isHumanReadableString = (value: string): boolean => {
|
|
41
|
+
if (!value) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (isLikelyEncodedText(value)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const controlCharacterCount = getControlCharacterCount(value);
|
|
50
|
+
if (controlCharacterCount === 0) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return controlCharacterCount / value.length < 0.05;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const buildStringPreview = (value: string): string => {
|
|
58
|
+
const previewLength = isHumanReadableString(value) ? MAX_READABLE_PREVIEW_LENGTH : MAX_UNREADABLE_PREVIEW_LENGTH;
|
|
59
|
+
|
|
60
|
+
if (value.length <= previewLength) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return `${value.slice(0, previewLength)}...`;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const formatString = (value: string): string => {
|
|
68
|
+
const preview = buildStringPreview(value);
|
|
69
|
+
if (preview === value) {
|
|
70
|
+
return `"${value}"`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return `"${preview}" (长度 ${value.length})`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const buildSearchText = (value: unknown, depth = 0): string => {
|
|
77
|
+
if (value === null || value === undefined) {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof value === 'string') {
|
|
82
|
+
return buildStringPreview(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
86
|
+
return String(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (depth >= MAX_SEARCH_DEPTH) {
|
|
90
|
+
return '[...]';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
const items = value.slice(0, MAX_SEARCH_ARRAY_LENGTH).map((item) => buildSearchText(item, depth + 1));
|
|
95
|
+
const suffix = value.length > MAX_SEARCH_ARRAY_LENGTH ? ` ...(${value.length})` : '';
|
|
96
|
+
return `[${items.join(', ')}${suffix}]`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isPlainObject(value)) {
|
|
100
|
+
const keys = Object.keys(value);
|
|
101
|
+
const items = keys
|
|
102
|
+
.slice(0, MAX_SEARCH_OBJECT_KEYS)
|
|
103
|
+
.map((key) => `${key}:${buildSearchText(value[key], depth + 1)}`);
|
|
104
|
+
const suffix = keys.length > MAX_SEARCH_OBJECT_KEYS ? ` ...(${keys.length})` : '';
|
|
105
|
+
return `{${items.join(', ')}${suffix}}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return String(value);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return '[unserializable]';
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const stringifyForSearch = (value: unknown): string => {
|
|
116
|
+
return buildSearchText(value);
|
|
117
|
+
};
|
|
118
|
+
|
|
10
119
|
export const formatJson = (data: any, indent = 0): RichTextNode[] => {
|
|
11
120
|
const indentStr = ' '.repeat(indent);
|
|
12
121
|
|
|
@@ -62,13 +171,23 @@ const formatArray = (data: any[], indent: number, indentStr: string): RichTextNo
|
|
|
62
171
|
const formatObject = (data: Record<string, any>, indent: number, indentStr: string): RichTextNode[] => {
|
|
63
172
|
let formatted: RichTextNode[] = [{ type: 'text', text: '{\n' }];
|
|
64
173
|
const keys = Object.keys(data);
|
|
65
|
-
keys.
|
|
174
|
+
const displayKeys = keys.slice(0, MAX_OBJECT_KEYS);
|
|
175
|
+
|
|
176
|
+
displayKeys.forEach((key, index) => {
|
|
66
177
|
formatted.push({ type: 'text', text: indentStr + ' '.repeat(4) + key + ': ' });
|
|
67
178
|
formatted.push(...formatJson(data[key], indent + 4));
|
|
68
|
-
if (index <
|
|
179
|
+
if (index < displayKeys.length - 1 || keys.length > MAX_OBJECT_KEYS) {
|
|
69
180
|
formatted.push({ type: 'text', text: ',\n' });
|
|
70
181
|
}
|
|
71
182
|
});
|
|
183
|
+
|
|
184
|
+
if (keys.length > MAX_OBJECT_KEYS) {
|
|
185
|
+
formatted.push({
|
|
186
|
+
type: 'text',
|
|
187
|
+
text: indentStr + ` ... (共 ${keys.length} 个键,显示 ${displayKeys.length} 个)\n`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
72
191
|
formatted.push({ type: 'text', text: '\n' + indentStr + '}' });
|
|
73
192
|
|
|
74
193
|
return formatted;
|
|
@@ -76,14 +195,8 @@ const formatObject = (data: Record<string, any>, indent: number, indentStr: stri
|
|
|
76
195
|
|
|
77
196
|
const formatPrimitive = (data: any): RichTextNode[] => {
|
|
78
197
|
if (typeof data === 'string') {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return [
|
|
82
|
-
{
|
|
83
|
-
type: 'text',
|
|
84
|
-
text: `"${data.slice(0, MAX_STRING_LENGTH)}..." (长度 ${data.length})`,
|
|
85
|
-
},
|
|
86
|
-
];
|
|
198
|
+
if (data.length > MAX_STRING_LENGTH || !isHumanReadableString(data)) {
|
|
199
|
+
return [{ type: 'text', text: formatString(data) }];
|
|
87
200
|
}
|
|
88
201
|
return [{ type: 'text', text: `"${data}"` }];
|
|
89
202
|
} else {
|