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
|
-
|
|
136
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
60
|
+
"uniapp-log-sdk": "^1.8.0"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"element-china-area-data": "5.0.2"
|