百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

阿里低代码框架 lowcode-engine 低代码表单实战

connygpt 2024-08-20 13:59 4 浏览

前沿

lowcode-engine功能比较强大,最近这段时间做了个低代码表单的实战,在过程中遇到一些问题,在这里做下介绍和总结。

功能演示

前台功能

主要介绍一下前台功能的基本实现和一些问题。

FormContainer容器组件

我们的默认容器不是页面,而是需要自定义容器。例如,在常见的低代码平台中默认容器是表单容器,通过表单容器类提供布局能力。这块之前有一篇文章详情介绍,可以查看FormContainer容器。

那篇文章介绍了怎么实现自定义容器,我们打开详情页面,看到所有的表单项都是只读的,我们在容器中做一个全局状态管理,在这里用context去实现。

  • 定义 Provider
// 定义FormContainerProvider

export const FormContainerProvider
: FC<IFormContainerProviderProps> = ({ children, isMobile }) => {
  const processorAction = useCreation(() => {
    return createFormContainerProcessor();
  }, []);
  const { processor, getRoot, destroy } = processorAction || {};

  useEffect(() => {
    processor.setMobile(isMobile);
  }, [isMobile]);

  useEffect(() => {
    return () => {
      destroy?.();
    };
  }, []);

  return <Context.Provider value={processor!}>{children}</Context.Provider>;
};


  • 之后我们就可以在容器组件和FormItem组件内获取数据,这块简单做了封装处理。
// 从conext获取更改只读的方法
const [changeReadonly] = useFormContainerSelector((s) => [s.changeReadonly]);


  • Form容器对外提供能力

我们提交保存操作没有在容器内实现对应的物料,是在外部自定义的,这时候就需要我们对FormContainer绑定Ref,之后我们获取实例可以拿到对应的方法。

// 绑定ref
React.useImperativeHandle(
  ref,
  () => {
    return {
      formRef: form,
      changeReadonly, // 更改只读方法
    };
  },
  []
);


物料组件

我们对每个表单项开发对应的物料,物料的开发,官方提供脚手架快速创建项目,之前也写过一遍文章,流程不清楚的请移步自定义物料篇。这里我们用日期物料做说明,还会介绍一下开发调试,之前文章说我们要把物料发布到npm上,这样开发调试很不方便。

Filed Date 物料

  • 定义Date物料类型

可以看到我们有个基础的类型,是一些通用的属性,columnConfig这个属性是每个FormItem的config。

export interface IColumnEntity<T extends EFieldType = EFieldType> extends IBaseEntity {
  ...
  // 数据库字段类型
  fieldType: TFieldType;

  // 标题
  title?: string;

  // 扩展参数
  extraParam?: Record<string, any>;

  // 列配置信息
  columnConfig: T extends keyof TColumnConfigMap ? TColumnConfigMap[T] : TColumnConfig;

  // 校验信息
  validateConfig: IColumnValidateConfig;
}


  • FieldData config

日期物料的config信息,有了具体的TS类型,在我们写代码的时候会事半功倍

/**
 * 日期
 */
export interface IColumnDateConfig {
  /**
   * 描述
   */
  description: string;
  /**
   * 占位符
   */
  placeholder?: string;

  /**
   * 1. 普通 2禁用 3 只读
   */
  status: number;

  /**
   * 格式化类型 1. YY-MM 2. YYYY-MM-DD 3. YYYY-MM-DD HH:MM 4. YYYY-MM-DD HH:MM:SS
   */
  format: number;
  /**
   * 默认值类型
   */
  defaultValueType: string;

  /**
   * 默认值
   */
  defaultValue: string;
}


  • meta.ts信息

这里主要描述物料组件信息, 我们简单介绍一下setter信息,其它的可以看官方文档。

configure: {
  props: [
    {
      title: {
        label: '格式',
      },
      name: 'columnConfig.format',
      supportVariable: false,
      setter: {
        componentName: SelectSetter,
        props: {
          options: DateFormatConstant,
          changeReRenderEvent: true,
        },
        initialValue: 2,
      },
    },
 ]
}


props中的name属性columnConfig.format,我们可以使用这种方式来描述嵌套的属性。

  • 实现FieldData组件

这里相对来说也不复杂,需要注意的是porps中的内容,有我们在meta文件中定义的props,还有FormItem中标注的value,onChange属性,还有一些属性,大家可以打印下看看。有时候有些需求实现这上面的属性会有帮助,

