uni-oaview 1.2.1 → 1.2.3
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.
- package/components/oa-confirm/oa-confirm.vue +1 -1
- package/components/oa-modal-wrapper/oa-modal-wrapper.vue +65 -49
- package/components/oa-toast/oa-toast.vue +1 -1
- package/components/oa-vconsole/awesome-display-info.vue +6 -6
- package/components/oa-vconsole/console.vue +11 -4
- package/components/oa-vconsole/error.vue +18 -2
- package/components/oa-vconsole/native-event.vue +22 -4
- package/components/oa-vconsole/network.vue +45 -15
- package/components/oa-vconsole/oa-vconsole.vue +21 -7
- package/components/oa-vconsole/storage.vue +31 -3
- package/components/oa-vconsole/utils.ts +48 -15
- package/dist/index.d.ts +51 -15
- package/dist/index.esm.js +31 -3
- package/package.json +1 -1
- package/src/index.ts +5 -3
- package/src/utils/create-modal.ts +71 -12
|
@@ -77,8 +77,9 @@
|
|
|
77
77
|
ref="contentRef"
|
|
78
78
|
:is="currentModal.component"
|
|
79
79
|
v-bind="currentModal.props"
|
|
80
|
-
|
|
81
|
-
@
|
|
80
|
+
v-on="computedEventHandlers"
|
|
81
|
+
@handle-submit="handleSubmit"
|
|
82
|
+
@close="(reason?: any) => closeCurrentModal(undefined, false, reason || 'cancel')"
|
|
82
83
|
/>
|
|
83
84
|
</view>
|
|
84
85
|
</view>
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
import { onShow, onHide } from '@dcloudio/uni-app';
|
|
91
92
|
import { OPEN_MODAL, CLOSE_MODAL, UPDATE_MODAL } from '../../constants';
|
|
92
93
|
import type { Component } from 'vue';
|
|
94
|
+
import type { ModalBaseEvents, ModalCloseReason, ModalOptions } from '../../src/utils/create-modal';
|
|
93
95
|
|
|
94
96
|
interface ModalHeaderOptions {
|
|
95
97
|
/** 是否显示头部 */
|
|
@@ -116,35 +118,13 @@
|
|
|
116
118
|
submitProps?: Record<string, any>;
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
interface
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
/** 是否开启打开/关闭动画 */
|
|
127
|
-
animation?: boolean;
|
|
128
|
-
/** 是否适配底部安全区 */
|
|
129
|
-
safeArea?: boolean;
|
|
130
|
-
/** 弹框背景色 */
|
|
131
|
-
backgroundColor?: string;
|
|
132
|
-
/** 蒙层背景色,默认 'rgba(0, 0, 0, 0.4)' */
|
|
133
|
-
maskBackgroundColor?: string;
|
|
134
|
-
/** 弹框圆角,默认 '16px 16px 0 0' */
|
|
135
|
-
borderRadius?: string;
|
|
136
|
-
/** 头部配置 */
|
|
137
|
-
header?: ModalHeaderOptions;
|
|
138
|
-
/** 弹框标题(当 header.title 不存在时使用) */
|
|
139
|
-
title?: string;
|
|
140
|
-
/** 内容区是否显示内边距,默认 true */
|
|
141
|
-
contentPadding?: boolean;
|
|
142
|
-
/** 内容区最大高度 */
|
|
143
|
-
maxHeight?: string;
|
|
144
|
-
/** 是否显示蒙层,默认 true */
|
|
145
|
-
showMask?: boolean;
|
|
146
|
-
/** 头部操作按钮点击回调 */
|
|
147
|
-
onHeaderAction?: (action: string, modalId: string) => void;
|
|
121
|
+
interface ModalComponentRef {
|
|
122
|
+
/** 提交方法(可选) */
|
|
123
|
+
submit?: () => Promise<any> | any;
|
|
124
|
+
/** 验证方法(可选) */
|
|
125
|
+
validate?: () => Promise<boolean>;
|
|
126
|
+
/** 重置方法(可选) */
|
|
127
|
+
reset?: () => void;
|
|
148
128
|
}
|
|
149
129
|
|
|
150
130
|
interface ModalInstance {
|
|
@@ -158,13 +138,36 @@
|
|
|
158
138
|
const MAX_MODALS = 5;
|
|
159
139
|
const modalStack = ref<ModalInstance[]>([]);
|
|
160
140
|
const popupRef = ref<any>(null);
|
|
161
|
-
const contentRef = ref<
|
|
141
|
+
const contentRef = ref<ModalComponentRef | null>(null);
|
|
162
142
|
const isPageAlive = ref(false);
|
|
163
143
|
const isClosing = ref(false);
|
|
164
144
|
const isH5 = ref(false);
|
|
165
145
|
|
|
166
146
|
const currentModal = computed(() => modalStack.value[modalStack.value.length - 1] || null);
|
|
167
147
|
|
|
148
|
+
/**
|
|
149
|
+
* 计算动态事件处理器
|
|
150
|
+
* 将 options.events 中的事件处理函数动态绑定到组件
|
|
151
|
+
*/
|
|
152
|
+
const computedEventHandlers = computed(() => {
|
|
153
|
+
const handlers: Record<string, Function> = {};
|
|
154
|
+
|
|
155
|
+
if (currentModal.value?.options.events) {
|
|
156
|
+
Object.entries(currentModal.value.options.events).forEach(([eventName, handler]) => {
|
|
157
|
+
if (typeof handler === 'function') {
|
|
158
|
+
handlers[eventName] = handler;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 向后兼容:onHeaderAction 和 onClose 回调
|
|
164
|
+
if (currentModal.value?.options.onHeaderAction) {
|
|
165
|
+
handlers.onHeaderAction = currentModal.value.options.onHeaderAction;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return handlers;
|
|
169
|
+
});
|
|
170
|
+
|
|
168
171
|
onShow(() => {
|
|
169
172
|
isPageAlive.value = true;
|
|
170
173
|
isH5.value = process.env.UNI_PLATFORM === 'h5';
|
|
@@ -182,7 +185,7 @@
|
|
|
182
185
|
return options.header?.show !== false;
|
|
183
186
|
};
|
|
184
187
|
|
|
185
|
-
const containerStyle = (options: ModalOptions) => {
|
|
188
|
+
const containerStyle = (options: ModalOptions): Record<string, string> => {
|
|
186
189
|
const style: Record<string, string> = {};
|
|
187
190
|
if (options.maxHeight) {
|
|
188
191
|
style.maxHeight = options.maxHeight;
|
|
@@ -190,7 +193,7 @@
|
|
|
190
193
|
return style;
|
|
191
194
|
};
|
|
192
195
|
|
|
193
|
-
const bodyStyle = (options: ModalOptions) => {
|
|
196
|
+
const bodyStyle = (options: ModalOptions): Record<string, string> => {
|
|
194
197
|
const style: Record<string, string> = {};
|
|
195
198
|
if (options.contentPadding !== false) {
|
|
196
199
|
style.padding = '16px';
|
|
@@ -232,7 +235,7 @@
|
|
|
232
235
|
});
|
|
233
236
|
};
|
|
234
237
|
|
|
235
|
-
const closeCurrentModal = (result?: any) => {
|
|
238
|
+
const closeCurrentModal = (result?: any, fromSubmit = false, closeReason: ModalCloseReason = 'cancel') => {
|
|
236
239
|
if (!currentModal.value || isClosing.value) return;
|
|
237
240
|
|
|
238
241
|
isClosing.value = true;
|
|
@@ -247,7 +250,21 @@
|
|
|
247
250
|
const index = modalStack.value.findIndex((m) => m.id === modal.id);
|
|
248
251
|
if (index !== -1) {
|
|
249
252
|
const removed = modalStack.value.splice(index, 1)[0];
|
|
250
|
-
|
|
253
|
+
|
|
254
|
+
// 触发 onClose 事件(兼容旧的回调方式)
|
|
255
|
+
if (modal.options.onClose) {
|
|
256
|
+
modal.options.onClose(closeReason);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 如果有 events.onClose,也触发(新的事件方式)
|
|
260
|
+
if (modal.options.events?.onClose) {
|
|
261
|
+
modal.options.events.onClose(closeReason);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 只有 submit 时才 resolve 触发 Promise
|
|
265
|
+
if (fromSubmit) {
|
|
266
|
+
removed?.resolve?.(result);
|
|
267
|
+
}
|
|
251
268
|
}
|
|
252
269
|
isClosing.value = false;
|
|
253
270
|
|
|
@@ -260,16 +277,15 @@
|
|
|
260
277
|
}, 320);
|
|
261
278
|
};
|
|
262
279
|
|
|
263
|
-
const updateCurrentModalProps = (newProps: Record<string, any>) => {
|
|
264
|
-
if (currentModal.value) {
|
|
265
|
-
Object.assign(currentModal.value.props, newProps);
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
280
|
const handleHeaderAction = (action: string) => {
|
|
270
281
|
if (currentModal.value?.options.onHeaderAction) {
|
|
271
282
|
currentModal.value.options.onHeaderAction(action, currentModal.value.id);
|
|
272
283
|
}
|
|
284
|
+
|
|
285
|
+
// 新的事件方式
|
|
286
|
+
if (currentModal.value?.options.events?.onHeaderAction) {
|
|
287
|
+
currentModal.value.options.events.onHeaderAction(action, currentModal.value.id);
|
|
288
|
+
}
|
|
273
289
|
};
|
|
274
290
|
|
|
275
291
|
const handleSubmit = async () => {
|
|
@@ -287,7 +303,7 @@
|
|
|
287
303
|
return;
|
|
288
304
|
}
|
|
289
305
|
|
|
290
|
-
closeCurrentModal(result);
|
|
306
|
+
closeCurrentModal(result, true, 'submit');
|
|
291
307
|
} catch (error) {
|
|
292
308
|
console.error('Submit failed:', error);
|
|
293
309
|
} finally {
|
|
@@ -300,12 +316,12 @@
|
|
|
300
316
|
if (process.env.NODE_ENV === 'development') {
|
|
301
317
|
console.warn('[oa-modal-wrapper] 内容组件未暴露 submit 方法,弹窗将直接关闭');
|
|
302
318
|
}
|
|
303
|
-
closeCurrentModal();
|
|
319
|
+
closeCurrentModal(undefined, true, 'submit');
|
|
304
320
|
};
|
|
305
321
|
|
|
306
322
|
const onPopupChange = (e: any) => {
|
|
307
323
|
if (!e.show && !isClosing.value) {
|
|
308
|
-
closeCurrentModal();
|
|
324
|
+
closeCurrentModal(undefined, false, 'mask');
|
|
309
325
|
}
|
|
310
326
|
};
|
|
311
327
|
|
|
@@ -326,7 +342,7 @@
|
|
|
326
342
|
popupRef.value.close();
|
|
327
343
|
}
|
|
328
344
|
} else if (id === currentModal.value?.id) {
|
|
329
|
-
closeCurrentModal(result);
|
|
345
|
+
closeCurrentModal(result, false, 'cancel');
|
|
330
346
|
}
|
|
331
347
|
};
|
|
332
348
|
|
|
@@ -334,7 +350,7 @@
|
|
|
334
350
|
if (!isPageAlive.value) return;
|
|
335
351
|
const { id, props } = params;
|
|
336
352
|
if (id === currentModal.value?.id) {
|
|
337
|
-
|
|
353
|
+
Object.assign(currentModal.value.props, props);
|
|
338
354
|
}
|
|
339
355
|
};
|
|
340
356
|
|
|
@@ -345,7 +361,7 @@
|
|
|
345
361
|
defineExpose({
|
|
346
362
|
closeTopModal: () => {
|
|
347
363
|
if (currentModal.value) {
|
|
348
|
-
closeCurrentModal();
|
|
364
|
+
closeCurrentModal(undefined, false, 'header');
|
|
349
365
|
return true;
|
|
350
366
|
}
|
|
351
367
|
return false;
|
|
@@ -422,7 +438,7 @@
|
|
|
422
438
|
|
|
423
439
|
.header-title {
|
|
424
440
|
font-size: 16px;
|
|
425
|
-
font-weight:
|
|
441
|
+
font-weight: bold;
|
|
426
442
|
color: #19242c;
|
|
427
443
|
line-height: 22px;
|
|
428
444
|
}
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
<script lang="ts" setup>
|
|
10
10
|
import { formatJson } from './utils';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
log: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
defineProps<Props>();
|
|
17
17
|
</script>
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
<script lang="ts" setup>
|
|
11
11
|
import { onBeforeUnmount, ref } from 'vue';
|
|
12
12
|
import { consoleSubject, getConsoleLogs } from 'uniapp-log-sdk';
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
const MAX_CONSOLE_LOGS = 500;
|
|
15
|
+
|
|
16
|
+
const logConvert = (items: any[]): string =>
|
|
14
17
|
items.reduce((prev, current, index) => {
|
|
15
18
|
try {
|
|
16
19
|
prev +=
|
|
@@ -18,14 +21,18 @@
|
|
|
18
21
|
? JSON.stringify(current)
|
|
19
22
|
: current + '' + (index === items.length - 1 ? '' : ',');
|
|
20
23
|
} catch (e) {
|
|
21
|
-
prev
|
|
24
|
+
prev += current;
|
|
22
25
|
}
|
|
23
26
|
return prev;
|
|
24
27
|
}, '');
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
const consoleLogs = ref<string[]>(getConsoleLogs().slice(-MAX_CONSOLE_LOGS).map(logConvert));
|
|
30
|
+
|
|
26
31
|
const stop = consoleSubject.subscribe((data: any) => {
|
|
27
|
-
|
|
32
|
+
const converted = data.slice(-MAX_CONSOLE_LOGS).map(logConvert);
|
|
33
|
+
consoleLogs.value = converted;
|
|
28
34
|
});
|
|
35
|
+
|
|
29
36
|
onBeforeUnmount(() => {
|
|
30
37
|
stop?.unsubscribe();
|
|
31
38
|
});
|
|
@@ -11,10 +11,26 @@
|
|
|
11
11
|
<script lang="ts" setup>
|
|
12
12
|
import { ref, onBeforeUnmount } from 'vue';
|
|
13
13
|
import { errorSubject, getErrorLogs } from 'uniapp-log-sdk';
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
const MAX_ERROR_LOGS = 100;
|
|
16
|
+
|
|
17
|
+
const errorLogs = ref<Record<string, any>[]>([
|
|
18
|
+
...new Set(
|
|
19
|
+
getErrorLogs()
|
|
20
|
+
?.slice(-MAX_ERROR_LOGS)
|
|
21
|
+
.map((item: any) => item.join(';')),
|
|
22
|
+
),
|
|
23
|
+
]);
|
|
24
|
+
|
|
15
25
|
const stop = errorSubject.subscribe((data: Record<string, any>[]) => {
|
|
16
|
-
|
|
26
|
+
const unique = [...new Set(data?.map((item: any) => item.join(';')))];
|
|
27
|
+
if (unique.length > MAX_ERROR_LOGS) {
|
|
28
|
+
errorLogs.value = unique.slice(-MAX_ERROR_LOGS);
|
|
29
|
+
} else {
|
|
30
|
+
errorLogs.value = unique;
|
|
31
|
+
}
|
|
17
32
|
});
|
|
33
|
+
|
|
18
34
|
onBeforeUnmount(() => {
|
|
19
35
|
stop?.unsubscribe();
|
|
20
36
|
});
|
|
@@ -18,17 +18,35 @@
|
|
|
18
18
|
import { ref, onBeforeUnmount } from 'vue';
|
|
19
19
|
import awesomeDisplayInfo from './awesome-display-info.vue';
|
|
20
20
|
import { nativeEventSubject, getNativeEventLogs } from 'uniapp-log-sdk';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
|
|
22
|
+
interface NativeEventLog {
|
|
23
|
+
key: string;
|
|
24
|
+
params: any;
|
|
25
|
+
response: any;
|
|
26
|
+
startTime: number;
|
|
27
|
+
endTime: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const MAX_NATIVE_EVENT_LOGS = 50;
|
|
31
|
+
|
|
32
|
+
const nativeEventLogs = ref<NativeEventLog[]>(getNativeEventLogs().slice(-MAX_NATIVE_EVENT_LOGS));
|
|
33
|
+
|
|
34
|
+
const stop = nativeEventSubject.subscribe((data: NativeEventLog[]) => {
|
|
35
|
+
if (data.length > MAX_NATIVE_EVENT_LOGS) {
|
|
36
|
+
nativeEventLogs.value = data.slice(-MAX_NATIVE_EVENT_LOGS);
|
|
37
|
+
} else {
|
|
38
|
+
nativeEventLogs.value = data;
|
|
39
|
+
}
|
|
24
40
|
});
|
|
25
|
-
|
|
41
|
+
|
|
42
|
+
const getTitle = (log: NativeEventLog): string => {
|
|
26
43
|
const { startTime, endTime, key } = log;
|
|
27
44
|
if (startTime && endTime) {
|
|
28
45
|
return `${key};${(endTime - startTime) / 1000}s`;
|
|
29
46
|
}
|
|
30
47
|
return key;
|
|
31
48
|
};
|
|
49
|
+
|
|
32
50
|
onBeforeUnmount(() => {
|
|
33
51
|
stop?.unsubscribe();
|
|
34
52
|
});
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<uni-collapse>
|
|
3
3
|
<uni-collapse-item
|
|
4
|
-
v-for="log in networkLogs"
|
|
4
|
+
v-for="(log, index) in networkLogs"
|
|
5
5
|
:title="getTitle(log)"
|
|
6
6
|
:key="log.request.url + log.response?.data?.errno"
|
|
7
|
-
:class="{
|
|
7
|
+
:class="{
|
|
8
|
+
'uni-collapse-item-failed': log.response && (log.response.statusCode !== 200 || log.response.data?.errno !== 0),
|
|
9
|
+
}"
|
|
8
10
|
>
|
|
9
|
-
<view style="font-size: 10px">
|
|
10
|
-
<view style="word-break: break-all; padding: 0
|
|
11
|
+
<view style="font-size: 10px; padding: 10px 15px">
|
|
12
|
+
<view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
|
|
11
13
|
<text :selectable="true" style="font-weight: bolder">状态码:</text>
|
|
12
14
|
{{ log.request.method }} {{ log.response?.statusCode }}
|
|
13
15
|
</view>
|
|
14
|
-
<view style="word-break: break-all; padding: 0
|
|
16
|
+
<view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
|
|
15
17
|
<text :selectable="true" style="font-weight: bolder">请求头:</text>
|
|
16
18
|
<awesome-display-info :log="log.request.header" />
|
|
17
19
|
</view>
|
|
18
|
-
<view style="word-break: break-all; padding: 0
|
|
20
|
+
<view style="word-break: break-all; padding: 8px 0; margin-bottom: 10px">
|
|
19
21
|
<text :selectable="true" style="font-weight: bolder">请求参数:</text>
|
|
20
22
|
<awesome-display-info :log="log.request.data" />
|
|
21
23
|
</view>
|
|
22
|
-
<view style="word-break: break-all; padding: 0
|
|
24
|
+
<view style="word-break: break-all; padding: 8px 0">
|
|
23
25
|
<text :selectable="true" style="font-weight: bolder">响应数据:</text>
|
|
24
26
|
<awesome-display-info :log="log.response" />
|
|
25
27
|
</view>
|
|
@@ -32,17 +34,45 @@
|
|
|
32
34
|
import { ref, onBeforeUnmount } from 'vue';
|
|
33
35
|
import awesomeDisplayInfo from './awesome-display-info.vue';
|
|
34
36
|
import { networkSubject, getNetworkLogs } from 'uniapp-log-sdk';
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
interface NetworkLog {
|
|
39
|
+
request: any;
|
|
40
|
+
response: any;
|
|
41
|
+
startTime: number;
|
|
42
|
+
endTime: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const MAX_NETWORK_LOGS = 100;
|
|
46
|
+
|
|
47
|
+
const networkLogs = ref<NetworkLog[]>(getNetworkLogs().slice(-MAX_NETWORK_LOGS));
|
|
48
|
+
|
|
49
|
+
const stop = networkSubject.subscribe((data: NetworkLog[]) => {
|
|
50
|
+
if (data.length > MAX_NETWORK_LOGS) {
|
|
51
|
+
networkLogs.value = data.slice(-MAX_NETWORK_LOGS);
|
|
52
|
+
} else {
|
|
53
|
+
networkLogs.value = data;
|
|
54
|
+
}
|
|
38
55
|
});
|
|
39
|
-
|
|
56
|
+
|
|
57
|
+
const getTitle = (log: NetworkLog): string => {
|
|
40
58
|
const { startTime, endTime, request } = log;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
return request.url;
|
|
59
|
+
const timeStr = formatTime(startTime);
|
|
60
|
+
const duration = endTime && startTime ? `${((endTime - startTime) / 1000).toFixed(2)}s` : 'pending';
|
|
61
|
+
return `[${timeStr}] ${request.url};${duration}`;
|
|
45
62
|
};
|
|
63
|
+
|
|
64
|
+
const formatTime = (timestamp: number): string => {
|
|
65
|
+
const date = new Date(timestamp);
|
|
66
|
+
const year = date.getFullYear();
|
|
67
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
68
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
69
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
70
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
71
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
72
|
+
const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
|
|
73
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
74
|
+
};
|
|
75
|
+
|
|
46
76
|
onBeforeUnmount(() => {
|
|
47
77
|
stop?.unsubscribe();
|
|
48
78
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
styleType="text"
|
|
6
6
|
:values="list"
|
|
7
7
|
:current="current"
|
|
8
|
-
@clickItem="
|
|
8
|
+
@clickItem="handleTabClick"
|
|
9
9
|
></uni-segmented-control>
|
|
10
10
|
<view style="height: calc(100% - 48px); overflow-y: auto; padding: 8px">
|
|
11
11
|
<template v-if="current === 0">
|
|
@@ -51,24 +51,38 @@
|
|
|
51
51
|
import Storage from './storage.vue';
|
|
52
52
|
import Console from './console.vue';
|
|
53
53
|
import System from './system.vue';
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
interface SegmentedControlClickEvent {
|
|
56
|
+
currentIndex: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const popupRef = ref<any>();
|
|
55
60
|
const list = ref(['console', 'network', 'event', 'error', 'storage', 'system']);
|
|
56
61
|
const current = ref(0);
|
|
62
|
+
|
|
63
|
+
const handleTabClick = (e: SegmentedControlClickEvent) => {
|
|
64
|
+
current.value = e.currentIndex;
|
|
65
|
+
};
|
|
66
|
+
|
|
57
67
|
const openDebug = () => {
|
|
58
68
|
popupRef.value?.open('bottom');
|
|
59
69
|
};
|
|
70
|
+
|
|
60
71
|
const bottom = ref(200);
|
|
61
72
|
const right = ref(10);
|
|
62
|
-
let pageX: number
|
|
73
|
+
let pageX: number;
|
|
74
|
+
let pageY: number;
|
|
75
|
+
|
|
63
76
|
const start = (e: any) => {
|
|
64
|
-
|
|
77
|
+
const page = e.changedTouches[0];
|
|
65
78
|
pageX = page.pageX;
|
|
66
79
|
pageY = page.pageY;
|
|
67
80
|
};
|
|
81
|
+
|
|
68
82
|
const move = (e: any) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
const page = e.changedTouches[0];
|
|
84
|
+
const x = page.pageX - pageX;
|
|
85
|
+
const y = page.pageY - pageY;
|
|
72
86
|
pageX = page.pageX;
|
|
73
87
|
pageY = page.pageY;
|
|
74
88
|
right.value = right.value - x;
|
|
@@ -8,17 +8,45 @@
|
|
|
8
8
|
</uni-collapse>
|
|
9
9
|
</template>
|
|
10
10
|
<script lang="ts" setup>
|
|
11
|
-
import { ref } from 'vue';
|
|
11
|
+
import { ref, onBeforeUnmount } from 'vue';
|
|
12
12
|
import awesomeDisplayInfo from './awesome-display-info.vue';
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
interface StorageItem {
|
|
15
|
+
key: string;
|
|
16
|
+
data: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const MAX_STORAGE_ITEMS = 50;
|
|
20
|
+
|
|
21
|
+
// 标记组件是否已挂载(用于防止异步回调导致的内存泄漏)
|
|
22
|
+
let isComponentMounted = true;
|
|
23
|
+
|
|
24
|
+
const storageLogs = ref<StorageItem[]>([]);
|
|
25
|
+
|
|
26
|
+
// 组件卸载时标记
|
|
27
|
+
onBeforeUnmount(() => {
|
|
28
|
+
isComponentMounted = false;
|
|
29
|
+
});
|
|
30
|
+
|
|
14
31
|
uni.getStorageInfo({
|
|
15
32
|
success: function (res) {
|
|
16
|
-
|
|
33
|
+
// 如果组件已卸载,直接返回,防止内存泄漏
|
|
34
|
+
if (!isComponentMounted) return;
|
|
35
|
+
|
|
36
|
+
const keys = res.keys || [];
|
|
37
|
+
const displayKeys = keys.slice(-MAX_STORAGE_ITEMS);
|
|
38
|
+
|
|
39
|
+
displayKeys.forEach((key) => {
|
|
17
40
|
storageLogs.value.push({
|
|
18
41
|
key,
|
|
19
42
|
data: uni.getStorageSync(key),
|
|
20
43
|
});
|
|
21
44
|
});
|
|
45
|
+
|
|
46
|
+
// 如果有省略的 key,在列表开头显示提示
|
|
47
|
+
if (keys.length > MAX_STORAGE_ITEMS) {
|
|
48
|
+
console.warn(`[Storage] 共有 ${keys.length} 个存储项,仅显示最近 ${MAX_STORAGE_ITEMS} 个`);
|
|
49
|
+
}
|
|
22
50
|
},
|
|
23
51
|
});
|
|
24
52
|
</script>
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
const MAX_DEPTH = 10;
|
|
2
|
+
const MAX_ARRAY_LENGTH = 10;
|
|
3
|
+
const MAX_STRING_LENGTH = 300;
|
|
4
|
+
|
|
5
|
+
interface RichTextNode {
|
|
6
|
+
type: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const formatJson = (data: any, indent = 0): RichTextNode[] => {
|
|
2
11
|
const indentStr = ' '.repeat(indent);
|
|
3
12
|
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
13
|
+
// 防止死循环,限制最大递归深度(indent 每层增加 4,所以深度 = indent / 4)
|
|
14
|
+
const currentDepth = Math.floor(indent / 4);
|
|
15
|
+
if (currentDepth > MAX_DEPTH) {
|
|
16
|
+
return [{ type: 'text', text: '...' }];
|
|
7
17
|
}
|
|
8
18
|
|
|
9
19
|
if (Array.isArray(data)) {
|
|
@@ -15,32 +25,46 @@ export const formatJson = (data: any, indent = 0) => {
|
|
|
15
25
|
}
|
|
16
26
|
};
|
|
17
27
|
|
|
18
|
-
const formatArray = (data: any[], indent: number, indentStr: string) => {
|
|
28
|
+
const formatArray = (data: any[], indent: number, indentStr: string): RichTextNode[] => {
|
|
19
29
|
if (data.length === 0) {
|
|
20
30
|
return [{ type: 'text', text: '[]' }];
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
let formatted = [{ type: 'text', text: '[\n' }];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
let formatted: RichTextNode[] = [{ type: 'text', text: '[\n' }];
|
|
34
|
+
|
|
35
|
+
// 如果数组长度超过限制,只显示前 MAX_ARRAY_LENGTH 个
|
|
36
|
+
const displayLength = Math.min(data.length, MAX_ARRAY_LENGTH);
|
|
37
|
+
const hasOmitted = data.length > MAX_ARRAY_LENGTH;
|
|
38
|
+
|
|
39
|
+
data.slice(0, displayLength).forEach((item, index) => {
|
|
40
|
+
formatted.push({ type: 'text', text: indentStr + ' '.repeat(4) });
|
|
41
|
+
formatted.push(...formatJson(item, indent + 4));
|
|
42
|
+
if (index < displayLength - 1) {
|
|
28
43
|
formatted.push({ type: 'text', text: ',\n' });
|
|
29
44
|
} else {
|
|
30
45
|
formatted.push({ type: 'text', text: '\n' });
|
|
31
46
|
}
|
|
32
47
|
});
|
|
48
|
+
|
|
49
|
+
// 如果有省略的元素,显示提示
|
|
50
|
+
if (hasOmitted) {
|
|
51
|
+
formatted.push({
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: indentStr + `... (共 ${data.length} 个,显示 ${displayLength} 个)\n`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
formatted.push({ type: 'text', text: indentStr + ']' });
|
|
34
58
|
|
|
35
59
|
return formatted;
|
|
36
60
|
};
|
|
37
61
|
|
|
38
|
-
const formatObject = (data: Record<string, any>, indent: number, indentStr: string) => {
|
|
39
|
-
let formatted = [{ type: 'text', text: '{\n' }];
|
|
62
|
+
const formatObject = (data: Record<string, any>, indent: number, indentStr: string): RichTextNode[] => {
|
|
63
|
+
let formatted: RichTextNode[] = [{ type: 'text', text: '{\n' }];
|
|
40
64
|
const keys = Object.keys(data);
|
|
41
65
|
keys.forEach((key, index) => {
|
|
42
|
-
formatted.push({ type: 'text', text: indentStr + ' '.repeat(
|
|
43
|
-
formatted.push(...formatJson(data[key], indent +
|
|
66
|
+
formatted.push({ type: 'text', text: indentStr + ' '.repeat(4) + key + ': ' });
|
|
67
|
+
formatted.push(...formatJson(data[key], indent + 4));
|
|
44
68
|
if (index < keys.length - 1) {
|
|
45
69
|
formatted.push({ type: 'text', text: ',\n' });
|
|
46
70
|
}
|
|
@@ -50,8 +74,17 @@ const formatObject = (data: Record<string, any>, indent: number, indentStr: stri
|
|
|
50
74
|
return formatted;
|
|
51
75
|
};
|
|
52
76
|
|
|
53
|
-
const formatPrimitive = (data: any) => {
|
|
77
|
+
const formatPrimitive = (data: any): RichTextNode[] => {
|
|
54
78
|
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
|
+
];
|
|
87
|
+
}
|
|
55
88
|
return [{ type: 'text', text: `"${data}"` }];
|
|
56
89
|
} else {
|
|
57
90
|
return [{ type: 'text', text: String(data) }];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _mp_rt1_vue___Ref from 'vue';
|
|
2
|
-
import { Component,
|
|
2
|
+
import { Component, Plugin } from 'vue';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* 上报APP启动小程序的时候的入参
|
|
@@ -74,6 +74,18 @@ declare function createDebounceFn(callback: (...params: any[]) => void, time: nu
|
|
|
74
74
|
*/
|
|
75
75
|
declare function getTempFilePathForCookie(url: string, cookie?: string): Promise<string>;
|
|
76
76
|
|
|
77
|
+
/** 弹框关闭原因类型 */
|
|
78
|
+
type ModalCloseReason = 'submit' | 'cancel' | 'mask' | 'header';
|
|
79
|
+
/** 基础事件接口 - 所有弹框都应该支持 */
|
|
80
|
+
interface ModalBaseEvents {
|
|
81
|
+
/** 弹框关闭时触发 */
|
|
82
|
+
onClose?: (reason: ModalCloseReason) => void;
|
|
83
|
+
}
|
|
84
|
+
/** 头部事件接口 */
|
|
85
|
+
interface ModalHeaderEvents {
|
|
86
|
+
/** 头部操作按钮点击回调 */
|
|
87
|
+
onHeaderAction?: (action: string, modalId: string) => void;
|
|
88
|
+
}
|
|
77
89
|
interface ModalHeaderOptions {
|
|
78
90
|
/** 是否显示头部 */
|
|
79
91
|
show?: boolean;
|
|
@@ -98,7 +110,7 @@ interface ModalHeaderOptions {
|
|
|
98
110
|
/** 提交按钮的属性 */
|
|
99
111
|
submitProps?: Record<string, any>;
|
|
100
112
|
}
|
|
101
|
-
interface ModalOptions {
|
|
113
|
+
interface ModalOptions<E extends ModalBaseEvents = ModalBaseEvents> {
|
|
102
114
|
/** 弹框类型:center(中间) | top(顶部) | bottom(底部) | left(左侧) | right(右侧) */
|
|
103
115
|
type?: 'center' | 'top' | 'bottom' | 'left' | 'right';
|
|
104
116
|
/** 点击蒙层是否关闭弹框(已废弃,使用 isMaskClick) */
|
|
@@ -125,16 +137,22 @@ interface ModalOptions {
|
|
|
125
137
|
maxHeight?: string;
|
|
126
138
|
/** 是否显示蒙层,默认 true */
|
|
127
139
|
showMask?: boolean;
|
|
128
|
-
/**
|
|
140
|
+
/** 内容组件的事件回调对象 */
|
|
141
|
+
events?: E & Record<string, (...args: any[]) => void>;
|
|
142
|
+
/** 头部操作按钮点击回调(已废弃,使用 events.onHeaderAction) */
|
|
129
143
|
onHeaderAction?: (action: string, modalId: string) => void;
|
|
144
|
+
/** 弹框关闭回调(已废弃,使用 events.onClose) */
|
|
145
|
+
onClose?: (reason?: ModalCloseReason) => void;
|
|
130
146
|
}
|
|
131
|
-
interface CreateModalReturn {
|
|
132
|
-
/**
|
|
133
|
-
|
|
147
|
+
interface CreateModalReturn<E extends ModalBaseEvents = ModalBaseEvents> extends Promise<any> {
|
|
148
|
+
/** 监听特定事件 */
|
|
149
|
+
on<K extends keyof E>(event: K, callback: E[K] extends (...args: infer Args) => void ? (...args: Args) => void : never): this;
|
|
150
|
+
/** 订阅弹框关闭事件,获取返回结果(向后兼容) */
|
|
151
|
+
subscribe(callback: (result: any) => void): this;
|
|
134
152
|
/** 关闭弹框 */
|
|
135
|
-
close
|
|
153
|
+
close(result?: any): void;
|
|
136
154
|
/** 更新弹框内容组件的 props */
|
|
137
|
-
updateProps
|
|
155
|
+
updateProps(props: Record<string, any>): void;
|
|
138
156
|
/** Promise then 方法 */
|
|
139
157
|
then: Promise<any>['then'];
|
|
140
158
|
/** Promise catch 方法 */
|
|
@@ -143,12 +161,32 @@ interface CreateModalReturn {
|
|
|
143
161
|
/**
|
|
144
162
|
* 函数式打开弹框
|
|
145
163
|
* @param component 弹框内容组件
|
|
146
|
-
* @param options
|
|
164
|
+
* @param options 弹框配置和内容组件属性
|
|
147
165
|
* @returns 弹框控制器
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* interface UserFormEvents extends ModalBaseEvents {
|
|
170
|
+
* onValidationError?: (errors: Record<string, string>) => void
|
|
171
|
+
* onFieldChange?: (field: string, value: any) => void
|
|
172
|
+
* }
|
|
173
|
+
*
|
|
174
|
+
* createModal<UserFormEvents>(UserForm, {
|
|
175
|
+
* title: '编辑用户',
|
|
176
|
+
* props: { userId: '123' },
|
|
177
|
+
* events: {
|
|
178
|
+
* onValidationError: (errors) => console.log(errors),
|
|
179
|
+
* onFieldChange: (field, value) => console.log(field, value),
|
|
180
|
+
* onClose: (reason) => console.log('关闭原因:', reason)
|
|
181
|
+
* }
|
|
182
|
+
* })
|
|
183
|
+
* .on('onValidationError', (errors) => { ... })
|
|
184
|
+
* .then((result) => console.log('最终结果:', result))
|
|
185
|
+
* ```
|
|
148
186
|
*/
|
|
149
|
-
declare function createModal(component: Component, options?: ModalOptions & {
|
|
187
|
+
declare function createModal<E extends ModalBaseEvents = ModalBaseEvents>(component: Component, options?: ModalOptions<E> & {
|
|
150
188
|
props?: Record<string, any>;
|
|
151
|
-
}): CreateModalReturn
|
|
189
|
+
}): CreateModalReturn<E>;
|
|
152
190
|
/**
|
|
153
191
|
* 关闭所有弹框
|
|
154
192
|
*/
|
|
@@ -281,9 +319,7 @@ declare function useCookieTempFileUrl(url: string): {
|
|
|
281
319
|
tempUrl: _mp_rt1_vue___Ref<string>;
|
|
282
320
|
};
|
|
283
321
|
|
|
284
|
-
declare const
|
|
285
|
-
install: (app: App<Element>) => void;
|
|
286
|
-
};
|
|
322
|
+
declare const plugin: Plugin;
|
|
287
323
|
/**
|
|
288
324
|
* 向客户端获取数据
|
|
289
325
|
* @param eventName 事件名称
|
|
@@ -293,4 +329,4 @@ declare const _default: {
|
|
|
293
329
|
*/
|
|
294
330
|
declare function getDataByApp(eventName: string, params: Record<string, any>, immediate?: boolean): Promise<any>;
|
|
295
331
|
|
|
296
|
-
export { CreateModalReturn, Message, MessageBox, ModalHeaderOptions, ModalOptions, RouteMethodName, VersionCheckRuntimeOptions, VersionCheckRuntimeState, VersionCheckSource, VersionCheckTriggerContext, closeAllModals, compareVersions, createDebounceFn, createModal, createVersionCheckRuntime,
|
|
332
|
+
export { CreateModalReturn, Message, MessageBox, ModalBaseEvents, ModalCloseReason, ModalHeaderEvents, ModalHeaderOptions, ModalOptions, RouteMethodName, VersionCheckRuntimeOptions, VersionCheckRuntimeState, VersionCheckSource, VersionCheckTriggerContext, closeAllModals, compareVersions, createDebounceFn, createModal, createVersionCheckRuntime, plugin as default, getAppVersion, getDataByApp, getTempFilePathForCookie, isCurrentVersionHigher, sendLaunchAppParamsLog, useCookieTempFileUrl, versionCheckRuntime };
|
package/dist/index.esm.js
CHANGED
|
@@ -741,8 +741,28 @@ function __rest(s, e) {
|
|
|
741
741
|
/**
|
|
742
742
|
* 函数式打开弹框
|
|
743
743
|
* @param component 弹框内容组件
|
|
744
|
-
* @param options
|
|
744
|
+
* @param options 弹框配置和内容组件属性
|
|
745
745
|
* @returns 弹框控制器
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* ```ts
|
|
749
|
+
* interface UserFormEvents extends ModalBaseEvents {
|
|
750
|
+
* onValidationError?: (errors: Record<string, string>) => void
|
|
751
|
+
* onFieldChange?: (field: string, value: any) => void
|
|
752
|
+
* }
|
|
753
|
+
*
|
|
754
|
+
* createModal<UserFormEvents>(UserForm, {
|
|
755
|
+
* title: '编辑用户',
|
|
756
|
+
* props: { userId: '123' },
|
|
757
|
+
* events: {
|
|
758
|
+
* onValidationError: (errors) => console.log(errors),
|
|
759
|
+
* onFieldChange: (field, value) => console.log(field, value),
|
|
760
|
+
* onClose: (reason) => console.log('关闭原因:', reason)
|
|
761
|
+
* }
|
|
762
|
+
* })
|
|
763
|
+
* .on('onValidationError', (errors) => { ... })
|
|
764
|
+
* .then((result) => console.log('最终结果:', result))
|
|
765
|
+
* ```
|
|
746
766
|
*/
|
|
747
767
|
function createModal(component) {
|
|
748
768
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -751,6 +771,7 @@ function createModal(component) {
|
|
|
751
771
|
var promise = new Promise(function (resolve, reject) {
|
|
752
772
|
promiseResolve = resolve;
|
|
753
773
|
});
|
|
774
|
+
var eventListeners = new Map();
|
|
754
775
|
var props = options.props,
|
|
755
776
|
modalOptions = __rest(options, ["props"]);
|
|
756
777
|
// 发送打开事件
|
|
@@ -764,6 +785,13 @@ function createModal(component) {
|
|
|
764
785
|
}
|
|
765
786
|
});
|
|
766
787
|
return {
|
|
788
|
+
on: function on(event, callback) {
|
|
789
|
+
if (!eventListeners.has(event)) {
|
|
790
|
+
eventListeners.set(event, new Set());
|
|
791
|
+
}
|
|
792
|
+
eventListeners.get(event).add(callback);
|
|
793
|
+
return this;
|
|
794
|
+
},
|
|
767
795
|
subscribe: function subscribe(callback) {
|
|
768
796
|
promise.then(function (result) {
|
|
769
797
|
return callback === null || callback === void 0 ? void 0 : callback(result);
|
|
@@ -3887,7 +3915,7 @@ var install = function install(app) {
|
|
|
3887
3915
|
}
|
|
3888
3916
|
});
|
|
3889
3917
|
};
|
|
3890
|
-
var
|
|
3918
|
+
var plugin = {
|
|
3891
3919
|
install: install
|
|
3892
3920
|
};
|
|
3893
3921
|
/**
|
|
@@ -3920,4 +3948,4 @@ function getDataByApp(eventName, params) {
|
|
|
3920
3948
|
});
|
|
3921
3949
|
}
|
|
3922
3950
|
|
|
3923
|
-
export { Message, MessageBox, closeAllModals, compareVersions, createDebounceFn, createModal, createVersionCheckRuntime,
|
|
3951
|
+
export { Message, MessageBox, closeAllModals, compareVersions, createDebounceFn, createModal, createVersionCheckRuntime, plugin as default, getAppVersion, getDataByApp, getTempFilePathForCookie, isCurrentVersionHigher, sendLaunchAppParamsLog, useCookieTempFileUrl, versionCheckRuntime };
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { App } from 'vue';
|
|
1
|
+
import type { App, Plugin } from 'vue';
|
|
2
2
|
|
|
3
3
|
import { sendLaunchAppParamsLog } from './utils';
|
|
4
4
|
export * from './utils';
|
|
5
5
|
export * from './composables';
|
|
6
6
|
|
|
7
|
-
const install = (app: App
|
|
7
|
+
const install = (app: App) => {
|
|
8
8
|
app.mixin({
|
|
9
9
|
onLaunch(params: any) {
|
|
10
10
|
sendLaunchAppParamsLog(params);
|
|
@@ -12,10 +12,12 @@ const install = (app: App<Element>) => {
|
|
|
12
12
|
});
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const plugin: Plugin = {
|
|
16
16
|
install,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
export default plugin;
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* 向客户端获取数据
|
|
21
23
|
* @param eventName 事件名称
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { Component } from 'vue';
|
|
2
2
|
import { OPEN_MODAL, CLOSE_MODAL, UPDATE_MODAL } from '../../constants';
|
|
3
3
|
|
|
4
|
+
/** 弹框关闭原因类型 */
|
|
5
|
+
export type ModalCloseReason = 'submit' | 'cancel' | 'mask' | 'header';
|
|
6
|
+
|
|
7
|
+
/** 基础事件接口 - 所有弹框都应该支持 */
|
|
8
|
+
export interface ModalBaseEvents {
|
|
9
|
+
/** 弹框关闭时触发 */
|
|
10
|
+
onClose?: (reason: ModalCloseReason) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 头部事件接口 */
|
|
14
|
+
export interface ModalHeaderEvents {
|
|
15
|
+
/** 头部操作按钮点击回调 */
|
|
16
|
+
onHeaderAction?: (action: string, modalId: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
export interface ModalHeaderOptions {
|
|
5
20
|
/** 是否显示头部 */
|
|
6
21
|
show?: boolean;
|
|
@@ -26,7 +41,7 @@ export interface ModalHeaderOptions {
|
|
|
26
41
|
submitProps?: Record<string, any>;
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
export interface ModalOptions {
|
|
44
|
+
export interface ModalOptions<E extends ModalBaseEvents = ModalBaseEvents> {
|
|
30
45
|
/** 弹框类型:center(中间) | top(顶部) | bottom(底部) | left(左侧) | right(右侧) */
|
|
31
46
|
type?: 'center' | 'top' | 'bottom' | 'left' | 'right';
|
|
32
47
|
/** 点击蒙层是否关闭弹框(已废弃,使用 isMaskClick) */
|
|
@@ -53,19 +68,33 @@ export interface ModalOptions {
|
|
|
53
68
|
maxHeight?: string;
|
|
54
69
|
/** 是否显示蒙层,默认 true */
|
|
55
70
|
showMask?: boolean;
|
|
56
|
-
/**
|
|
71
|
+
/** 内容组件的事件回调对象 */
|
|
72
|
+
events?: E & Record<string, (...args: any[]) => void>;
|
|
73
|
+
/** 头部操作按钮点击回调(已废弃,使用 events.onHeaderAction) */
|
|
57
74
|
onHeaderAction?: (action: string, modalId: string) => void;
|
|
75
|
+
/** 弹框关闭回调(已废弃,使用 events.onClose) */
|
|
76
|
+
onClose?: (reason?: ModalCloseReason) => void;
|
|
58
77
|
}
|
|
59
78
|
|
|
60
|
-
export interface CreateModalReturn {
|
|
61
|
-
/**
|
|
62
|
-
|
|
79
|
+
export interface CreateModalReturn<E extends ModalBaseEvents = ModalBaseEvents> extends Promise<any> {
|
|
80
|
+
/** 监听特定事件 */
|
|
81
|
+
on<K extends keyof E>(
|
|
82
|
+
event: K,
|
|
83
|
+
callback: E[K] extends (...args: infer Args) => void ? (...args: Args) => void : never,
|
|
84
|
+
): this;
|
|
85
|
+
|
|
86
|
+
/** 订阅弹框关闭事件,获取返回结果(向后兼容) */
|
|
87
|
+
subscribe(callback: (result: any) => void): this;
|
|
88
|
+
|
|
63
89
|
/** 关闭弹框 */
|
|
64
|
-
close
|
|
90
|
+
close(result?: any): void;
|
|
91
|
+
|
|
65
92
|
/** 更新弹框内容组件的 props */
|
|
66
|
-
updateProps
|
|
93
|
+
updateProps(props: Record<string, any>): void;
|
|
94
|
+
|
|
67
95
|
/** Promise then 方法 */
|
|
68
96
|
then: Promise<any>['then'];
|
|
97
|
+
|
|
69
98
|
/** Promise catch 方法 */
|
|
70
99
|
catch: Promise<any>['catch'];
|
|
71
100
|
}
|
|
@@ -73,13 +102,33 @@ export interface CreateModalReturn {
|
|
|
73
102
|
/**
|
|
74
103
|
* 函数式打开弹框
|
|
75
104
|
* @param component 弹框内容组件
|
|
76
|
-
* @param options
|
|
105
|
+
* @param options 弹框配置和内容组件属性
|
|
77
106
|
* @returns 弹框控制器
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* interface UserFormEvents extends ModalBaseEvents {
|
|
111
|
+
* onValidationError?: (errors: Record<string, string>) => void
|
|
112
|
+
* onFieldChange?: (field: string, value: any) => void
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* createModal<UserFormEvents>(UserForm, {
|
|
116
|
+
* title: '编辑用户',
|
|
117
|
+
* props: { userId: '123' },
|
|
118
|
+
* events: {
|
|
119
|
+
* onValidationError: (errors) => console.log(errors),
|
|
120
|
+
* onFieldChange: (field, value) => console.log(field, value),
|
|
121
|
+
* onClose: (reason) => console.log('关闭原因:', reason)
|
|
122
|
+
* }
|
|
123
|
+
* })
|
|
124
|
+
* .on('onValidationError', (errors) => { ... })
|
|
125
|
+
* .then((result) => console.log('最终结果:', result))
|
|
126
|
+
* ```
|
|
78
127
|
*/
|
|
79
|
-
export function createModal(
|
|
128
|
+
export function createModal<E extends ModalBaseEvents = ModalBaseEvents>(
|
|
80
129
|
component: Component,
|
|
81
|
-
options: ModalOptions & { props?: Record<string, any> } = {},
|
|
82
|
-
): CreateModalReturn {
|
|
130
|
+
options: ModalOptions<E> & { props?: Record<string, any> } = {},
|
|
131
|
+
): CreateModalReturn<E> {
|
|
83
132
|
const id = `modal-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
84
133
|
|
|
85
134
|
let promiseResolve: (value: any) => void;
|
|
@@ -90,6 +139,8 @@ export function createModal(
|
|
|
90
139
|
promiseReject = reject;
|
|
91
140
|
});
|
|
92
141
|
|
|
142
|
+
const eventListeners = new Map<string, Set<Function>>();
|
|
143
|
+
|
|
93
144
|
const { props, ...modalOptions } = options;
|
|
94
145
|
|
|
95
146
|
// 发送打开事件
|
|
@@ -104,6 +155,14 @@ export function createModal(
|
|
|
104
155
|
});
|
|
105
156
|
|
|
106
157
|
return {
|
|
158
|
+
on(event: string, callback: Function) {
|
|
159
|
+
if (!eventListeners.has(event)) {
|
|
160
|
+
eventListeners.set(event, new Set());
|
|
161
|
+
}
|
|
162
|
+
eventListeners.get(event)!.add(callback);
|
|
163
|
+
return this;
|
|
164
|
+
},
|
|
165
|
+
|
|
107
166
|
subscribe(callback: (result: any) => void) {
|
|
108
167
|
promise.then((result) => callback?.(result));
|
|
109
168
|
return this;
|
|
@@ -119,7 +178,7 @@ export function createModal(
|
|
|
119
178
|
|
|
120
179
|
then: promise.then.bind(promise),
|
|
121
180
|
catch: promise.catch.bind(promise),
|
|
122
|
-
}
|
|
181
|
+
} as CreateModalReturn<E>;
|
|
123
182
|
}
|
|
124
183
|
|
|
125
184
|
/**
|