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.
@@ -43,6 +43,8 @@
43
43
  secretArray.push(count);
44
44
  if (secretArray.join('') === secret) {
45
45
  showLog.value = true;
46
+ /** 如果秘钥正确则进入调试模式 */
47
+ uni.setStorageSync('isDebug', true);
46
48
  }
47
49
  } else {
48
50
  clickCount = 0;
@@ -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} ${safeStringify(detail?.params)} ${safeStringify(
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 nextLogs = visibleLogs.slice(-MAX_NATIVE_EVENT_LOGS);
201
- const detailMap = nativeEventDetailMap.value;
202
-
203
- const nextMetas: NativeEventLogMeta[] = [];
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 MAX_STRING_LENGTH = 10000;
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: string;
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.forEach((key, index) => {
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 < keys.length - 1) {
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
- if (data.length > MAX_STRING_LENGTH) {
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uni-oaview",
3
- "version": "1.9.13",
3
+ "version": "1.9.15",
4
4
  "description": "uniapp小程序组件库",
5
5
  "main": "dist/index.esm.js",
6
6
  "typings": "dist/index.d.ts",