feat: improve lowcode component error state in simulator renderer (#1818)

* feat: improve lowcode component error state in simulator renderer

* chore: use faultComponentMap instead of ComponentRender.FaultComponent
This commit is contained in:
eternalsky 2023-03-31 15:12:39 +08:00 committed by GitHub
parent d0c3c0f13c
commit d64da1e065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 82 additions and 22 deletions

View File

@ -52,6 +52,8 @@ module.exports = {
'error', 'error',
{ default: ['signature', 'field', 'constructor', 'method'] } { default: ['signature', 'field', 'constructor', 'method'] }
], ],
'@typescript-eslint/no-unused-vars': ['error'] '@typescript-eslint/no-unused-vars': ['error'],
'no-redeclare': 0,
'@typescript-eslint/no-redeclare': 1,
}, },
}; };

View File

@ -234,6 +234,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return engineConfig.get('faultComponent') ?? null; return engineConfig.get('faultComponent') ?? null;
} }
get faultComponentMap(): any {
return engineConfig.get('faultComponentMap') ?? null;
}
@computed get componentsAsset(): Asset | undefined { @computed get componentsAsset(): Asset | undefined {
return this.get('componentsAsset'); return this.get('componentsAsset');
} }

View File

@ -195,6 +195,7 @@ class Renderer extends Component<{
thisRequiredInJSE={host.thisRequiredInJSE} thisRequiredInJSE={host.thisRequiredInJSE}
notFoundComponent={host.notFoundComponent} notFoundComponent={host.notFoundComponent}
faultComponent={host.faultComponent} faultComponent={host.faultComponent}
faultComponentMap={host.faultComponentMap}
customCreateElement={(Component: any, props: any, children: any) => { customCreateElement={(Component: any, props: any, children: any) => {
const { __id, ...viewProps } = props; const { __id, ...viewProps } = props;
viewProps.componentId = __id; viewProps.componentId = __id;

View File

@ -465,6 +465,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
rendererName: 'LowCodeRenderer', rendererName: 'LowCodeRenderer',
thisRequiredInJSE: host.thisRequiredInJSE, thisRequiredInJSE: host.thisRequiredInJSE,
faultComponent: host.faultComponent, faultComponent: host.faultComponent,
faultComponentMap: host.faultComponentMap,
customCreateElement: (Comp: any, props: any, children: any) => { customCreateElement: (Comp: any, props: any, children: any) => {
const componentMeta = host.currentDocument?.getComponentMeta(Comp.displayName); const componentMeta = host.currentDocument?.getComponentMeta(Comp.displayName);
if (componentMeta?.isModal) { if (componentMeta?.isModal) {
@ -629,6 +630,7 @@ function getLowCodeComponentProps(props: any) {
} }
newProps[k] = props[k]; newProps[k] = props[k];
}); });
newProps['componentName'] = props['_componentName'];
return newProps; return newProps;
} }

View File

@ -161,7 +161,9 @@ export default function baseRendererFactory(): IBaseRenderComponent {
constructor(props: IBaseRendererProps, context: IBaseRendererContext) { constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
super(props, context); super(props, context);
this.context = context; this.context = context;
this.__parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression; this.__parseExpression = (str: string, self: any) => {
return parseExpression({ str, self, thisRequired: props?.thisRequiredInJSE, logScope: props.componentName });
};
this.__beforeInit(props); this.__beforeInit(props);
this.__init(props); this.__init(props);
this.__afterInit(props); this.__afterInit(props);
@ -299,8 +301,8 @@ export default function baseRendererFactory(): IBaseRenderComponent {
}; };
__parseData = (data: any, ctx?: Record<string, any>) => { __parseData = (data: any, ctx?: Record<string, any>) => {
const { __ctx, thisRequiredInJSE } = this.props; const { __ctx, thisRequiredInJSE, componentName } = this.props;
return parseData(data, ctx || __ctx || this, { thisRequiredInJSE }); return parseData(data, ctx || __ctx || this, { thisRequiredInJSE, logScope: componentName });
}; };
__initDataSource = (props: IBaseRendererProps) => { __initDataSource = (props: IBaseRendererProps) => {

View File

@ -21,7 +21,7 @@ export default function rendererFactory(): IRenderComponent {
class FaultComponent extends PureComponent<IPublicTypeNodeSchema | any> { class FaultComponent extends PureComponent<IPublicTypeNodeSchema | any> {
render() { render() {
logger.error(`%c组件渲染异常, 异常原因: ${this.props.error?.message || this.props.error || '未知'}`, 'color: #ff0000;'); logger.error(`%c${this.props.componentName || ''} 组件渲染异常, 异常原因: ${this.props.error?.message || this.props.error || '未知'}`, 'color: #ff0000;');
return createElement(Div, { return createElement(Div, {
style: { style: {
width: '100%', width: '100%',
@ -159,7 +159,25 @@ export default function rendererFactory(): IRenderComponent {
} }
getFaultComponent() { getFaultComponent() {
return this.props.faultComponent || FaultComponent; const { faultComponent, faultComponentMap, schema } = this.props;
if (faultComponentMap) {
const { componentName } = schema;
return faultComponentMap[componentName] || faultComponent || FaultComponent;
}
return faultComponent || FaultComponent;
}
getComp() {
const { schema, components } = this.props;
const { componentName } = schema;
const allComponents = { ...RENDERER_COMPS, ...components };
let Comp = allComponents[componentName] || RENDERER_COMPS[`${componentName}Renderer`];
if (Comp && Comp.prototype) {
if (!(Comp.prototype instanceof BaseRenderer)) {
Comp = RENDERER_COMPS[`${componentName}Renderer`];
}
}
return Comp;
} }
render() { render() {
@ -173,14 +191,8 @@ export default function rendererFactory(): IRenderComponent {
return '模型结构异常'; return '模型结构异常';
} }
debug('entry.render'); debug('entry.render');
const { componentName } = schema;
const allComponents = { ...RENDERER_COMPS, ...components }; const allComponents = { ...RENDERER_COMPS, ...components };
let Comp = allComponents[componentName] || RENDERER_COMPS[`${componentName}Renderer`]; let Comp = this.getComp();
if (Comp && Comp.prototype) {
if (!(Comp.prototype instanceof BaseRenderer)) {
Comp = RENDERER_COMPS[`${componentName}Renderer`];
}
}
if (this.state && this.state.engineRenderError) { if (this.state && this.state.engineRenderError) {
return createElement(this.getFaultComponent(), { return createElement(this.getFaultComponent(), {
@ -190,11 +202,13 @@ export default function rendererFactory(): IRenderComponent {
} }
if (Comp) { if (Comp) {
return createElement(AppContext.Provider, { value: { return createElement(AppContext.Provider, {
appHelper, value: {
components: allComponents, appHelper,
engine: this, components: allComponents,
} }, createElement(ConfigProvider, { engine: this,
},
}, createElement(ConfigProvider, {
device: this.props.device, device: this.props.device,
locale: this.props.locale, locale: this.props.locale,
}, createElement(Comp, { }, createElement(Comp, {

View File

@ -160,6 +160,11 @@ export interface IRendererProps {
/** 当组件渲染异常时,显示的组件 */ /** 当组件渲染异常时,显示的组件 */
faultComponent?: IGeneralComponent; faultComponent?: IGeneralComponent;
/** */
faultComponentMap?: {
[prop: string]: IGeneralComponent;
};
/** 设备信息 */ /** 设备信息 */
device?: string; device?: string;
@ -208,6 +213,7 @@ export interface IBaseRendererProps {
* 'default' * 'default'
*/ */
device?: 'default' | 'mobile' | string; device?: 'default' | 'mobile' | string;
componentName?: string;
} }
export interface INodeInfo { export interface INodeInfo {

View File

@ -157,6 +157,7 @@ export function canAcceptsRef(Comp: any) {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
return Comp?.$$typeof === REACT_FORWARD_REF_TYPE || Comp?.prototype?.isReactComponent || Comp?.prototype?.setState || Comp._forwardRef; return Comp?.$$typeof === REACT_FORWARD_REF_TYPE || Comp?.prototype?.isReactComponent || Comp?.prototype?.setState || Comp._forwardRef;
} }
/** /**
* transform array to a object * transform array to a object
* @param arr array to be transformed * @param arr array to be transformed
@ -207,7 +208,6 @@ export function checkPropTypes(value: any, name: string, rule: any, componentNam
return !err; return !err;
} }
/** /**
* transform string to a function * transform string to a function
* @param str function in string form * @param str function in string form
@ -230,7 +230,26 @@ export function transformStringToFunction(str: string) {
* @param self scope object * @param self scope object
* @returns funtion * @returns funtion
*/ */
export function parseExpression(str: any, self: any, thisRequired = false) {
function parseExpression(options: {
str: any; self: any; thisRequired?: boolean; logScope?: string;
}): any;
function parseExpression(str: any, self: any, thisRequired?: boolean): any;
function parseExpression(a: any, b?: any, c = false) {
let str;
let self;
let thisRequired;
let logScope;
if (typeof a === 'object' && b === undefined) {
str = a.str;
self = a.self;
thisRequired = a.thisRequired;
logScope = a.logScope;
} else {
str = a;
self = b;
thisRequired = c;
}
try { try {
const contextArr = ['"use strict";', 'var __self = arguments[0];']; const contextArr = ['"use strict";', 'var __self = arguments[0];'];
contextArr.push('return '); contextArr.push('return ');
@ -250,11 +269,15 @@ export function parseExpression(str: any, self: any, thisRequired = false) {
const code = `with(${thisRequired ? '{}' : '$scope || {}'}) { ${tarStr} }`; const code = `with(${thisRequired ? '{}' : '$scope || {}'}) { ${tarStr} }`;
return new Function('$scope', code)(self); return new Function('$scope', code)(self);
} catch (err) { } catch (err) {
logger.error('parseExpression.error', err, str, self?.__self ?? self); logger.error(`${logScope || ''} parseExpression.error`, err, str, self?.__self ?? self);
return undefined; return undefined;
} }
} }
export {
parseExpression,
};
export function parseThisRequiredExpression(str: any, self: any) { export function parseThisRequiredExpression(str: any, self: any) {
return parseExpression(str, self, true); return parseExpression(str, self, true);
} }
@ -320,11 +343,17 @@ export function forEach(targetObj: any, fn: any, context?: any) {
interface IParseOptions { interface IParseOptions {
thisRequiredInJSE?: boolean; thisRequiredInJSE?: boolean;
logScope?: string;
} }
export function parseData(schema: unknown, self: any, options: IParseOptions = {}): any { export function parseData(schema: unknown, self: any, options: IParseOptions = {}): any {
if (isJSExpression(schema)) { if (isJSExpression(schema)) {
return parseExpression(schema, self, options.thisRequiredInJSE); return parseExpression({
str: schema,
self,
thisRequired: options.thisRequiredInJSE,
logScope: options.logScope,
});
} else if (isI18nData(schema)) { } else if (isI18nData(schema)) {
return parseI18n(schema, self); return parseI18n(schema, self);
} else if (typeof schema === 'string') { } else if (typeof schema === 'string') {