// FieldData 具体实现
export interface IFieldDateProps extends BaseWrapperProps<EFieldType.DATE> {}

export const FieldDate: FC<IFieldDateProps> = (props) => {
  const { columnConfig, onChange, value, ...otherProps } = props;
  const [readonly] = useFormContainerSelector((s) => [s.readonly]);

  const format = columnConfig?.format;

  const currFormat = DateFormatConstant.find((f) => f.value == format);

  const onDateChange: DatePickerProps['onChange'] = (date, dateString) => {
    const currUnix = date?.valueOf();
    onChange?.(currUnix);
  };
  return (
    <BaseWrapper {...props}>
      <DatePicker
        style={{ width: '100%' }}
        disabled={readonly || columnConfig?.status === EFieldStatus.disable}
        placeholder={columnConfig?.placeholder}
        showTime={currFormat?.showTime}
        format={currFormat?.label || 'YYYY-MM-DD'}
        value={value ? dayjs(value) : undefined}
        onChange={onDateChange}
      />
    </BaseWrapper>
  );
};


setter

实现我们的需求,setter是一个比较重要的环节,这里我们对setter做了重写,全部使用了antd的组件。setter我们分为通用的setter和单个物料的自己的setter。

  • setter定义

官方的案例Setter使用的是字符串,也就是在引擎注入的setter供我们使用。在项目中开发,我们可以用一个setter组件,待setter稳定后,考虑引擎注入。

  • 每个setter对应一个props属性

上面我们在meta文件中的columnConfig.format使用了SelectSetter,定义如下:

export const SelectSetterFun: FC<ISelectSetterProps> = (props) => {
  const {
    options = [{ label: '-', value: '' }],
    onChange,
    mode,
    value,
    showSearch,
    onChangeEvent,
    changeReRenderEvent,
  } = props;

  const dataSource = formateOptions(options);

  const { sendReRenderEvent } = useReRenderEvent({ isBindEvent: false });
  return (
    <Select
      style={{ width: '100%' }}
      value={value}
      size={'small'}
      options={dataSource}
      onChange={(val) => {
        onChange?.(val);
        onChangeEvent?.(val);
        changeReRenderEvent && sendReRenderEvent();
      }}
      showSearch={showSearch}
    />
  );
};

export const SelectSetter = SetterHoc(SelectSetterFun);


  • 高阶组件 SetterHoc 在setter中直接使用hooks组件会有问题,我们用类组件做一层包裹。
export const SetterHoc = (Component: any) => {
  return class SetterComponent extends React.Component {
    render() {
      return <Component {...this.props} />;
    }
  };
};


  • 获取和设置其它props值

有的需求我们在setter中需要获取其它组件的属性,通过props?.field?.parent 可以获取到,这里封装了一个自定的hooks,来获取和设置值

export const usePropsValue = (props: any) => {
 
  const getPropValue = useMemoizedFn((key: string) => {
    const propsField = props?.field?.parent;
    // 获取同级其他属性 showJump 的值
    return propsField.getPropValue(key);
  });

  const setPropsValue = useMemoizedFn((key: string, value: any) => {
    const propsField = props?.field?.parent;
    // 获取同级其他属性 showJump 的值
    propsField.setPropValue(key, value);
  });

  return {
    getPropValue,
    setPropsValue,
  };
};


还有一种方法可以可以实现此效果,就是在setter上设置extraProps属性,这个属性可以有两个方法setValue和getValue.

  1. 在meta上设置
// 更改其它选项,在meta上设置
extraProps: OptionsSetterExtraProps,


// 更改其它选项,在meta上设置
extraProps: OptionsSetterExtraProps,


  • setter之间通信

在引擎中,通信需要通过事件的方式去做。在这里,通常我们有些setter的变更会影响其它setter,例如:日期的格式变化默认值会做相应的调整。在业务中,setter的变更,通知依赖的setter刷新,刷新的时候重新获取属性值,做业务调整。

在这里,封装了reRender一个hooks,


