Skip to content

Commit 41b4dc0

Browse files
committed
add mirco component bus
1 parent ca3d90f commit 41b4dc0

File tree

6 files changed

+214
-14
lines changed

6 files changed

+214
-14
lines changed

mobile-ui/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"base64-js": "^1.5.1",
1818
"cross-env": "^7.0.3",
1919
"dayjs": "^1.11.13",
20+
"jszip": "^3.10.1",
2021
"lodash": "^4.17.21",
2122
"moment": "^2.30.1",
2223
"monaco-editor": "^0.51.0",

mobile-ui/src/components/flow/components/FlowPage.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import FlowContent from "@/components/flow/components/FlowContent";
1818
import FlowFooter from "@/components/flow/components/FlowFooter";
1919
import {FlowViewReactContext} from "@/components/flow/view";
2020
import FlowForm404 from "@/components/flow/components/FlowForm404";
21-
import {getComponent} from "@/framework/ComponentBus";
21+
import ComponentBus from "@/framework/ComponentBus";
2222
import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext";
2323
import {FlowButtonClickContext} from "@/components/flow/domain/FlowButtonClickContext";
2424

@@ -48,9 +48,9 @@ const FlowPage: React.FC<FlowPageProps> = (props) => {
4848
const FlowFormView = flowRecordContext.getFlowFormView() as React.ComponentType<FlowFormViewProps>;
4949

5050
// 延期表单视图
51-
const PostponedFormView = getComponent(PostponedFormViewKey) as React.ComponentType<PostponedFormProps>;
51+
const PostponedFormView = ComponentBus.getInstance().getComponent<PostponedFormProps>(PostponedFormViewKey);
5252
// 选人表单视图
53-
const UserSelectFormView = getComponent(UserSelectFormViewKey) as React.ComponentType<UserSelectFormProps>;
53+
const UserSelectFormView = ComponentBus.getInstance().getComponent<UserSelectFormProps>(UserSelectFormViewKey);
5454

5555
const version = useSelector((state: FlowReduxState) => state.flow.version);
5656

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {registerComponent} from "@/framework/ComponentBus";
1+
import ComponentBus from "@/framework/ComponentBus";
22
import {PostponedFormViewKey, UserSelectFormViewKey} from "@/components/flow/types";
33
import PostponedFormView from "@/components/flow/plugins/PostponedFormView";
44
import UserSelectFormView from "@/components/flow/plugins/UserSelectFormView";
55

6-
registerComponent(PostponedFormViewKey, PostponedFormView);
7-
registerComponent(UserSelectFormViewKey, UserSelectFormView);
6+
ComponentBus.getInstance().registerComponent(PostponedFormViewKey, PostponedFormView);
7+
ComponentBus.getInstance().registerComponent(UserSelectFormViewKey, UserSelectFormView);
88

Original file line numberDiff line numberDiff line change
@@ -1,14 +1,78 @@
11
import React from "react";
2+
import {loadRemoteComponent, loadRemoteScript} from "@/utils/dynamicLoader";
23

3-
const componentBus = new Map<string, React.ComponentType<any>>();
4+
class ComponentBus {
45

6+
private readonly componentBus = new Map<string, React.ComponentType<any>>();
57

6-
// 注册表操作方法
7-
export const registerComponent = (name: string, component: React.ComponentType<any>) => {
8-
componentBus.set(name, component);
9-
};
8+
/**
9+
* 注册组件
10+
* @param key 组件key
11+
* @param component 组件类型
12+
*/
13+
public registerComponent = (key: string, component: React.ComponentType<any>) => {
14+
this.componentBus.set(key, component);
15+
}
1016

11-
// 获取组件
12-
export const getComponent = (name: string): React.ComponentType<any> | undefined => {
13-
return componentBus.get(name);
17+
/**
18+
* 删除组件
19+
* @param key 组件key
20+
*/
21+
public removeComponent = (key: string) => {
22+
this.componentBus.delete(key);
23+
}
24+
25+
/**
26+
* 注册远程组件
27+
* @param key 组件key
28+
* @param remoteUrl 远程组件地址
29+
* @param scope 远程组件作用域
30+
* @param module 远程组件名称
31+
*/
32+
public registerRemoteComponent = (key: string, remoteUrl: string, scope: string, module: string): Promise<boolean> => {
33+
return new Promise((resolve, reject) => {
34+
loadRemoteScript(remoteUrl).then(() => {
35+
loadRemoteComponent(scope, module)
36+
.then((ComponentModule: any) => {
37+
const Component = ComponentModule.default || ComponentModule;
38+
this.registerComponent(key, Component);
39+
resolve(true);
40+
}).catch(error => {
41+
reject(error);
42+
});
43+
}).catch(error => {
44+
reject(error);
45+
});
46+
})
47+
}
48+
49+
/**
50+
* 获取组件
51+
* @param key 组件key
52+
* @param defaultComponent 默认组件
53+
*/
54+
public getComponent = <T extends {} = any>(key: string, defaultComponent?: React.ComponentType<T>): React.ComponentType<T> | undefined => {
55+
const component = this.componentBus.get(key) as React.ComponentType<T> | undefined;
56+
if (component) {
57+
return component;
58+
}
59+
if (defaultComponent) {
60+
return defaultComponent;
61+
}
62+
return undefined;
63+
};
64+
65+
private ComponentBus() {
66+
// 私有构造函数,防止外部实例化
67+
}
68+
69+
private static instance: ComponentBus = new ComponentBus();
70+
71+
// 单例模式
72+
public static getInstance(): ComponentBus {
73+
return ComponentBus.instance;
74+
}
1475
}
76+
77+
export default ComponentBus;
78+

mobile-ui/src/utils/base64.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
export const rcFileToBase64 = (file: any): Promise<string> => {
3+
return new Promise((resolve, reject) => {
4+
const reader = new FileReader();
5+
reader.readAsDataURL(file);
6+
reader.onload = () => resolve(reader.result as string);
7+
reader.onerror = error => reject(error);
8+
});
9+
}
10+
11+
12+
export const base64ToBlob = (base64: string, type: string) => {
13+
const binStr = atob(base64.split(',')[1]);
14+
const len = binStr.length;
15+
const arr = new Uint8Array(len);
16+
for (let i = 0; i < len; i++) {
17+
arr[i] = binStr.charCodeAt(i);
18+
}
19+
return new Blob([arr], {type: type});
20+
}
21+
22+
export const base64ToString = (base64: string) => {
23+
if (base64.indexOf('base64') !== -1) {
24+
base64 = base64.split(',')[1];
25+
}
26+
return atob(base64);
27+
}

mobile-ui/src/utils/dynamicLoader.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import JSZip from "jszip";
2+
import {base64ToBlob} from "@/utils/base64";
3+
4+
export const loadRemoteComponent = (scope: string, module: string) => {
5+
return new Promise(async (resolve, reject) => {
6+
try {
7+
// Initialize the sharing scope (shared modules like react, etc.)
8+
//@ts-ignore
9+
await __webpack_init_sharing__('default');
10+
//@ts-ignore
11+
const container = window[scope]; // Get the container loaded on the window object
12+
if (!container) {
13+
reject(new Error(`Remote scope ${scope} not found on window.`));
14+
return;
15+
}
16+
17+
//@ts-ignore
18+
await container.init(__webpack_share_scopes__.default); // Initialize the container
19+
const factory = await container.get(module); // Get the module factory
20+
resolve(factory()); // Get the actual module
21+
} catch (e) {
22+
reject(e);
23+
}
24+
});
25+
};
26+
27+
export const loadRemoteScript = (url: string): Promise<void> => {
28+
return new Promise((resolve, reject) => {
29+
try {
30+
const script = document.createElement('script');
31+
script.src = url;
32+
script.onload = (e) => {
33+
resolve();
34+
};
35+
script.onerror = reject;
36+
document.head.appendChild(script);
37+
} catch (e) {
38+
reject(e);
39+
}
40+
});
41+
};
42+
43+
44+
export const loadFileScript = (content: string): Promise<void> => {
45+
return new Promise((resolve, reject) => {
46+
try {
47+
try {
48+
eval(content);
49+
} catch (e) {
50+
resolve();
51+
return;
52+
}
53+
const encoder = new TextEncoder();
54+
const encodedContent = encoder.encode(content);
55+
const blob = new Blob([encodedContent], {type: 'application/javascript'});
56+
const url = URL.createObjectURL(blob);
57+
const script = document.createElement('script');
58+
script.src = url;
59+
script.onload = () => {
60+
resolve();
61+
};
62+
script.onerror = (e) => {
63+
reject(e);
64+
};
65+
document.head.appendChild(script);
66+
} catch (e) {
67+
reject(e);
68+
}
69+
});
70+
};
71+
72+
73+
export const loadZipJsFileScript = async (base64: string): Promise<void> => {
74+
return new Promise((resolve, reject) => {
75+
const file = base64ToBlob(base64, 'application/zip');
76+
if (file) {
77+
const zip = new JSZip();
78+
const content = file.arrayBuffer();
79+
zip.loadAsync(content).then((unzipped:any) => {
80+
const jsFiles: { relativePath: string, content: string }[] = [];
81+
82+
const filePromises: Promise<void>[] = [];
83+
unzipped.forEach((relativePath:any, file:any) => {
84+
if (relativePath.endsWith(".js")) {
85+
const filePromise = file.async('text').then((text:any) => {
86+
jsFiles.push({relativePath, content: text});
87+
});
88+
filePromises.push(filePromise);
89+
}
90+
});
91+
92+
Promise.all(filePromises).then(() => {
93+
jsFiles.reduce((prevPromise: any, jsFile) => {
94+
return prevPromise.then(() => {
95+
return loadFileScript(jsFile.content).then(() => {
96+
console.log('Load success file:', jsFile.relativePath);
97+
});
98+
});
99+
}, Promise.resolve()).then(() => {
100+
resolve();
101+
}).catch(reject);
102+
}).catch(reject);
103+
}).catch(reject);
104+
} else {
105+
reject(new Error('Failed to convert base64 to Blob.'));
106+
}
107+
});
108+
};

0 commit comments

Comments
 (0)