uni-oaview 1.9.13 → 1.9.14
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.
|
@@ -38,6 +38,7 @@
|
|
|
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 { stringifyForSearch } from './utils';
|
|
41
42
|
import { nativeEventSubject, getNativeEventLogs } from 'uniapp-log-sdk';
|
|
42
43
|
|
|
43
44
|
interface NativeEventLog {
|
|
@@ -125,20 +126,10 @@
|
|
|
125
126
|
|
|
126
127
|
const normalizeKeyword = (value: string): string => value.trim().toLowerCase();
|
|
127
128
|
|
|
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
129
|
const matchesKeyword = (meta: NativeEventLogMeta, keywordValue: string): boolean => {
|
|
139
130
|
if (!keywordValue) return true;
|
|
140
131
|
const detail = getNativeEventDetail(meta.id);
|
|
141
|
-
const searchText = `${meta.title} ${meta.key} ${
|
|
132
|
+
const searchText = `${meta.title} ${meta.key} ${stringifyForSearch(detail?.params)} ${stringifyForSearch(
|
|
142
133
|
detail?.response,
|
|
143
134
|
)}`.toLowerCase();
|
|
144
135
|
return searchText.includes(keywordValue);
|
|
@@ -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 {
|