export const useReRenderEvent = (props?: IUseReRenderEventProps) => {
  const { isBindEvent = true } = props || {};
  const update = useUpdate(); // 强制触发更新

  const reRenderEvent = useMemoizedFn(() => {
    update();
  });

  /**
   * 发送重新渲染事件
   */
  const sendReRenderEvent = useMemoizedFn(() => {
    event.emit(EFiledEventName.ReRenderEmit);
  });

  useEffect(() => {
    isBindEvent && event.on(EFiledEventName.ReRender, reRenderEvent);
    return () => {
      isBindEvent && event.off(EFiledEventName.ReRender, reRenderEvent);
    };
  }, [isBindEvent]);

  return {
    sendReRenderEvent,
  };
};


这个hooks,有两个作用,一个是发送重新渲染事件,一个是监听渲染事件。在上面的案例当中,

1.在格式的setter中引入该hooks,做事件发送。

const { sendReRenderEvent } = useReRenderEvent({ isBindEvent: false });

return (
  <Select
    ...
    onChange={(val) => {
      changeReRenderEvent && sendReRenderEvent();
    }}
    ...
  />
);


  1. 在默认值setter中,做事件的监听。
// 监听格式的变化
useReRenderEvent();

// 获取格式数据
const { getPropValue } = usePropsValue(otherProps);

const format = getPropValue('format');


渲染详情页

封装FormContainerRnder组件,来做渲染。

  • 引擎提供了ReactRender的能力,我们传入对应的scheam信息,就可以做到显示。
<ReactRenderer
  className="lowcode-plugin-sample-preview-content"
  schema={schema}
  designMode="dialog"
  rendererName="LowCodeRenderer"
  components={components}
  onCompGetRef={onCompGetRef}
  appHelper={{
    requestHandlersMap: {
      fetch: createFetchHandler(),
    },
  }}
/>


  • 获取FormContainer组件Ref

在数据提交的时候,我们需要获取组件的实力,在引擎中获取Ref方法,要使用 onCompGetRef方法。

const onCompGetRef = (schema: any, ref: any) => {
  if ('FormContainer' === schema.componentName) {
    const { formRef, ...otherRef } = ref;
    formInstanceRef.current = ref.formRef;
    formOtherRef.current = otherRef;
    // 获取到ref,执行resolve
    promiseRef?.resolve(true);
  }
};


在渲染的时候,我们有可能获取不到实例,我们用个异步来处理。

// 此处异步是因为不能立马获取到form的实例
const promiseRef = useCreation(() => {
  return createPromiseWrapper();
}, []);


提供对外的数据能力

React.useImperativeHandle(
  ref,
  () => {
    return {
      getFormInstance: async () => {
        await promiseRef.promise;
        return formInstanceRef.current! as FormInstance;
      },
      changeReadonly: async (disabled: boolean) => {
        await promiseRef.promise;
        formOtherRef.current?.changeReadonly?.(disabled);
      },
    };
  },
  []
);


  • 初始化数据

打开编辑详情页的时候,需要把从接口获取的数据给设置到表单上。有了FormContainer实例,我们可以很方便的做设置

useAsyncEffect(async () => {
  if (mode !== EMode.create && !itemData.loading && Object.keys(itemData.data).length > 0) {
    // 获取实例
    const formInstance = await formRef.current?.getFormInstance();
    // 数据转换
    const formValues = convertItemDataToFormValues(itemData.data, table.data.columns);
    
    // 设置值
    formInstance?.setFieldsValue?.(formValues);
  }
}, [itemData.data, itemData.loading]);


提交数据

获取表单数据,做提交。这里通过FormContainer的时候,可以获取所有的值,包括做一些前端的校验等。

  • 获取所有值,调用api,做数据提交
export const getFormValues = async (formRef: React.MutableRefObject<IPreviewRef>) => {
  const formInstance = await formRef?.current?.getFormInstance();
  return formInstance?.getFieldsValue();
};


开发调试

开发物料后,如果我们发布npm,整个流程会很繁琐,效率低,物料脚手架也提供了调试,不过在我们实际业务开发中,会有一些业务数据和上下文的环节依赖,所有要能实时调试开发。接下来几个步骤介绍一下

  • 启动lowcode开发模式
"lowcode:dev": "build-scripts start --config ./build.lowcode.js",


会开启一个实时的监听服务。

  • 在我们引擎中的assets.json修改,使用上面服务的地址,修改内容如下

修改在url中的内容为本地地址,这时候我们开发后。刷新浏览器,会实时看到结果

  • 做环境变量,动态切换
import assetsLocal from '../services/assets-local.json';
import assets from '../services/assets.json';

