uni-oaview 1.5.2 → 1.6.0

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.
@@ -58,6 +58,7 @@
58
58
  }
59
59
 
60
60
  interface NetworkLog {
61
+ type: 'request' | 'uploadFile' | 'downloadFile';
61
62
  request: NetworkLogRequest;
62
63
  response?: NetworkLogResponse;
63
64
  startTime: number;
@@ -132,8 +133,15 @@
132
133
  return networkLogDetailMap.value.get(id) ?? null;
133
134
  };
134
135
 
135
- const upsertLogs = (logs: NetworkLog[]) => {
136
- const nextLogs = logs.slice(-MAX_NETWORK_LOGS);
136
+ // 使用类型守卫过滤出 HTTP 日志
137
+ const isHttpLog = (log: any): log is NetworkLog => {
138
+ return log && log.type !== 'websocket';
139
+ };
140
+
141
+ const upsertLogs = (logs: any[]): void => {
142
+ // 过滤出非 websocket 的日志
143
+ const httpLogs = logs.filter(isHttpLog);
144
+ const nextLogs = httpLogs.slice(-MAX_NETWORK_LOGS);
137
145
  const detailMap = networkLogDetailMap.value;
138
146
 
139
147
  const nextMetas: NetworkLogMeta[] = [];
@@ -162,7 +170,7 @@
162
170
 
163
171
  upsertLogs(getNetworkLogs());
164
172
 
165
- const stop = networkSubject.subscribe((data: NetworkLog[]) => {
173
+ const stop = networkSubject.subscribe((data: any[]): void => {
166
174
  upsertLogs(data);
167
175
  });
168
176
 
@@ -37,6 +37,9 @@
37
37
  <Audio />
38
38
  </template>
39
39
  <template v-if="current === 7">
40
+ <Websocket />
41
+ </template>
42
+ <template v-if="current === 8">
40
43
  <DevTools />
41
44
  </template>
42
45
  </view>
@@ -65,10 +68,11 @@
65
68
  import Console from './console.vue';
66
69
  import System from './system.vue';
67
70
  import Audio from './audio.vue';
71
+ import Websocket from './websocket.vue';
68
72
  import DevTools from './dev-tools.vue';
69
73
 
70
74
  const popupRef = ref<any>();
71
- const list = ref(['console', 'network', 'event', 'error', 'storage', 'system', 'audio', 'dev-tools']);
75
+ const list = ref(['console', 'network', 'event', 'error', 'storage', 'system', 'audio', 'websocket', 'dev-tools']);
72
76
  const current = ref(0);
73
77
 
74
78
  const handleTabClick = (index: number) => {
@@ -0,0 +1,281 @@
1
+ <template>
2
+ <uni-collapse ref="collapseRef" v-model="activeNames">
3
+ <uni-collapse-item
4
+ v-for="meta in websocketLogMetas"
5
+ :key="meta.id"
6
+ :name="meta.id"
7
+ :title="meta.title"
8
+ :class="{
9
+ 'uni-collapse-item-error': meta.isError,
10
+ 'uni-collapse-item-connecting': meta.isConnecting,
11
+ }"
12
+ >
13
+ <view style="font-size: 10px; padding: 10px 15px">
14
+ <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
15
+ <text :selectable="true" style="font-weight: bolder">URL:</text>
16
+ {{ meta.url }}
17
+ </view>
18
+ <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
19
+ <text :selectable="true" style="font-weight: bolder">状态:</text>
20
+ {{ meta.status }}
21
+ </view>
22
+ <view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
23
+ <text :selectable="true" style="font-weight: bolder">消息统计:</text>
24
+ 发送 {{ meta.sendCount }},接收 {{ meta.receiveCount }},共 {{ meta.messageTotal }} 条
25
+ </view>
26
+ <template v-if="isExpanded(meta.id)">
27
+ <view v-if="meta.protocols" style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
28
+ <text :selectable="true" style="font-weight: bolder">协议:</text>
29
+ {{ meta.protocols }}
30
+ </view>
31
+ <view
32
+ v-if="getWebsocketLogDetail(meta.id)?.request?.header"
33
+ style="word-break: break-all; padding: 8px 0; margin-bottom: 10px"
34
+ >
35
+ <text :selectable="true" style="font-weight: bolder">请求头:</text>
36
+ <awesome-display-info :log="getWebsocketLogDetail(meta.id)?.request?.header" />
37
+ </view>
38
+ <view
39
+ v-if="meta.isError && getWebsocketLogDetail(meta.id)?.errorInfo"
40
+ style="word-break: break-all; padding: 8px 0; margin-bottom: 10px"
41
+ >
42
+ <text :selectable="true" style="font-weight: bolder; color: red">错误信息:</text>
43
+ <awesome-display-info :log="getWebsocketLogDetail(meta.id)?.errorInfo" />
44
+ </view>
45
+ <view
46
+ v-if="getMessageList(meta.id).length > 0"
47
+ style="word-break: break-all; padding: 8px 0; margin-bottom: 10px"
48
+ >
49
+ <text :selectable="true" style="font-weight: bolder">
50
+ 消息列表(共 {{ meta.messageTotal }} 条,显示最近 100 条):
51
+ </text>
52
+ <view
53
+ v-for="(msg, index) in getMessageList(meta.id)"
54
+ :key="index"
55
+ style="padding: 4px 0; font-size: 9px; color: #666"
56
+ >
57
+ <text :selectable="true">
58
+ [{{ formatTime(msg.time) }}] [{{ msg.type === 'send' ? '发送' : '接收' }}] {{ msg.size }}B
59
+ </text>
60
+ <view
61
+ v-if="msg.dataPreview"
62
+ style="margin-top: 2px; padding: 4px; background-color: #f5f5f5; border-radius: 2px"
63
+ >
64
+ {{ msg.dataPreview }}
65
+ </view>
66
+ </view>
67
+ </view>
68
+ </template>
69
+ </view>
70
+ </uni-collapse-item>
71
+ </uni-collapse>
72
+ </template>
73
+
74
+ <script lang="ts" setup>
75
+ import { nextTick, onBeforeUnmount, ref, shallowRef, watch } from 'vue';
76
+ import awesomeDisplayInfo from './awesome-display-info.vue';
77
+ import { websocketSubject, getWebsocketLogs } from 'uniapp-log-sdk';
78
+
79
+ interface MessageRecord {
80
+ type: 'send' | 'receive';
81
+ data: string | ArrayBuffer;
82
+ time: number;
83
+ size: number;
84
+ }
85
+
86
+ interface WebsocketLog {
87
+ type: 'websocket';
88
+ connectionId: string;
89
+ request: {
90
+ url: string;
91
+ header?: unknown;
92
+ protocols?: string[];
93
+ };
94
+ response?: unknown;
95
+ messages: MessageRecord[];
96
+ stats: {
97
+ messageTotal: number;
98
+ messageSendCount: number;
99
+ messageReceiveCount: number;
100
+ droppedMessageCount: number;
101
+ };
102
+ startTime: number;
103
+ endTime: number;
104
+ openTime: number;
105
+ status: 'connecting' | 'open' | 'closed' | 'error';
106
+ errorInfo?: unknown;
107
+ }
108
+
109
+ interface WebsocketLogMeta {
110
+ id: string;
111
+ title: string;
112
+ url: string;
113
+ status: string;
114
+ messageTotal: number;
115
+ sendCount: number;
116
+ receiveCount: number;
117
+ protocols?: string;
118
+ duration: string;
119
+ isError: boolean;
120
+ isConnecting: boolean;
121
+ }
122
+
123
+ const MAX_WEBSOCKET_LOGS = 50;
124
+ const MESSAGE_PREVIEW_LENGTH = 100;
125
+
126
+ const collapseRef = ref<{ resize?: () => void } | null>(null);
127
+ const activeNames = ref<string[] | string>([]);
128
+
129
+ const websocketLogDetailMap = shallowRef<Map<string, WebsocketLog>>(new Map());
130
+ const websocketLogMetas = ref<WebsocketLogMeta[]>([]);
131
+
132
+ const formatTime = (timestamp: number): string => {
133
+ const date = new Date(timestamp);
134
+ const hours = String(date.getHours()).padStart(2, '0');
135
+ const minutes = String(date.getMinutes()).padStart(2, '0');
136
+ const seconds = String(date.getSeconds()).padStart(2, '0');
137
+ const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
138
+ return `${hours}:${minutes}:${seconds}.${milliseconds}`;
139
+ };
140
+
141
+ const buildWebsocketLogId = (log: WebsocketLog): string => {
142
+ // 使用 connectionId 作为唯一标识,避免同 URL 多连接的混淆
143
+ return log.connectionId;
144
+ };
145
+
146
+ const getMessagePreview = (data: string | ArrayBuffer): string => {
147
+ if (typeof data === 'string') {
148
+ return data.length > MESSAGE_PREVIEW_LENGTH ? data.substring(0, MESSAGE_PREVIEW_LENGTH) + '...' : data;
149
+ }
150
+ return `[ArrayBuffer(${data.byteLength})]`;
151
+ };
152
+
153
+ const buildMetaFromLog = (log: WebsocketLog): WebsocketLogMeta => {
154
+ const { startTime, endTime, request, status, stats, errorInfo } = log;
155
+ const timeStr = formatTime(startTime);
156
+ const duration = endTime && startTime ? `${((endTime - startTime) / 1000).toFixed(2)}s` : 'pending';
157
+ const isError = status === 'error' || (errorInfo !== null && errorInfo !== undefined);
158
+ const isConnecting = status === 'connecting';
159
+ const protocolStr = request?.protocols?.join(',') || '';
160
+
161
+ return {
162
+ id: buildWebsocketLogId(log),
163
+ title: `[${timeStr}] ${request?.url};${duration};${status}`,
164
+ url: request?.url ?? '',
165
+ status,
166
+ messageTotal: stats.messageTotal,
167
+ sendCount: stats.messageSendCount,
168
+ receiveCount: stats.messageReceiveCount,
169
+ protocols: protocolStr,
170
+ duration,
171
+ isError,
172
+ isConnecting,
173
+ };
174
+ };
175
+
176
+ const getActiveNameList = (): string[] => {
177
+ const value = activeNames.value;
178
+ return Array.isArray(value) ? value : value ? [value] : [];
179
+ };
180
+
181
+ const isExpanded = (id: string): boolean => getActiveNameList().includes(id);
182
+
183
+ const getWebsocketLogDetail = (id: string): WebsocketLog | null => {
184
+ return websocketLogDetailMap.value.get(id) ?? null;
185
+ };
186
+
187
+ const getMessageList = (id: string): Array<MessageRecord & { dataPreview: string }> => {
188
+ const log = getWebsocketLogDetail(id);
189
+ if (!log) return [];
190
+ return log.messages.map((msg) => ({
191
+ ...msg,
192
+ dataPreview: getMessagePreview(msg.data),
193
+ }));
194
+ };
195
+
196
+ const upsertLogs = (logs: WebsocketLog[]) => {
197
+ const nextLogs = logs.slice(-MAX_WEBSOCKET_LOGS);
198
+ const detailMap = websocketLogDetailMap.value;
199
+
200
+ const nextMetas: WebsocketLogMeta[] = [];
201
+ for (const log of nextLogs) {
202
+ const id = buildWebsocketLogId(log);
203
+ detailMap.set(id, log);
204
+ nextMetas.push(buildMetaFromLog(log));
205
+ }
206
+
207
+ websocketLogMetas.value = nextMetas;
208
+
209
+ // 清理 detailMap,避免无限增长
210
+ const keepIds = new Set(nextMetas.map((m) => m.id));
211
+ for (const key of detailMap.keys()) {
212
+ if (!keepIds.has(key)) detailMap.delete(key);
213
+ }
214
+
215
+ // 清理 activeNames(如果展开项已被裁剪掉)
216
+ const currentActive = activeNames.value;
217
+ if (Array.isArray(currentActive)) {
218
+ if (currentActive.length) activeNames.value = currentActive.filter((id) => keepIds.has(id));
219
+ } else if (currentActive && !keepIds.has(currentActive)) {
220
+ activeNames.value = '';
221
+ }
222
+ };
223
+
224
+ upsertLogs(getWebsocketLogs());
225
+
226
+ const stop = websocketSubject.subscribe((data: WebsocketLog[]) => {
227
+ upsertLogs(data);
228
+ });
229
+
230
+ // 展开项变化时刷新 collapse 高度缓存(小程序端必须手动调用,否则内容高度可能为 0)
231
+ watch(
232
+ () => getActiveNameList().join('|'),
233
+ () => {
234
+ // #ifdef MP
235
+ nextTick(() => collapseRef.value?.resize?.());
236
+ // #endif
237
+ },
238
+ );
239
+
240
+ // 列表更新时,如果有已展开项(内容高度可能变化),同步刷新高度缓存
241
+ watch(
242
+ () => websocketLogMetas.value,
243
+ () => {
244
+ // #ifdef MP
245
+ if (getActiveNameList().length) nextTick(() => collapseRef.value?.resize?.());
246
+ // #endif
247
+ },
248
+ { deep: false },
249
+ );
250
+
251
+ onBeforeUnmount(() => {
252
+ stop?.unsubscribe();
253
+ });
254
+ </script>
255
+
256
+ <style lang="scss" scoped>
257
+ .uni-collapse-content {
258
+ font-size: 12px;
259
+ }
260
+ ::v-deep {
261
+ .uni-collapse-item__title-text {
262
+ white-space: normal !important;
263
+ overflow: visible !important;
264
+ text-overflow: clip !important;
265
+ line-height: normal !important;
266
+ word-break: break-all !important;
267
+ font-size: 10px;
268
+ font-weight: normal;
269
+ }
270
+ .uni-collapse-item-error {
271
+ .uni-collapse-item__title-text {
272
+ color: red !important;
273
+ }
274
+ }
275
+ .uni-collapse-item-connecting {
276
+ .uni-collapse-item__title-text {
277
+ color: #f90 !important;
278
+ }
279
+ }
280
+ }
281
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uni-oaview",
3
- "version": "1.5.02",
3
+ "version": "1.6.00",
4
4
  "description": "uniapp小程序组件库",
5
5
  "main": "dist/index.esm.js",
6
6
  "typings": "dist/index.d.ts",
@@ -51,13 +51,13 @@
51
51
  "rollup-plugin-typescript2": "^0.27.2",
52
52
  "rollup-plugin-vue": "^6.0.0",
53
53
  "typescript": "^4.0.2",
54
- "uniapp-log-sdk": "^1.7.2"
54
+ "uniapp-log-sdk": "^1.8.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@dcloudio/uni-ui": "^1.5.11",
58
58
  "@vueuse/core": "^9.12.0",
59
59
  "dayjs": "1.11.7",
60
- "uniapp-log-sdk": "^1.7.2"
60
+ "uniapp-log-sdk": "^1.8.0"
61
61
  },
62
62
  "dependencies": {
63
63
  "element-china-area-data": "5.0.2"