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