export const getAssetsJson = () => {
  // 用本地配置文件
  if (process.env.LOCAL_UI_MATERIAL === 'true') {
    return assetsLocal;
  }
  return assets;
};


总结

以上就是对lowcode-engine低代码实战内容,后续我们介绍一下引擎和后台之间的交互,可以让大家实现一个完整的案例。


作者:Witty_Wizard
链接:https://juejin.cn/post/7346865556328808463

相关推荐

3分钟让你的项目支持AI问答模块,完全开源!

hello,大家好,我是徐小夕。之前和大家分享了很多可视化,零代码和前端工程化的最佳实践,今天继续分享一下最近开源的Next-Admin的最新更新。最近对这个项目做了一些优化,并集成了大家比较关注...

干货|程序员的副业挂,12个平台分享

1、D2adminD2Admin是一个完全开源免费的企业中后台产品前端集成方案,使用最新的前端技术栈,小于60kb的本地首屏js加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助...

Github标星超200K,这10个可视化面板你知道几个

在Github上有很多开源免费的后台控制面板可以选择,但是哪些才是最好、最受欢迎的可视化控制面板呢?今天就和大家推荐Github上10个好看又流行的可视化面板:1.AdminLTEAdminLTE是...

开箱即用的炫酷中后台前端开源框架第二篇

#头条创作挑战赛#1、SoybeanAdmin(1)介绍:SoybeanAdmin是一个基于Vue3、Vite3、TypeScript、NaiveUI、Pinia和UnoCSS的清新优...

搭建React+AntDeign的开发环境和框架

搭建React+AntDeign的开发环境和框架随着前端技术的不断发展,React和AntDesign已经成为越来越多Web应用程序的首选开发框架。React是一个用于构建用户界面的JavaScrip...

基于.NET 5实现的开源通用权限管理平台

??大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!??今天小编推荐一款基于.NE...

StreamPark - 大数据流计算引擎

使用Docker完成StreamPark的部署??1.基于h2和docker-compose进行StreamPark部署wgethttps://raw.githubusercontent.com/a...

教你使用UmiJS框架开发React

1、什么是Umi.js?umi,中文可发音为乌米,是一个可插拔的企业级react应用框架。你可以将它简单地理解为一个专注性能的类next.js前端框架,并通过约定、自动生成和解析代码等方式来辅助...

简单在线流程图工具在用例设计中的运用

敏捷模式下,测试团队的用例逐渐简化以适应快速的发版节奏,大家很早就开始运用思维导图工具比如xmind来编写测试方法、测试点。如今不少已经不少利用开源的思维导图组件(如百度脑图...)来构建测试测试...

【开源分享】神奇的大数据实时平台框架,让Flink&amp;Spark开发更简单

这是一个神奇的框架,让Flink|Spark开发更简单,一站式大数据实时平台!他就是StreamX!什么是StreamX大数据技术如今发展的如火如荼,已经呈现百花齐放欣欣向荣的景象,实时处理流域...

聊聊规则引擎的调研及实现全过程

摘要本期主要以规则引擎业务实现为例,陈述在陌生业务前如何进行业务深入、调研、技术选型、设计及实现全过程分析,如果你对规则引擎不感冒、也可以从中了解一些抽象实现过程。诉求从硬件采集到的数据提供的形式多种...

【开源推荐】Diboot 2.0.5 发布,自动化开发助理

一、前言Diboot2.0.5版本已于近日发布,在此次发布中,我们新增了file-starter组件,完善了iam-starter组件,对core核心进行了相关优化,让devtools也支持对IAM...

微软推出Copilot Actions,使用人工智能自动执行重复性任务

IT之家11月19日消息,微软在今天举办的Ignite大会上宣布了一系列新功能,旨在进一步提升Microsoft365Copilot的智能化水平。其中最引人注目的是Copilot...

Electron 使用Selenium和WebDriver

本节我们来学习如何在Electron下使用Selenium和WebDriver。SeleniumSelenium是ThoughtWorks提供的一个强大的基于浏览器的开源自动化测试工具...

Quick &#39;n Easy Web Builder 11.1.0设计和构建功能齐全的网页的工具

一个实用而有效的应用程序,能够让您轻松构建、创建和设计个人的HTML网站。Quick'nEasyWebBuilder是一款全面且轻巧的软件,为用户提供了一种简单的方式来创建、编辑...