uview-pro 0.3.0 → 0.3.1
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/changelog.md +584 -574
- package/components/common/props.ts +22 -22
- package/components/u-action-sheet/types.ts +37 -37
- package/components/u-action-sheet/u-action-sheet.vue +178 -178
- package/components/u-alert-tips/types.ts +41 -41
- package/components/u-alert-tips/u-alert-tips.vue +238 -238
- package/components/u-avatar/types.ts +36 -36
- package/components/u-avatar/u-avatar.vue +217 -217
- package/components/u-avatar-cropper/types.ts +23 -23
- package/components/u-avatar-cropper/u-avatar-cropper.vue +297 -297
- package/components/u-avatar-cropper/weCropper.d.ts +62 -62
- package/components/u-avatar-cropper/weCropper.js +1281 -1281
- package/components/u-avatar-cropper/weCropper.ts +1276 -1276
- package/components/u-back-top/types.ts +36 -36
- package/components/u-back-top/u-back-top.vue +140 -140
- package/components/u-badge/types.ts +38 -38
- package/components/u-badge/u-badge.vue +183 -183
- package/components/u-button/types.ts +66 -66
- package/components/u-button/u-button.vue +579 -579
- package/components/u-calendar/types.ts +75 -75
- package/components/u-calendar/u-calendar.vue +793 -793
- package/components/u-car-keyboard/types.ts +14 -14
- package/components/u-car-keyboard/u-car-keyboard.vue +262 -262
- package/components/u-card/types.ts +61 -61
- package/components/u-card/u-card.vue +209 -209
- package/components/u-cell-group/types.ts +19 -19
- package/components/u-cell-group/u-cell-group.vue +60 -60
- package/components/u-cell-item/types.ts +56 -56
- package/components/u-cell-item/u-cell-item.vue +226 -226
- package/components/u-checkbox/types.ts +33 -33
- package/components/u-checkbox/u-checkbox.vue +282 -282
- package/components/u-checkbox-group/types.ts +34 -34
- package/components/u-checkbox-group/u-checkbox-group.vue +130 -130
- package/components/u-circle-progress/types.ts +54 -54
- package/components/u-circle-progress/u-circle-progress.vue +206 -206
- package/components/u-city-select/types.ts +22 -22
- package/components/u-city-select/u-city-select.vue +276 -276
- package/components/u-col/types.ts +32 -32
- package/components/u-col/u-col.vue +142 -142
- package/components/u-collapse/types.ts +33 -33
- package/components/u-collapse/u-collapse.vue +190 -190
- package/components/u-collapse-item/types.ts +27 -27
- package/components/u-collapse-item/u-collapse-item.vue +290 -290
- package/components/u-column-notice/types.ts +50 -50
- package/components/u-column-notice/u-column-notice.vue +222 -222
- package/components/u-count-down/types.ts +44 -44
- package/components/u-count-down/u-count-down.vue +286 -286
- package/components/u-count-to/types.ts +34 -34
- package/components/u-count-to/u-count-to.vue +266 -266
- package/components/u-divider/types.ts +33 -33
- package/components/u-divider/u-divider.vue +145 -145
- package/components/u-dropdown/types.ts +34 -34
- package/components/u-dropdown/u-dropdown.vue +330 -330
- package/components/u-dropdown-item/types.ts +29 -29
- package/components/u-dropdown-item/u-dropdown-item.vue +120 -120
- package/components/u-empty/types.ts +38 -38
- package/components/u-empty/u-empty.vue +103 -103
- package/components/u-field/types.ts +71 -71
- package/components/u-field/u-field.vue +388 -388
- package/components/u-form/types.ts +29 -29
- package/components/u-form/u-form.vue +130 -130
- package/components/u-form-item/types.ts +72 -72
- package/components/u-form-item/u-form-item.vue +447 -447
- package/components/u-full-screen/types.ts +16 -16
- package/components/u-full-screen/u-full-screen.vue +103 -103
- package/components/u-gap/types.ts +20 -20
- package/components/u-gap/u-gap.vue +50 -50
- package/components/u-grid/types.ts +21 -21
- package/components/u-grid/u-grid.vue +91 -91
- package/components/u-grid-item/types.ts +16 -16
- package/components/u-grid-item/u-grid-item.vue +130 -130
- package/components/u-icon/types.ts +61 -61
- package/components/u-icon/u-icon.vue +296 -296
- package/components/u-image/types.ts +51 -51
- package/components/u-image/u-image.vue +239 -239
- package/components/u-index-anchor/types.ts +16 -16
- package/components/u-index-anchor/u-index-anchor.vue +94 -94
- package/components/u-index-list/types.ts +43 -43
- package/components/u-index-list/u-index-list.vue +352 -352
- package/components/u-input/types.ts +137 -137
- package/components/u-input/u-input.vue +288 -288
- package/components/u-keyboard/types.ts +40 -40
- package/components/u-keyboard/u-keyboard.vue +178 -178
- package/components/u-lazy-load/types.ts +37 -37
- package/components/u-lazy-load/u-lazy-load.vue +246 -246
- package/components/u-line/types.ts +44 -44
- package/components/u-line/u-line.vue +68 -68
- package/components/u-line-progress/types.ts +58 -58
- package/components/u-line-progress/u-line-progress.vue +126 -126
- package/components/u-link/types.ts +43 -43
- package/components/u-link/u-link.vue +84 -84
- package/components/u-loading/types.ts +35 -35
- package/components/u-loading/u-loading.vue +105 -105
- package/components/u-loading-popup/types.ts +26 -26
- package/components/u-loading-popup/u-loading-popup.vue +253 -253
- package/components/u-loadmore/types.ts +79 -79
- package/components/u-loadmore/u-loadmore.vue +156 -156
- package/components/u-mask/types.ts +40 -40
- package/components/u-mask/u-mask.vue +113 -113
- package/components/u-message-input/types.ts +74 -74
- package/components/u-message-input/u-message-input.vue +281 -281
- package/components/u-modal/types.ts +118 -118
- package/components/u-modal/u-modal.vue +220 -220
- package/components/u-navbar/types.ts +103 -103
- package/components/u-navbar/u-navbar.vue +251 -251
- package/components/u-no-network/image.ts +2 -2
- package/components/u-no-network/types.ts +28 -28
- package/components/u-no-network/u-no-network.vue +303 -303
- package/components/u-notice-bar/types.ts +111 -111
- package/components/u-notice-bar/u-notice-bar.vue +189 -189
- package/components/u-number-box/types.ts +42 -42
- package/components/u-number-box/u-number-box.vue +321 -321
- package/components/u-number-keyboard/types.ts +26 -26
- package/components/u-number-keyboard/u-number-keyboard.vue +188 -188
- package/components/u-picker/types.ts +123 -123
- package/components/u-picker/u-picker.vue +685 -685
- package/components/u-popup/types.ts +59 -59
- package/components/u-popup/u-popup.vue +385 -385
- package/components/u-radio/types.ts +27 -27
- package/components/u-radio/u-radio.vue +279 -279
- package/components/u-radio-group/types.ts +31 -31
- package/components/u-radio-group/u-radio-group.vue +96 -96
- package/components/u-rate/types.ts +42 -42
- package/components/u-rate/u-rate.vue +249 -249
- package/components/u-read-more/types.ts +37 -37
- package/components/u-read-more/u-read-more.vue +172 -172
- package/components/u-root-portal/u-root-portal.vue +56 -56
- package/components/u-row/types.ts +22 -22
- package/components/u-row/u-row.vue +105 -105
- package/components/u-row-notice/types.ts +41 -41
- package/components/u-row-notice/u-row-notice.vue +256 -256
- package/components/u-safe-bottom/u-safe-bottom.vue +57 -57
- package/components/u-search/types.ts +55 -55
- package/components/u-search/u-search.vue +279 -279
- package/components/u-section/types.ts +34 -34
- package/components/u-section/u-section.vue +150 -150
- package/components/u-select/types.ts +45 -45
- package/components/u-select/u-select.vue +388 -388
- package/components/u-skeleton/types.ts +22 -22
- package/components/u-skeleton/u-skeleton.vue +231 -231
- package/components/u-slider/types.ts +34 -34
- package/components/u-slider/u-slider.vue +255 -255
- package/components/u-status-bar/u-status-bar.vue +74 -74
- package/components/u-steps/types.ts +30 -30
- package/components/u-steps/u-steps.vue +181 -181
- package/components/u-sticky/types.ts +24 -24
- package/components/u-sticky/u-sticky.vue +178 -178
- package/components/u-subsection/types.ts +38 -38
- package/components/u-subsection/u-subsection.vue +339 -339
- package/components/u-swipe-action/types.ts +52 -52
- package/components/u-swipe-action/u-swipe-action.vue +276 -276
- package/components/u-swiper/types.ts +49 -49
- package/components/u-swiper/u-swiper.vue +308 -308
- package/components/u-switch/types.ts +30 -30
- package/components/u-switch/u-switch.vue +150 -150
- package/components/u-tabbar/types.ts +38 -38
- package/components/u-tabbar/u-tabbar.vue +315 -315
- package/components/u-table/types.ts +27 -27
- package/components/u-table/u-table.vue +67 -67
- package/components/u-tabs/types.ts +53 -53
- package/components/u-tabs/u-tabs.vue +302 -302
- package/components/u-tabs-swiper/types.ts +55 -55
- package/components/u-tabs-swiper/u-tabs-swiper.vue +409 -409
- package/components/u-tag/types.ts +39 -39
- package/components/u-tag/u-tag.vue +268 -268
- package/components/u-td/types.ts +14 -14
- package/components/u-td/u-td.vue +98 -98
- package/components/u-text/types.ts +72 -72
- package/components/u-text/u-text.vue +343 -343
- package/components/u-th/types.ts +14 -14
- package/components/u-th/u-th.vue +92 -92
- package/components/u-time-line/u-time-line.vue +53 -53
- package/components/u-time-line-item/types.ts +16 -16
- package/components/u-time-line-item/u-time-line-item.vue +90 -90
- package/components/u-toast/types.ts +38 -38
- package/components/u-toast/u-toast.vue +240 -240
- package/components/u-top-tips/types.ts +16 -16
- package/components/u-top-tips/u-top-tips.vue +130 -130
- package/components/u-tr/types.ts +11 -11
- package/components/u-tr/u-tr.vue +39 -39
- package/components/u-upload/types.ts +82 -82
- package/components/u-upload/u-upload.vue +568 -568
- package/components/u-verification-code/types.ts +24 -24
- package/components/u-verification-code/u-verification-code.vue +176 -176
- package/components/u-waterfall/types.ts +18 -18
- package/components/u-waterfall/u-waterfall.vue +187 -187
- package/iconfont.css +913 -913
- package/index.scss +25 -25
- package/index.ts +38 -38
- package/libs/config/config.ts +26 -26
- package/libs/config/zIndex.ts +37 -37
- package/libs/css/color.scss +155 -155
- package/libs/css/common.scss +178 -178
- package/libs/css/style.components.scss +16 -16
- package/libs/css/style.h5.scss +8 -8
- package/libs/css/style.mp.scss +72 -72
- package/libs/css/style.nvue.scss +15 -15
- package/libs/css/style.vue.scss +188 -188
- package/libs/function/$parent.ts +24 -24
- package/libs/function/addUnit.ts +13 -13
- package/libs/function/color.ts +37 -37
- package/libs/function/colorGradient.ts +139 -139
- package/libs/function/debounce.ts +28 -28
- package/libs/function/deepClone.ts +39 -39
- package/libs/function/deepMerge.ts +35 -35
- package/libs/function/getParent.ts +63 -63
- package/libs/function/getRect.ts +26 -26
- package/libs/function/guid.ts +42 -42
- package/libs/function/md5.ts +403 -403
- package/libs/function/parent.ts +21 -21
- package/libs/function/queryParams.ts +64 -64
- package/libs/function/random.ts +16 -16
- package/libs/function/randomArray.ts +11 -11
- package/libs/function/route.ts +118 -118
- package/libs/function/styleUtils.ts +83 -83
- package/libs/function/sys.ts +15 -15
- package/libs/function/test.ts +289 -289
- package/libs/function/throttle.ts +31 -31
- package/libs/function/timeFormat.ts +55 -55
- package/libs/function/timeFrom.ts +48 -48
- package/libs/function/toast.ts +14 -14
- package/libs/function/trim.ts +21 -21
- package/libs/function/type2icon.ts +39 -39
- package/libs/hooks/index.ts +4 -4
- package/libs/hooks/useCompRelation.ts +364 -364
- package/libs/hooks/useComponent.ts +759 -759
- package/libs/hooks/useEmitter.ts +79 -79
- package/libs/hooks/useParent.ts +33 -33
- package/libs/hooks/useRect.ts +33 -33
- package/libs/index.ts +337 -337
- package/libs/request/auto-http.ts +76 -76
- package/libs/request/index.ts +242 -242
- package/libs/store/index.ts +88 -88
- package/libs/util/async-validator.d.ts +75 -75
- package/libs/util/async-validator.js +1 -1
- package/libs/util/calendar.d.ts +57 -57
- package/libs/util/emitter.ts +112 -112
- package/libs/util/eventBus.ts +86 -86
- package/libs/util/logger.ts +364 -364
- package/libs/util/mitt.ts +118 -118
- package/libs/util/parent.ts +20 -20
- package/package.json +4 -4
- package/readme.md +241 -241
- package/theme.scss +38 -38
- package/types/components.d.ts +97 -97
- package/types/global.d.ts +331 -331
- package/types/ignore-errors.d.ts +30 -30
- package/types/index.d.ts +19 -19
- package/types/uni-app.d.ts +63 -63
|
@@ -1,759 +1,759 @@
|
|
|
1
|
-
// utils/useComponent.ts
|
|
2
|
-
import { ref, reactive, getCurrentInstance, onUnmounted, nextTick, computed } from 'vue';
|
|
3
|
-
import { logger } from '../util/logger';
|
|
4
|
-
import { mitt } from '../util/mitt';
|
|
5
|
-
|
|
6
|
-
// 简化类型定义
|
|
7
|
-
interface ParentContext {
|
|
8
|
-
name: string;
|
|
9
|
-
uid: string; // 添加唯一ID
|
|
10
|
-
addChild: (child: ChildContext) => void;
|
|
11
|
-
removeChild: (childId: string) => void;
|
|
12
|
-
broadcast: (event: string, data?: any) => void;
|
|
13
|
-
getChildren: () => ChildContext[];
|
|
14
|
-
getExposed: () => Record<string, any>;
|
|
15
|
-
getChildExposed: (childId: string) => Record<string, any>;
|
|
16
|
-
getChildrenExposed: () => Array<{ id: string; name: string; exposed: Record<string, any> }>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface ChildContext {
|
|
20
|
-
id: string;
|
|
21
|
-
uid: string; // 添加唯一ID
|
|
22
|
-
name: string;
|
|
23
|
-
emitToParent: (event: string, data?: any) => void;
|
|
24
|
-
getParentExposed: () => Record<string, any>;
|
|
25
|
-
getInstance: () => any;
|
|
26
|
-
getExposed: () => Record<string, any>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 全局存储 - 改为页面级别存储
|
|
30
|
-
const pageComponentMaps = new Map<
|
|
31
|
-
string,
|
|
32
|
-
{
|
|
33
|
-
parentMap: Map<string, ParentContext>;
|
|
34
|
-
childMap: Map<string, ChildContext>;
|
|
35
|
-
}
|
|
36
|
-
>();
|
|
37
|
-
|
|
38
|
-
// 获取当前页面的组件映射
|
|
39
|
-
function getCurrentPageMaps() {
|
|
40
|
-
// 在uniapp中,可以通过getCurrentPages()获取当前页面路径
|
|
41
|
-
const pages = getCurrentPages();
|
|
42
|
-
const currentPage = pages[pages.length - 1];
|
|
43
|
-
const pagePath = currentPage?.route || 'default';
|
|
44
|
-
|
|
45
|
-
if (!pageComponentMaps.has(pagePath)) {
|
|
46
|
-
pageComponentMaps.set(pagePath, {
|
|
47
|
-
parentMap: new Map<string, ParentContext>(),
|
|
48
|
-
childMap: new Map<string, ChildContext>()
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return pageComponentMaps.get(pagePath)!;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 清理指定页面的组件关系
|
|
56
|
-
function cleanupPageComponentRelations(pagePath: string) {
|
|
57
|
-
if (pageComponentMaps.has(pagePath)) {
|
|
58
|
-
const pageMaps = pageComponentMaps.get(pagePath)!;
|
|
59
|
-
pageMaps.parentMap.clear();
|
|
60
|
-
pageMaps.childMap.clear();
|
|
61
|
-
pageComponentMaps.delete(pagePath);
|
|
62
|
-
logger.log(`Cleaned up component relations for page: ${pagePath}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 事件常量
|
|
67
|
-
const PARENT_REGISTERED_EVENT = 'parent:registered';
|
|
68
|
-
const PARENT_UNMOUNTED_EVENT = 'parent:unmounted';
|
|
69
|
-
const CHILD_REGISTERED_EVENT = 'child:registered';
|
|
70
|
-
|
|
71
|
-
// 创建事件总线实例
|
|
72
|
-
type EventBusEvents = {
|
|
73
|
-
[PARENT_REGISTERED_EVENT]: { name: string; parent: ParentContext; pagePath: string };
|
|
74
|
-
[PARENT_UNMOUNTED_EVENT]: { name: string; pagePath: string };
|
|
75
|
-
[CHILD_REGISTERED_EVENT]: { id: string; name: string; parentName: string; pagePath: string };
|
|
76
|
-
[key: `parent:${string}:${string}`]: { data?: any; childId: string; childName: string; pagePath: string };
|
|
77
|
-
[key: `child:${string}:${string}`]: any;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const eventBus = mitt<EventBusEvents>();
|
|
81
|
-
|
|
82
|
-
// 热更新重新注册管理器
|
|
83
|
-
let isHotReloading = false;
|
|
84
|
-
const hotReloadReconnectCallbacks: Map<string, Function[]> = new Map();
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 注册热更新重新连接回调
|
|
88
|
-
*/
|
|
89
|
-
function registerHotReloadReconnect(key: string, callback: Function): void {
|
|
90
|
-
if (!hotReloadReconnectCallbacks.has(key)) {
|
|
91
|
-
hotReloadReconnectCallbacks.set(key, []);
|
|
92
|
-
}
|
|
93
|
-
hotReloadReconnectCallbacks.get(key)!.push(callback);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 注销热更新重新连接回调
|
|
98
|
-
*/
|
|
99
|
-
function unregisterHotReloadReconnect(key: string, callback: Function): void {
|
|
100
|
-
const callbacks = hotReloadReconnectCallbacks.get(key);
|
|
101
|
-
if (callbacks) {
|
|
102
|
-
const index = callbacks.indexOf(callback);
|
|
103
|
-
if (index > -1) {
|
|
104
|
-
callbacks.splice(index, 1);
|
|
105
|
-
}
|
|
106
|
-
if (callbacks.length === 0) {
|
|
107
|
-
hotReloadReconnectCallbacks.delete(key);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 执行热更新重新连接
|
|
114
|
-
*/
|
|
115
|
-
function executeHotReloadReconnect(): void {
|
|
116
|
-
logger.log('Executing hot reload reconnection for all registered callbacks');
|
|
117
|
-
hotReloadReconnectCallbacks.forEach((callbacks, key) => {
|
|
118
|
-
callbacks.forEach(callback => {
|
|
119
|
-
try {
|
|
120
|
-
callback();
|
|
121
|
-
logger.log(`Successfully reconnected: ${key}`);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
logger.warn(`Failed to reconnect ${key}:`, error);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 热更新清理函数
|
|
130
|
-
export function cleanupComponentRelations(): void {
|
|
131
|
-
logger.log('Cleaning up component relations for hot reload');
|
|
132
|
-
pageComponentMaps.clear();
|
|
133
|
-
eventBus.clear();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 热更新处理
|
|
137
|
-
if (import.meta.hot) {
|
|
138
|
-
import.meta.hot.accept(() => {
|
|
139
|
-
isHotReloading = true;
|
|
140
|
-
logger.log('Hot reload detected, starting reconnection process');
|
|
141
|
-
|
|
142
|
-
// 第一步:清理旧的组件关系
|
|
143
|
-
setTimeout(() => {
|
|
144
|
-
cleanupComponentRelations();
|
|
145
|
-
|
|
146
|
-
// 第二步:执行重新连接
|
|
147
|
-
setTimeout(() => {
|
|
148
|
-
executeHotReloadReconnect();
|
|
149
|
-
isHotReloading = false;
|
|
150
|
-
logger.log('Hot reload reconnection completed');
|
|
151
|
-
}, 100); // 增加延迟确保组件已重新创建
|
|
152
|
-
}, 50);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 生成实例唯一ID
|
|
158
|
-
*/
|
|
159
|
-
function generateInstanceId(componentName: string): string {
|
|
160
|
-
return `${componentName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 生成组件唯一UID
|
|
165
|
-
*/
|
|
166
|
-
function generateComponentUid(): string {
|
|
167
|
-
return `uid_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* 获取当前页面路径
|
|
172
|
-
*/
|
|
173
|
-
function getCurrentPagePath(): string {
|
|
174
|
-
// 在uniapp中获取当前页面路径
|
|
175
|
-
try {
|
|
176
|
-
const pages = getCurrentPages();
|
|
177
|
-
const currentPage = pages[pages.length - 1];
|
|
178
|
-
return currentPage?.route || 'default';
|
|
179
|
-
} catch (error) {
|
|
180
|
-
return 'default';
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* 父组件 Hook
|
|
186
|
-
*/
|
|
187
|
-
export function useParent(componentName: string) {
|
|
188
|
-
const instance = getCurrentInstance();
|
|
189
|
-
if (!instance) {
|
|
190
|
-
throw new Error('useParent must be called within setup function');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!componentName) {
|
|
194
|
-
throw new Error('Component name is required for useParent');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const pagePath = getCurrentPagePath();
|
|
198
|
-
const pageMaps = getCurrentPageMaps();
|
|
199
|
-
|
|
200
|
-
// 生成父组件唯一UID
|
|
201
|
-
const parentUid = generateComponentUid();
|
|
202
|
-
|
|
203
|
-
// 使用组合名称:组件名 + UID,确保唯一性
|
|
204
|
-
const uniqueParentName = `${componentName}-${parentUid}`;
|
|
205
|
-
|
|
206
|
-
// 热更新时清理旧的父组件
|
|
207
|
-
if (pageMaps.parentMap.has(uniqueParentName)) {
|
|
208
|
-
logger.log(`Cleaning up existing parent ${uniqueParentName} for hot reload on page ${pagePath}`);
|
|
209
|
-
pageMaps.parentMap.delete(uniqueParentName);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const children = reactive<ChildContext[]>([]);
|
|
213
|
-
|
|
214
|
-
// 父组件上下文
|
|
215
|
-
const parentContext: ParentContext = {
|
|
216
|
-
name: uniqueParentName, // 使用唯一名称
|
|
217
|
-
uid: parentUid, // 添加唯一ID
|
|
218
|
-
|
|
219
|
-
addChild(child: ChildContext) {
|
|
220
|
-
if (!children.find(c => c.id === child.id)) {
|
|
221
|
-
children.push(child);
|
|
222
|
-
logger.log(`Parent ${uniqueParentName} on page ${pagePath} added child: ${child.name}`);
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
removeChild(childId: string) {
|
|
227
|
-
const index = children.findIndex(c => c.id === childId);
|
|
228
|
-
if (index > -1) {
|
|
229
|
-
children.splice(index, 1);
|
|
230
|
-
logger.log(`Parent ${uniqueParentName} on page ${pagePath} removed child: ${childId}`);
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
broadcast(event: string, data?: any) {
|
|
235
|
-
logger.log(`Parent ${uniqueParentName} on page ${pagePath} broadcasting event: ${event}`);
|
|
236
|
-
children.forEach(child => {
|
|
237
|
-
eventBus.emit(`child:${child.id}:${event}`, data);
|
|
238
|
-
});
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
getChildren() {
|
|
242
|
-
return [...children];
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
getExposed() {
|
|
246
|
-
return instance.exposed || {};
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
getChildExposed(childId: string) {
|
|
250
|
-
const child = children.find(c => c.id === childId);
|
|
251
|
-
if (child && child.getExposed) {
|
|
252
|
-
return child.getExposed();
|
|
253
|
-
}
|
|
254
|
-
logger.warn(`Child ${childId} not found or does not have getExposed method on page ${pagePath}`);
|
|
255
|
-
return {};
|
|
256
|
-
},
|
|
257
|
-
|
|
258
|
-
getChildrenExposed() {
|
|
259
|
-
return children
|
|
260
|
-
.filter(child => child.getExposed)
|
|
261
|
-
.map(child => {
|
|
262
|
-
const exposed = child.getExposed();
|
|
263
|
-
return {
|
|
264
|
-
id: child.id,
|
|
265
|
-
name: child.name,
|
|
266
|
-
exposed: exposed
|
|
267
|
-
};
|
|
268
|
-
})
|
|
269
|
-
.filter(item => Object.keys(item.exposed).length > 0);
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
// 注册父组件并广播事件
|
|
274
|
-
pageMaps.parentMap.set(uniqueParentName, parentContext);
|
|
275
|
-
logger.log(`Parent ${uniqueParentName} registered on page ${pagePath}`);
|
|
276
|
-
|
|
277
|
-
// 广播父组件注册事件(包含页面路径信息)
|
|
278
|
-
eventBus.emit(PARENT_REGISTERED_EVENT, {
|
|
279
|
-
name: uniqueParentName,
|
|
280
|
-
parent: parentContext,
|
|
281
|
-
pagePath
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// 组件卸载时清理
|
|
285
|
-
onUnmounted(() => {
|
|
286
|
-
pageMaps.parentMap.delete(uniqueParentName);
|
|
287
|
-
eventBus.emit(PARENT_UNMOUNTED_EVENT, {
|
|
288
|
-
name: uniqueParentName,
|
|
289
|
-
pagePath
|
|
290
|
-
});
|
|
291
|
-
logger.log(`Parent ${uniqueParentName} unmounted from page ${pagePath}`);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
parentName: uniqueParentName, // 返回唯一名称
|
|
296
|
-
parentUid, // 返回唯一ID
|
|
297
|
-
children,
|
|
298
|
-
broadcast: parentContext.broadcast,
|
|
299
|
-
getChildren: parentContext.getChildren,
|
|
300
|
-
getChildExposed: parentContext.getChildExposed,
|
|
301
|
-
getChildrenExposed: parentContext.getChildrenExposed,
|
|
302
|
-
pagePath
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 子组件 Hook
|
|
308
|
-
*/
|
|
309
|
-
export function useChildren(componentName: string, parentName: string) {
|
|
310
|
-
const instance = getCurrentInstance();
|
|
311
|
-
if (!instance) {
|
|
312
|
-
throw new Error('useChildren must be called within setup function');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (!componentName || !parentName) {
|
|
316
|
-
throw new Error('Component name and parent name are required for useChildren');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const pagePath = getCurrentPagePath();
|
|
320
|
-
const pageMaps = getCurrentPageMaps();
|
|
321
|
-
const instanceId = generateInstanceId(componentName);
|
|
322
|
-
const childUid = generateComponentUid(); // 生成子组件唯一UID
|
|
323
|
-
const parentRef = ref<ParentContext | null>(null);
|
|
324
|
-
const parentExposed = ref<Record<string, any>>({});
|
|
325
|
-
|
|
326
|
-
// 热更新时清理旧的子组件
|
|
327
|
-
if (pageMaps.childMap.has(instanceId)) {
|
|
328
|
-
logger.log(`Cleaning up existing child ${componentName} for hot reload on page ${pagePath}`);
|
|
329
|
-
pageMaps.childMap.delete(instanceId);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// 获取父组件暴露内容
|
|
333
|
-
const getParentExposed = (): Record<string, any> => {
|
|
334
|
-
if (parentRef.value) {
|
|
335
|
-
const exposed = parentRef.value.getExposed();
|
|
336
|
-
parentExposed.value = exposed;
|
|
337
|
-
return exposed;
|
|
338
|
-
}
|
|
339
|
-
return {};
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// 获取子组件exposed内容
|
|
343
|
-
const getExposed = (): Record<string, any> => {
|
|
344
|
-
return instance.exposed || {};
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
// 链接到父组件(只在当前页面查找)
|
|
348
|
-
const linkParent = (): boolean => {
|
|
349
|
-
// 在当前页面中查找匹配的父组件
|
|
350
|
-
let parent: ParentContext | undefined;
|
|
351
|
-
|
|
352
|
-
// 首先尝试精确匹配
|
|
353
|
-
parent = pageMaps.parentMap.get(parentName);
|
|
354
|
-
|
|
355
|
-
// 如果精确匹配失败,尝试前缀匹配(支持向后兼容)
|
|
356
|
-
if (!parent) {
|
|
357
|
-
for (const [key, value] of pageMaps.parentMap.entries()) {
|
|
358
|
-
if (key.startsWith(parentName + '-')) {
|
|
359
|
-
parent = value;
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (parent) {
|
|
366
|
-
parentRef.value = parent;
|
|
367
|
-
parent.addChild(childContext);
|
|
368
|
-
getParentExposed();
|
|
369
|
-
logger.log(`Child ${componentName} linked to parent ${parent.name} on page ${pagePath}`);
|
|
370
|
-
return true;
|
|
371
|
-
}
|
|
372
|
-
return false;
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
// 子组件上下文
|
|
376
|
-
const childContext: ChildContext = {
|
|
377
|
-
id: instanceId,
|
|
378
|
-
uid: childUid, // 添加唯一ID
|
|
379
|
-
name: componentName,
|
|
380
|
-
|
|
381
|
-
emitToParent(event: string, data?: any) {
|
|
382
|
-
if (parentRef.value) {
|
|
383
|
-
eventBus.emit(`parent:${parentRef.value.name}:${event}`, {
|
|
384
|
-
data,
|
|
385
|
-
childId: instanceId,
|
|
386
|
-
childName: componentName,
|
|
387
|
-
pagePath
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
},
|
|
391
|
-
|
|
392
|
-
getParentExposed,
|
|
393
|
-
getInstance() {
|
|
394
|
-
return instance;
|
|
395
|
-
},
|
|
396
|
-
getExposed
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// 注册子组件
|
|
400
|
-
pageMaps.childMap.set(instanceId, childContext);
|
|
401
|
-
logger.log(`Child ${componentName} registered on page ${pagePath}`);
|
|
402
|
-
|
|
403
|
-
// 广播子组件注册事件
|
|
404
|
-
eventBus.emit(CHILD_REGISTERED_EVENT, {
|
|
405
|
-
id: instanceId,
|
|
406
|
-
name: componentName,
|
|
407
|
-
parentName: parentName,
|
|
408
|
-
pagePath
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// 立即尝试连接父组件
|
|
412
|
-
let connected = linkParent();
|
|
413
|
-
|
|
414
|
-
// 如果没连接上,监听父组件注册事件(只监听当前页面的父组件)
|
|
415
|
-
if (!connected) {
|
|
416
|
-
const parentRegisteredHandler = (eventData: { name: string; parent: ParentContext; pagePath: string }) => {
|
|
417
|
-
// 检查是否是我们要连接的父组件(精确匹配或前缀匹配)
|
|
418
|
-
if (
|
|
419
|
-
(eventData.name === parentName || eventData.name.startsWith(parentName + '-')) &&
|
|
420
|
-
eventData.pagePath === pagePath
|
|
421
|
-
) {
|
|
422
|
-
connected = linkParent();
|
|
423
|
-
if (connected) {
|
|
424
|
-
eventBus.off(PARENT_REGISTERED_EVENT, parentRegisteredHandler);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
eventBus.on(PARENT_REGISTERED_EVENT, parentRegisteredHandler);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// 监听父组件卸载事件(只监听当前页面的父组件)
|
|
432
|
-
const parentUnmountedHandler = (eventData: { name: string; pagePath: string }) => {
|
|
433
|
-
if (
|
|
434
|
-
(eventData.name === parentName || eventData.name.startsWith(parentName + '-')) &&
|
|
435
|
-
eventData.pagePath === pagePath &&
|
|
436
|
-
parentRef.value
|
|
437
|
-
) {
|
|
438
|
-
parentRef.value = null;
|
|
439
|
-
parentExposed.value = {};
|
|
440
|
-
logger.log(`Parent ${parentName} unmounted from page ${pagePath}, child ${componentName} disconnected`);
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
eventBus.on(PARENT_UNMOUNTED_EVENT, parentUnmountedHandler);
|
|
444
|
-
|
|
445
|
-
// 组件卸载时清理
|
|
446
|
-
onUnmounted(() => {
|
|
447
|
-
if (parentRef.value) {
|
|
448
|
-
parentRef.value.removeChild(instanceId);
|
|
449
|
-
}
|
|
450
|
-
pageMaps.childMap.delete(instanceId);
|
|
451
|
-
eventBus.off(PARENT_REGISTERED_EVENT);
|
|
452
|
-
eventBus.off(PARENT_UNMOUNTED_EVENT, parentUnmountedHandler);
|
|
453
|
-
logger.log(`Child ${componentName} unmounted from page ${pagePath}`);
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
return {
|
|
457
|
-
childId: instanceId,
|
|
458
|
-
childUid, // 返回唯一ID
|
|
459
|
-
childName: componentName,
|
|
460
|
-
parent: parentRef,
|
|
461
|
-
emitToParent: childContext.emitToParent,
|
|
462
|
-
getParentExposed,
|
|
463
|
-
parentExposed: computed(() => parentExposed.value),
|
|
464
|
-
getExposed: childContext.getExposed,
|
|
465
|
-
pagePath
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* 监听子组件事件(返回取消监听函数)
|
|
471
|
-
*/
|
|
472
|
-
export function onChildEvent(
|
|
473
|
-
parentName: string,
|
|
474
|
-
event: string,
|
|
475
|
-
handler: (data?: any, childId?: string, childName?: string) => void
|
|
476
|
-
): () => void {
|
|
477
|
-
const pagePath = getCurrentPagePath();
|
|
478
|
-
|
|
479
|
-
const eventHandler = (eventData: { data?: any; childId: string; childName: string; pagePath: string }) => {
|
|
480
|
-
// 只处理当前页面的事件
|
|
481
|
-
if (eventData.pagePath === pagePath) {
|
|
482
|
-
handler(eventData.data, eventData.childId, eventData.childName);
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
eventBus.on(`parent:${parentName}:${event}`, eventHandler);
|
|
487
|
-
|
|
488
|
-
// 返回取消监听函数
|
|
489
|
-
return () => {
|
|
490
|
-
eventBus.off(`parent:${parentName}:${event}`, eventHandler);
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* 监听父组件事件(返回取消监听函数)
|
|
496
|
-
*/
|
|
497
|
-
export function onParentEvent(childId: string, event: string, handler: (data?: any) => void): () => void {
|
|
498
|
-
eventBus.on(`child:${childId}:${event}`, handler);
|
|
499
|
-
|
|
500
|
-
// 返回取消监听函数
|
|
501
|
-
return () => {
|
|
502
|
-
eventBus.off(`child:${childId}:${event}`, handler);
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* 自动取消监听的事件注册 - 单个事件
|
|
508
|
-
*/
|
|
509
|
-
export function useParentEvent(
|
|
510
|
-
childId: string,
|
|
511
|
-
event: string,
|
|
512
|
-
handler: (data?: any) => void,
|
|
513
|
-
autoClean = true,
|
|
514
|
-
hotReloadReconnect = true
|
|
515
|
-
): () => void {
|
|
516
|
-
const instance = getCurrentInstance();
|
|
517
|
-
const unsubscribe = onParentEvent(childId, event, handler);
|
|
518
|
-
|
|
519
|
-
// 热更新重新注册支持
|
|
520
|
-
if (hotReloadReconnect && instance) {
|
|
521
|
-
const reconnectKey = `parent-event-${childId}-${event}`;
|
|
522
|
-
const reconnectCallback = () => {
|
|
523
|
-
logger.log(`Reconnecting parent event: ${event} for child: ${childId}`);
|
|
524
|
-
onParentEvent(childId, event, handler);
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
528
|
-
|
|
529
|
-
// 组件卸载时清理重新注册回调
|
|
530
|
-
onUnmounted(() => {
|
|
531
|
-
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// 自动在组件卸载时清理
|
|
536
|
-
if (autoClean && instance) {
|
|
537
|
-
onUnmounted(unsubscribe);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
return unsubscribe;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* 自动取消监听的事件注册 - 批量事件
|
|
545
|
-
*/
|
|
546
|
-
export function useParentEvents(
|
|
547
|
-
childId: string,
|
|
548
|
-
events: Record<string, (data?: any) => void>,
|
|
549
|
-
autoClean = true,
|
|
550
|
-
hotReloadReconnect = true
|
|
551
|
-
): () => void {
|
|
552
|
-
const instance = getCurrentInstance();
|
|
553
|
-
const cleanups: Function[] = [];
|
|
554
|
-
|
|
555
|
-
Object.entries(events).forEach(([event, handler]) => {
|
|
556
|
-
const unsubscribe = onParentEvent(childId, event, handler);
|
|
557
|
-
cleanups.push(unsubscribe);
|
|
558
|
-
|
|
559
|
-
// 热更新重新注册支持
|
|
560
|
-
if (hotReloadReconnect && instance) {
|
|
561
|
-
const reconnectKey = `parent-events-${childId}-${event}`;
|
|
562
|
-
const reconnectCallback = () => {
|
|
563
|
-
logger.log(`Reconnecting parent event: ${event} for child: ${childId}`);
|
|
564
|
-
onParentEvent(childId, event, handler);
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
568
|
-
|
|
569
|
-
// 组件卸载时清理重新注册回调
|
|
570
|
-
onUnmounted(() => {
|
|
571
|
-
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
const cleanupAll = () => {
|
|
577
|
-
cleanups.forEach(cleanup => cleanup());
|
|
578
|
-
cleanups.length = 0;
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
if (autoClean && instance) {
|
|
582
|
-
onUnmounted(cleanupAll);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
return cleanupAll;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* 自动取消监听的子组件事件注册 - 单个事件
|
|
590
|
-
*/
|
|
591
|
-
export function useChildEvent(
|
|
592
|
-
parentName: string,
|
|
593
|
-
event: string,
|
|
594
|
-
handler: (data?: any, childId?: string, childName?: string) => void,
|
|
595
|
-
autoClean = true,
|
|
596
|
-
hotReloadReconnect = true
|
|
597
|
-
): () => void {
|
|
598
|
-
const instance = getCurrentInstance();
|
|
599
|
-
const unsubscribe = onChildEvent(parentName, event, handler);
|
|
600
|
-
|
|
601
|
-
// 热更新重新注册支持
|
|
602
|
-
if (hotReloadReconnect && instance) {
|
|
603
|
-
const reconnectKey = `child-event-${parentName}-${event}`;
|
|
604
|
-
const reconnectCallback = () => {
|
|
605
|
-
logger.log(`Reconnecting child event: ${event} for parent: ${parentName}`);
|
|
606
|
-
onChildEvent(parentName, event, handler);
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
610
|
-
|
|
611
|
-
// 组件卸载时清理重新注册回调
|
|
612
|
-
onUnmounted(() => {
|
|
613
|
-
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// 自动在组件卸载时清理
|
|
618
|
-
if (autoClean && instance) {
|
|
619
|
-
onUnmounted(unsubscribe);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return unsubscribe;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* 自动取消监听的子组件事件注册 - 批量事件
|
|
627
|
-
*/
|
|
628
|
-
export function useChildEvents(
|
|
629
|
-
parentName: string,
|
|
630
|
-
events: Record<string, (data?: any, childId?: string, childName?: string) => void>,
|
|
631
|
-
autoClean = true,
|
|
632
|
-
hotReloadReconnect = true
|
|
633
|
-
): () => void {
|
|
634
|
-
const instance = getCurrentInstance();
|
|
635
|
-
const cleanups: Function[] = [];
|
|
636
|
-
|
|
637
|
-
Object.entries(events).forEach(([event, handler]) => {
|
|
638
|
-
const unsubscribe = onChildEvent(parentName, event, handler);
|
|
639
|
-
cleanups.push(unsubscribe);
|
|
640
|
-
|
|
641
|
-
// 热更新重新注册支持
|
|
642
|
-
if (hotReloadReconnect && instance) {
|
|
643
|
-
const reconnectKey = `child-events-${parentName}-${event}`;
|
|
644
|
-
const reconnectCallback = () => {
|
|
645
|
-
logger.log(`Reconnecting child event: ${event} for parent: ${parentName}`);
|
|
646
|
-
onChildEvent(parentName, event, handler);
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
650
|
-
|
|
651
|
-
// 组件卸载时清理重新注册回调
|
|
652
|
-
onUnmounted(() => {
|
|
653
|
-
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
const cleanupAll = () => {
|
|
659
|
-
cleanups.forEach(cleanup => cleanup());
|
|
660
|
-
cleanups.length = 0;
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
if (autoClean && instance) {
|
|
664
|
-
onUnmounted(cleanupAll);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return cleanupAll;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* 检查父组件是否存在(在当前页面)
|
|
672
|
-
*/
|
|
673
|
-
export function hasParent(parentName: string): boolean {
|
|
674
|
-
const pageMaps = getCurrentPageMaps();
|
|
675
|
-
|
|
676
|
-
// 精确匹配
|
|
677
|
-
if (pageMaps.parentMap.has(parentName)) {
|
|
678
|
-
return true;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// 前缀匹配(支持向后兼容)
|
|
682
|
-
for (const key of pageMaps.parentMap.keys()) {
|
|
683
|
-
if (key.startsWith(parentName + '-')) {
|
|
684
|
-
return true;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return false;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* 获取所有已注册的父组件名称(当前页面)
|
|
693
|
-
*/
|
|
694
|
-
export function getRegisteredParents(): string[] {
|
|
695
|
-
const pageMaps = getCurrentPageMaps();
|
|
696
|
-
return Array.from(pageMaps.parentMap.keys());
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* 获取父组件实例(当前页面)
|
|
701
|
-
*/
|
|
702
|
-
export function getParent(parentName: string): ParentContext | undefined {
|
|
703
|
-
const pageMaps = getCurrentPageMaps();
|
|
704
|
-
|
|
705
|
-
// 精确匹配
|
|
706
|
-
let parent = pageMaps.parentMap.get(parentName);
|
|
707
|
-
if (parent) {
|
|
708
|
-
return parent;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// 前缀匹配(支持向后兼容)
|
|
712
|
-
for (const [key, value] of pageMaps.parentMap.entries()) {
|
|
713
|
-
if (key.startsWith(parentName + '-')) {
|
|
714
|
-
return value;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return undefined;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* 获取子组件实例(当前页面)
|
|
723
|
-
*/
|
|
724
|
-
export function getChild(childId: string): ChildContext | undefined {
|
|
725
|
-
const pageMaps = getCurrentPageMaps();
|
|
726
|
-
return pageMaps.childMap.get(childId);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* 清理当前页面的组件关系(用于页面卸载时调用)
|
|
731
|
-
*/
|
|
732
|
-
export function cleanupCurrentPageComponents(): void {
|
|
733
|
-
const pagePath = getCurrentPagePath();
|
|
734
|
-
cleanupPageComponentRelations(pagePath);
|
|
735
|
-
logger.log(`Cleaned up component relations for current page: ${pagePath}`);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* 手动触发热更新重新连接(用于调试)
|
|
740
|
-
*/
|
|
741
|
-
export function manualHotReloadReconnect(): void {
|
|
742
|
-
logger.log('Manual hot reload reconnection triggered');
|
|
743
|
-
executeHotReloadReconnect();
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* 获取热更新状态
|
|
748
|
-
*/
|
|
749
|
-
export function getHotReloadStatus(): { isHotReloading: boolean; reconnectCallbacksCount: number } {
|
|
750
|
-
let totalCallbacks = 0;
|
|
751
|
-
hotReloadReconnectCallbacks.forEach(callbacks => {
|
|
752
|
-
totalCallbacks += callbacks.length;
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
return {
|
|
756
|
-
isHotReloading,
|
|
757
|
-
reconnectCallbacksCount: totalCallbacks
|
|
758
|
-
};
|
|
759
|
-
}
|
|
1
|
+
// utils/useComponent.ts
|
|
2
|
+
import { ref, reactive, getCurrentInstance, onUnmounted, nextTick, computed } from 'vue';
|
|
3
|
+
import { logger } from '../util/logger';
|
|
4
|
+
import { mitt } from '../util/mitt';
|
|
5
|
+
|
|
6
|
+
// 简化类型定义
|
|
7
|
+
interface ParentContext {
|
|
8
|
+
name: string;
|
|
9
|
+
uid: string; // 添加唯一ID
|
|
10
|
+
addChild: (child: ChildContext) => void;
|
|
11
|
+
removeChild: (childId: string) => void;
|
|
12
|
+
broadcast: (event: string, data?: any) => void;
|
|
13
|
+
getChildren: () => ChildContext[];
|
|
14
|
+
getExposed: () => Record<string, any>;
|
|
15
|
+
getChildExposed: (childId: string) => Record<string, any>;
|
|
16
|
+
getChildrenExposed: () => Array<{ id: string; name: string; exposed: Record<string, any> }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ChildContext {
|
|
20
|
+
id: string;
|
|
21
|
+
uid: string; // 添加唯一ID
|
|
22
|
+
name: string;
|
|
23
|
+
emitToParent: (event: string, data?: any) => void;
|
|
24
|
+
getParentExposed: () => Record<string, any>;
|
|
25
|
+
getInstance: () => any;
|
|
26
|
+
getExposed: () => Record<string, any>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 全局存储 - 改为页面级别存储
|
|
30
|
+
const pageComponentMaps = new Map<
|
|
31
|
+
string,
|
|
32
|
+
{
|
|
33
|
+
parentMap: Map<string, ParentContext>;
|
|
34
|
+
childMap: Map<string, ChildContext>;
|
|
35
|
+
}
|
|
36
|
+
>();
|
|
37
|
+
|
|
38
|
+
// 获取当前页面的组件映射
|
|
39
|
+
function getCurrentPageMaps() {
|
|
40
|
+
// 在uniapp中,可以通过getCurrentPages()获取当前页面路径
|
|
41
|
+
const pages = getCurrentPages();
|
|
42
|
+
const currentPage = pages[pages.length - 1];
|
|
43
|
+
const pagePath = currentPage?.route || 'default';
|
|
44
|
+
|
|
45
|
+
if (!pageComponentMaps.has(pagePath)) {
|
|
46
|
+
pageComponentMaps.set(pagePath, {
|
|
47
|
+
parentMap: new Map<string, ParentContext>(),
|
|
48
|
+
childMap: new Map<string, ChildContext>()
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return pageComponentMaps.get(pagePath)!;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 清理指定页面的组件关系
|
|
56
|
+
function cleanupPageComponentRelations(pagePath: string) {
|
|
57
|
+
if (pageComponentMaps.has(pagePath)) {
|
|
58
|
+
const pageMaps = pageComponentMaps.get(pagePath)!;
|
|
59
|
+
pageMaps.parentMap.clear();
|
|
60
|
+
pageMaps.childMap.clear();
|
|
61
|
+
pageComponentMaps.delete(pagePath);
|
|
62
|
+
logger.log(`Cleaned up component relations for page: ${pagePath}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 事件常量
|
|
67
|
+
const PARENT_REGISTERED_EVENT = 'parent:registered';
|
|
68
|
+
const PARENT_UNMOUNTED_EVENT = 'parent:unmounted';
|
|
69
|
+
const CHILD_REGISTERED_EVENT = 'child:registered';
|
|
70
|
+
|
|
71
|
+
// 创建事件总线实例
|
|
72
|
+
type EventBusEvents = {
|
|
73
|
+
[PARENT_REGISTERED_EVENT]: { name: string; parent: ParentContext; pagePath: string };
|
|
74
|
+
[PARENT_UNMOUNTED_EVENT]: { name: string; pagePath: string };
|
|
75
|
+
[CHILD_REGISTERED_EVENT]: { id: string; name: string; parentName: string; pagePath: string };
|
|
76
|
+
[key: `parent:${string}:${string}`]: { data?: any; childId: string; childName: string; pagePath: string };
|
|
77
|
+
[key: `child:${string}:${string}`]: any;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const eventBus = mitt<EventBusEvents>();
|
|
81
|
+
|
|
82
|
+
// 热更新重新注册管理器
|
|
83
|
+
let isHotReloading = false;
|
|
84
|
+
const hotReloadReconnectCallbacks: Map<string, Function[]> = new Map();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 注册热更新重新连接回调
|
|
88
|
+
*/
|
|
89
|
+
function registerHotReloadReconnect(key: string, callback: Function): void {
|
|
90
|
+
if (!hotReloadReconnectCallbacks.has(key)) {
|
|
91
|
+
hotReloadReconnectCallbacks.set(key, []);
|
|
92
|
+
}
|
|
93
|
+
hotReloadReconnectCallbacks.get(key)!.push(callback);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 注销热更新重新连接回调
|
|
98
|
+
*/
|
|
99
|
+
function unregisterHotReloadReconnect(key: string, callback: Function): void {
|
|
100
|
+
const callbacks = hotReloadReconnectCallbacks.get(key);
|
|
101
|
+
if (callbacks) {
|
|
102
|
+
const index = callbacks.indexOf(callback);
|
|
103
|
+
if (index > -1) {
|
|
104
|
+
callbacks.splice(index, 1);
|
|
105
|
+
}
|
|
106
|
+
if (callbacks.length === 0) {
|
|
107
|
+
hotReloadReconnectCallbacks.delete(key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 执行热更新重新连接
|
|
114
|
+
*/
|
|
115
|
+
function executeHotReloadReconnect(): void {
|
|
116
|
+
logger.log('Executing hot reload reconnection for all registered callbacks');
|
|
117
|
+
hotReloadReconnectCallbacks.forEach((callbacks, key) => {
|
|
118
|
+
callbacks.forEach(callback => {
|
|
119
|
+
try {
|
|
120
|
+
callback();
|
|
121
|
+
logger.log(`Successfully reconnected: ${key}`);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.warn(`Failed to reconnect ${key}:`, error);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 热更新清理函数
|
|
130
|
+
export function cleanupComponentRelations(): void {
|
|
131
|
+
logger.log('Cleaning up component relations for hot reload');
|
|
132
|
+
pageComponentMaps.clear();
|
|
133
|
+
eventBus.clear();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 热更新处理
|
|
137
|
+
if (import.meta.hot) {
|
|
138
|
+
import.meta.hot.accept(() => {
|
|
139
|
+
isHotReloading = true;
|
|
140
|
+
logger.log('Hot reload detected, starting reconnection process');
|
|
141
|
+
|
|
142
|
+
// 第一步:清理旧的组件关系
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
cleanupComponentRelations();
|
|
145
|
+
|
|
146
|
+
// 第二步:执行重新连接
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
executeHotReloadReconnect();
|
|
149
|
+
isHotReloading = false;
|
|
150
|
+
logger.log('Hot reload reconnection completed');
|
|
151
|
+
}, 100); // 增加延迟确保组件已重新创建
|
|
152
|
+
}, 50);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 生成实例唯一ID
|
|
158
|
+
*/
|
|
159
|
+
function generateInstanceId(componentName: string): string {
|
|
160
|
+
return `${componentName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 生成组件唯一UID
|
|
165
|
+
*/
|
|
166
|
+
function generateComponentUid(): string {
|
|
167
|
+
return `uid_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 获取当前页面路径
|
|
172
|
+
*/
|
|
173
|
+
function getCurrentPagePath(): string {
|
|
174
|
+
// 在uniapp中获取当前页面路径
|
|
175
|
+
try {
|
|
176
|
+
const pages = getCurrentPages();
|
|
177
|
+
const currentPage = pages[pages.length - 1];
|
|
178
|
+
return currentPage?.route || 'default';
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return 'default';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 父组件 Hook
|
|
186
|
+
*/
|
|
187
|
+
export function useParent(componentName: string) {
|
|
188
|
+
const instance = getCurrentInstance();
|
|
189
|
+
if (!instance) {
|
|
190
|
+
throw new Error('useParent must be called within setup function');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!componentName) {
|
|
194
|
+
throw new Error('Component name is required for useParent');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const pagePath = getCurrentPagePath();
|
|
198
|
+
const pageMaps = getCurrentPageMaps();
|
|
199
|
+
|
|
200
|
+
// 生成父组件唯一UID
|
|
201
|
+
const parentUid = generateComponentUid();
|
|
202
|
+
|
|
203
|
+
// 使用组合名称:组件名 + UID,确保唯一性
|
|
204
|
+
const uniqueParentName = `${componentName}-${parentUid}`;
|
|
205
|
+
|
|
206
|
+
// 热更新时清理旧的父组件
|
|
207
|
+
if (pageMaps.parentMap.has(uniqueParentName)) {
|
|
208
|
+
logger.log(`Cleaning up existing parent ${uniqueParentName} for hot reload on page ${pagePath}`);
|
|
209
|
+
pageMaps.parentMap.delete(uniqueParentName);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const children = reactive<ChildContext[]>([]);
|
|
213
|
+
|
|
214
|
+
// 父组件上下文
|
|
215
|
+
const parentContext: ParentContext = {
|
|
216
|
+
name: uniqueParentName, // 使用唯一名称
|
|
217
|
+
uid: parentUid, // 添加唯一ID
|
|
218
|
+
|
|
219
|
+
addChild(child: ChildContext) {
|
|
220
|
+
if (!children.find(c => c.id === child.id)) {
|
|
221
|
+
children.push(child);
|
|
222
|
+
logger.log(`Parent ${uniqueParentName} on page ${pagePath} added child: ${child.name}`);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
removeChild(childId: string) {
|
|
227
|
+
const index = children.findIndex(c => c.id === childId);
|
|
228
|
+
if (index > -1) {
|
|
229
|
+
children.splice(index, 1);
|
|
230
|
+
logger.log(`Parent ${uniqueParentName} on page ${pagePath} removed child: ${childId}`);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
broadcast(event: string, data?: any) {
|
|
235
|
+
logger.log(`Parent ${uniqueParentName} on page ${pagePath} broadcasting event: ${event}`);
|
|
236
|
+
children.forEach(child => {
|
|
237
|
+
eventBus.emit(`child:${child.id}:${event}`, data);
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
getChildren() {
|
|
242
|
+
return [...children];
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
getExposed() {
|
|
246
|
+
return instance.exposed || {};
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
getChildExposed(childId: string) {
|
|
250
|
+
const child = children.find(c => c.id === childId);
|
|
251
|
+
if (child && child.getExposed) {
|
|
252
|
+
return child.getExposed();
|
|
253
|
+
}
|
|
254
|
+
logger.warn(`Child ${childId} not found or does not have getExposed method on page ${pagePath}`);
|
|
255
|
+
return {};
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
getChildrenExposed() {
|
|
259
|
+
return children
|
|
260
|
+
.filter(child => child.getExposed)
|
|
261
|
+
.map(child => {
|
|
262
|
+
const exposed = child.getExposed();
|
|
263
|
+
return {
|
|
264
|
+
id: child.id,
|
|
265
|
+
name: child.name,
|
|
266
|
+
exposed: exposed
|
|
267
|
+
};
|
|
268
|
+
})
|
|
269
|
+
.filter(item => Object.keys(item.exposed).length > 0);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// 注册父组件并广播事件
|
|
274
|
+
pageMaps.parentMap.set(uniqueParentName, parentContext);
|
|
275
|
+
logger.log(`Parent ${uniqueParentName} registered on page ${pagePath}`);
|
|
276
|
+
|
|
277
|
+
// 广播父组件注册事件(包含页面路径信息)
|
|
278
|
+
eventBus.emit(PARENT_REGISTERED_EVENT, {
|
|
279
|
+
name: uniqueParentName,
|
|
280
|
+
parent: parentContext,
|
|
281
|
+
pagePath
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// 组件卸载时清理
|
|
285
|
+
onUnmounted(() => {
|
|
286
|
+
pageMaps.parentMap.delete(uniqueParentName);
|
|
287
|
+
eventBus.emit(PARENT_UNMOUNTED_EVENT, {
|
|
288
|
+
name: uniqueParentName,
|
|
289
|
+
pagePath
|
|
290
|
+
});
|
|
291
|
+
logger.log(`Parent ${uniqueParentName} unmounted from page ${pagePath}`);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
parentName: uniqueParentName, // 返回唯一名称
|
|
296
|
+
parentUid, // 返回唯一ID
|
|
297
|
+
children,
|
|
298
|
+
broadcast: parentContext.broadcast,
|
|
299
|
+
getChildren: parentContext.getChildren,
|
|
300
|
+
getChildExposed: parentContext.getChildExposed,
|
|
301
|
+
getChildrenExposed: parentContext.getChildrenExposed,
|
|
302
|
+
pagePath
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 子组件 Hook
|
|
308
|
+
*/
|
|
309
|
+
export function useChildren(componentName: string, parentName: string) {
|
|
310
|
+
const instance = getCurrentInstance();
|
|
311
|
+
if (!instance) {
|
|
312
|
+
throw new Error('useChildren must be called within setup function');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!componentName || !parentName) {
|
|
316
|
+
throw new Error('Component name and parent name are required for useChildren');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const pagePath = getCurrentPagePath();
|
|
320
|
+
const pageMaps = getCurrentPageMaps();
|
|
321
|
+
const instanceId = generateInstanceId(componentName);
|
|
322
|
+
const childUid = generateComponentUid(); // 生成子组件唯一UID
|
|
323
|
+
const parentRef = ref<ParentContext | null>(null);
|
|
324
|
+
const parentExposed = ref<Record<string, any>>({});
|
|
325
|
+
|
|
326
|
+
// 热更新时清理旧的子组件
|
|
327
|
+
if (pageMaps.childMap.has(instanceId)) {
|
|
328
|
+
logger.log(`Cleaning up existing child ${componentName} for hot reload on page ${pagePath}`);
|
|
329
|
+
pageMaps.childMap.delete(instanceId);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 获取父组件暴露内容
|
|
333
|
+
const getParentExposed = (): Record<string, any> => {
|
|
334
|
+
if (parentRef.value) {
|
|
335
|
+
const exposed = parentRef.value.getExposed();
|
|
336
|
+
parentExposed.value = exposed;
|
|
337
|
+
return exposed;
|
|
338
|
+
}
|
|
339
|
+
return {};
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// 获取子组件exposed内容
|
|
343
|
+
const getExposed = (): Record<string, any> => {
|
|
344
|
+
return instance.exposed || {};
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// 链接到父组件(只在当前页面查找)
|
|
348
|
+
const linkParent = (): boolean => {
|
|
349
|
+
// 在当前页面中查找匹配的父组件
|
|
350
|
+
let parent: ParentContext | undefined;
|
|
351
|
+
|
|
352
|
+
// 首先尝试精确匹配
|
|
353
|
+
parent = pageMaps.parentMap.get(parentName);
|
|
354
|
+
|
|
355
|
+
// 如果精确匹配失败,尝试前缀匹配(支持向后兼容)
|
|
356
|
+
if (!parent) {
|
|
357
|
+
for (const [key, value] of pageMaps.parentMap.entries()) {
|
|
358
|
+
if (key.startsWith(parentName + '-')) {
|
|
359
|
+
parent = value;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (parent) {
|
|
366
|
+
parentRef.value = parent;
|
|
367
|
+
parent.addChild(childContext);
|
|
368
|
+
getParentExposed();
|
|
369
|
+
logger.log(`Child ${componentName} linked to parent ${parent.name} on page ${pagePath}`);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// 子组件上下文
|
|
376
|
+
const childContext: ChildContext = {
|
|
377
|
+
id: instanceId,
|
|
378
|
+
uid: childUid, // 添加唯一ID
|
|
379
|
+
name: componentName,
|
|
380
|
+
|
|
381
|
+
emitToParent(event: string, data?: any) {
|
|
382
|
+
if (parentRef.value) {
|
|
383
|
+
eventBus.emit(`parent:${parentRef.value.name}:${event}`, {
|
|
384
|
+
data,
|
|
385
|
+
childId: instanceId,
|
|
386
|
+
childName: componentName,
|
|
387
|
+
pagePath
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
getParentExposed,
|
|
393
|
+
getInstance() {
|
|
394
|
+
return instance;
|
|
395
|
+
},
|
|
396
|
+
getExposed
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// 注册子组件
|
|
400
|
+
pageMaps.childMap.set(instanceId, childContext);
|
|
401
|
+
logger.log(`Child ${componentName} registered on page ${pagePath}`);
|
|
402
|
+
|
|
403
|
+
// 广播子组件注册事件
|
|
404
|
+
eventBus.emit(CHILD_REGISTERED_EVENT, {
|
|
405
|
+
id: instanceId,
|
|
406
|
+
name: componentName,
|
|
407
|
+
parentName: parentName,
|
|
408
|
+
pagePath
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// 立即尝试连接父组件
|
|
412
|
+
let connected = linkParent();
|
|
413
|
+
|
|
414
|
+
// 如果没连接上,监听父组件注册事件(只监听当前页面的父组件)
|
|
415
|
+
if (!connected) {
|
|
416
|
+
const parentRegisteredHandler = (eventData: { name: string; parent: ParentContext; pagePath: string }) => {
|
|
417
|
+
// 检查是否是我们要连接的父组件(精确匹配或前缀匹配)
|
|
418
|
+
if (
|
|
419
|
+
(eventData.name === parentName || eventData.name.startsWith(parentName + '-')) &&
|
|
420
|
+
eventData.pagePath === pagePath
|
|
421
|
+
) {
|
|
422
|
+
connected = linkParent();
|
|
423
|
+
if (connected) {
|
|
424
|
+
eventBus.off(PARENT_REGISTERED_EVENT, parentRegisteredHandler);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
eventBus.on(PARENT_REGISTERED_EVENT, parentRegisteredHandler);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 监听父组件卸载事件(只监听当前页面的父组件)
|
|
432
|
+
const parentUnmountedHandler = (eventData: { name: string; pagePath: string }) => {
|
|
433
|
+
if (
|
|
434
|
+
(eventData.name === parentName || eventData.name.startsWith(parentName + '-')) &&
|
|
435
|
+
eventData.pagePath === pagePath &&
|
|
436
|
+
parentRef.value
|
|
437
|
+
) {
|
|
438
|
+
parentRef.value = null;
|
|
439
|
+
parentExposed.value = {};
|
|
440
|
+
logger.log(`Parent ${parentName} unmounted from page ${pagePath}, child ${componentName} disconnected`);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
eventBus.on(PARENT_UNMOUNTED_EVENT, parentUnmountedHandler);
|
|
444
|
+
|
|
445
|
+
// 组件卸载时清理
|
|
446
|
+
onUnmounted(() => {
|
|
447
|
+
if (parentRef.value) {
|
|
448
|
+
parentRef.value.removeChild(instanceId);
|
|
449
|
+
}
|
|
450
|
+
pageMaps.childMap.delete(instanceId);
|
|
451
|
+
eventBus.off(PARENT_REGISTERED_EVENT);
|
|
452
|
+
eventBus.off(PARENT_UNMOUNTED_EVENT, parentUnmountedHandler);
|
|
453
|
+
logger.log(`Child ${componentName} unmounted from page ${pagePath}`);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
childId: instanceId,
|
|
458
|
+
childUid, // 返回唯一ID
|
|
459
|
+
childName: componentName,
|
|
460
|
+
parent: parentRef,
|
|
461
|
+
emitToParent: childContext.emitToParent,
|
|
462
|
+
getParentExposed,
|
|
463
|
+
parentExposed: computed(() => parentExposed.value),
|
|
464
|
+
getExposed: childContext.getExposed,
|
|
465
|
+
pagePath
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 监听子组件事件(返回取消监听函数)
|
|
471
|
+
*/
|
|
472
|
+
export function onChildEvent(
|
|
473
|
+
parentName: string,
|
|
474
|
+
event: string,
|
|
475
|
+
handler: (data?: any, childId?: string, childName?: string) => void
|
|
476
|
+
): () => void {
|
|
477
|
+
const pagePath = getCurrentPagePath();
|
|
478
|
+
|
|
479
|
+
const eventHandler = (eventData: { data?: any; childId: string; childName: string; pagePath: string }) => {
|
|
480
|
+
// 只处理当前页面的事件
|
|
481
|
+
if (eventData.pagePath === pagePath) {
|
|
482
|
+
handler(eventData.data, eventData.childId, eventData.childName);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
eventBus.on(`parent:${parentName}:${event}`, eventHandler);
|
|
487
|
+
|
|
488
|
+
// 返回取消监听函数
|
|
489
|
+
return () => {
|
|
490
|
+
eventBus.off(`parent:${parentName}:${event}`, eventHandler);
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 监听父组件事件(返回取消监听函数)
|
|
496
|
+
*/
|
|
497
|
+
export function onParentEvent(childId: string, event: string, handler: (data?: any) => void): () => void {
|
|
498
|
+
eventBus.on(`child:${childId}:${event}`, handler);
|
|
499
|
+
|
|
500
|
+
// 返回取消监听函数
|
|
501
|
+
return () => {
|
|
502
|
+
eventBus.off(`child:${childId}:${event}`, handler);
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* 自动取消监听的事件注册 - 单个事件
|
|
508
|
+
*/
|
|
509
|
+
export function useParentEvent(
|
|
510
|
+
childId: string,
|
|
511
|
+
event: string,
|
|
512
|
+
handler: (data?: any) => void,
|
|
513
|
+
autoClean = true,
|
|
514
|
+
hotReloadReconnect = true
|
|
515
|
+
): () => void {
|
|
516
|
+
const instance = getCurrentInstance();
|
|
517
|
+
const unsubscribe = onParentEvent(childId, event, handler);
|
|
518
|
+
|
|
519
|
+
// 热更新重新注册支持
|
|
520
|
+
if (hotReloadReconnect && instance) {
|
|
521
|
+
const reconnectKey = `parent-event-${childId}-${event}`;
|
|
522
|
+
const reconnectCallback = () => {
|
|
523
|
+
logger.log(`Reconnecting parent event: ${event} for child: ${childId}`);
|
|
524
|
+
onParentEvent(childId, event, handler);
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
528
|
+
|
|
529
|
+
// 组件卸载时清理重新注册回调
|
|
530
|
+
onUnmounted(() => {
|
|
531
|
+
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// 自动在组件卸载时清理
|
|
536
|
+
if (autoClean && instance) {
|
|
537
|
+
onUnmounted(unsubscribe);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return unsubscribe;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* 自动取消监听的事件注册 - 批量事件
|
|
545
|
+
*/
|
|
546
|
+
export function useParentEvents(
|
|
547
|
+
childId: string,
|
|
548
|
+
events: Record<string, (data?: any) => void>,
|
|
549
|
+
autoClean = true,
|
|
550
|
+
hotReloadReconnect = true
|
|
551
|
+
): () => void {
|
|
552
|
+
const instance = getCurrentInstance();
|
|
553
|
+
const cleanups: Function[] = [];
|
|
554
|
+
|
|
555
|
+
Object.entries(events).forEach(([event, handler]) => {
|
|
556
|
+
const unsubscribe = onParentEvent(childId, event, handler);
|
|
557
|
+
cleanups.push(unsubscribe);
|
|
558
|
+
|
|
559
|
+
// 热更新重新注册支持
|
|
560
|
+
if (hotReloadReconnect && instance) {
|
|
561
|
+
const reconnectKey = `parent-events-${childId}-${event}`;
|
|
562
|
+
const reconnectCallback = () => {
|
|
563
|
+
logger.log(`Reconnecting parent event: ${event} for child: ${childId}`);
|
|
564
|
+
onParentEvent(childId, event, handler);
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
568
|
+
|
|
569
|
+
// 组件卸载时清理重新注册回调
|
|
570
|
+
onUnmounted(() => {
|
|
571
|
+
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const cleanupAll = () => {
|
|
577
|
+
cleanups.forEach(cleanup => cleanup());
|
|
578
|
+
cleanups.length = 0;
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
if (autoClean && instance) {
|
|
582
|
+
onUnmounted(cleanupAll);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return cleanupAll;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* 自动取消监听的子组件事件注册 - 单个事件
|
|
590
|
+
*/
|
|
591
|
+
export function useChildEvent(
|
|
592
|
+
parentName: string,
|
|
593
|
+
event: string,
|
|
594
|
+
handler: (data?: any, childId?: string, childName?: string) => void,
|
|
595
|
+
autoClean = true,
|
|
596
|
+
hotReloadReconnect = true
|
|
597
|
+
): () => void {
|
|
598
|
+
const instance = getCurrentInstance();
|
|
599
|
+
const unsubscribe = onChildEvent(parentName, event, handler);
|
|
600
|
+
|
|
601
|
+
// 热更新重新注册支持
|
|
602
|
+
if (hotReloadReconnect && instance) {
|
|
603
|
+
const reconnectKey = `child-event-${parentName}-${event}`;
|
|
604
|
+
const reconnectCallback = () => {
|
|
605
|
+
logger.log(`Reconnecting child event: ${event} for parent: ${parentName}`);
|
|
606
|
+
onChildEvent(parentName, event, handler);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
610
|
+
|
|
611
|
+
// 组件卸载时清理重新注册回调
|
|
612
|
+
onUnmounted(() => {
|
|
613
|
+
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 自动在组件卸载时清理
|
|
618
|
+
if (autoClean && instance) {
|
|
619
|
+
onUnmounted(unsubscribe);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return unsubscribe;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* 自动取消监听的子组件事件注册 - 批量事件
|
|
627
|
+
*/
|
|
628
|
+
export function useChildEvents(
|
|
629
|
+
parentName: string,
|
|
630
|
+
events: Record<string, (data?: any, childId?: string, childName?: string) => void>,
|
|
631
|
+
autoClean = true,
|
|
632
|
+
hotReloadReconnect = true
|
|
633
|
+
): () => void {
|
|
634
|
+
const instance = getCurrentInstance();
|
|
635
|
+
const cleanups: Function[] = [];
|
|
636
|
+
|
|
637
|
+
Object.entries(events).forEach(([event, handler]) => {
|
|
638
|
+
const unsubscribe = onChildEvent(parentName, event, handler);
|
|
639
|
+
cleanups.push(unsubscribe);
|
|
640
|
+
|
|
641
|
+
// 热更新重新注册支持
|
|
642
|
+
if (hotReloadReconnect && instance) {
|
|
643
|
+
const reconnectKey = `child-events-${parentName}-${event}`;
|
|
644
|
+
const reconnectCallback = () => {
|
|
645
|
+
logger.log(`Reconnecting child event: ${event} for parent: ${parentName}`);
|
|
646
|
+
onChildEvent(parentName, event, handler);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
registerHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
650
|
+
|
|
651
|
+
// 组件卸载时清理重新注册回调
|
|
652
|
+
onUnmounted(() => {
|
|
653
|
+
unregisterHotReloadReconnect(reconnectKey, reconnectCallback);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
const cleanupAll = () => {
|
|
659
|
+
cleanups.forEach(cleanup => cleanup());
|
|
660
|
+
cleanups.length = 0;
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
if (autoClean && instance) {
|
|
664
|
+
onUnmounted(cleanupAll);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return cleanupAll;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* 检查父组件是否存在(在当前页面)
|
|
672
|
+
*/
|
|
673
|
+
export function hasParent(parentName: string): boolean {
|
|
674
|
+
const pageMaps = getCurrentPageMaps();
|
|
675
|
+
|
|
676
|
+
// 精确匹配
|
|
677
|
+
if (pageMaps.parentMap.has(parentName)) {
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// 前缀匹配(支持向后兼容)
|
|
682
|
+
for (const key of pageMaps.parentMap.keys()) {
|
|
683
|
+
if (key.startsWith(parentName + '-')) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* 获取所有已注册的父组件名称(当前页面)
|
|
693
|
+
*/
|
|
694
|
+
export function getRegisteredParents(): string[] {
|
|
695
|
+
const pageMaps = getCurrentPageMaps();
|
|
696
|
+
return Array.from(pageMaps.parentMap.keys());
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* 获取父组件实例(当前页面)
|
|
701
|
+
*/
|
|
702
|
+
export function getParent(parentName: string): ParentContext | undefined {
|
|
703
|
+
const pageMaps = getCurrentPageMaps();
|
|
704
|
+
|
|
705
|
+
// 精确匹配
|
|
706
|
+
let parent = pageMaps.parentMap.get(parentName);
|
|
707
|
+
if (parent) {
|
|
708
|
+
return parent;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// 前缀匹配(支持向后兼容)
|
|
712
|
+
for (const [key, value] of pageMaps.parentMap.entries()) {
|
|
713
|
+
if (key.startsWith(parentName + '-')) {
|
|
714
|
+
return value;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* 获取子组件实例(当前页面)
|
|
723
|
+
*/
|
|
724
|
+
export function getChild(childId: string): ChildContext | undefined {
|
|
725
|
+
const pageMaps = getCurrentPageMaps();
|
|
726
|
+
return pageMaps.childMap.get(childId);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* 清理当前页面的组件关系(用于页面卸载时调用)
|
|
731
|
+
*/
|
|
732
|
+
export function cleanupCurrentPageComponents(): void {
|
|
733
|
+
const pagePath = getCurrentPagePath();
|
|
734
|
+
cleanupPageComponentRelations(pagePath);
|
|
735
|
+
logger.log(`Cleaned up component relations for current page: ${pagePath}`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* 手动触发热更新重新连接(用于调试)
|
|
740
|
+
*/
|
|
741
|
+
export function manualHotReloadReconnect(): void {
|
|
742
|
+
logger.log('Manual hot reload reconnection triggered');
|
|
743
|
+
executeHotReloadReconnect();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* 获取热更新状态
|
|
748
|
+
*/
|
|
749
|
+
export function getHotReloadStatus(): { isHotReloading: boolean; reconnectCallbacksCount: number } {
|
|
750
|
+
let totalCallbacks = 0;
|
|
751
|
+
hotReloadReconnectCallbacks.forEach(callbacks => {
|
|
752
|
+
totalCallbacks += callbacks.length;
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
return {
|
|
756
|
+
isHotReloading,
|
|
757
|
+
reconnectCallbacksCount: totalCallbacks
|
|
758
|
+
};
|
|
759
|
+
}
|