1. 总述

1.1 功能说明

天舟云支持开发者上传各自域自定义的组件,其整体架构如下:

不了解资源中心开发?先看资源中心的资源开发手册

1.2 支持场景

表单详情、表单列表、视图列表、工作台均可使用自定义组件

1.3 注意事项

1.3.1 API转发

建议使用天舟云提供的统一网关转发各自域的业务API,防止跨域异常。

统一网关需要去 👉 正式环境的CSB的「路由管理」模块中,配置各环境下的转发目标地址。

其中,路由编码须唯一

使用时,转发地址为:/apis/common/proxy/lcpGw/tz_api/「路由编码」/「path内容」

2. 开发与集成

2.1 本地开发

🔥 快速初始化开发目录

推荐使用@cvte/tsdx-cli快速构建组件目录

npm i -g @cvte/tsdx-cli

tsdx-cli -v

2.1.1 准备工作

安装资源中心Cli,详情见资源开发手册,并将工程初始化为一个资源

项目工程安装天舟云依赖@cvte/cir-lcp-sdk,版本高于1.1.0

2.1.2 资源导出内容

资源初始化成功后,会在项目工程根目录下生成文件rc.exposes.js

每个自定义组件都必须拥有两个导出键:

  • 自定义组件渲染文件:示例中为LcpFormRelateData
  • 自定义组件配置文件:示例中为LcpFormRelateDataConfigs,命名建议以Configs结尾

需增加导出内容,示例如下:

// 必须使用export default的方式
export default {
  /**
   * 
   * 导出如下所示数据结构:
      shared?: { // 导出资源中的共享依赖
        [depName: string]: { // 依赖名称作为键名
          isEager?: boolean; // 共享是否为即时消费模式,默认为false
          isSingleton?: boolean; // 共享是否为全局单例模式,默认为false
          strictVersion?: boolean; // 是否严格遵守版本,默认为false
          requiredVersion?: string; // 依赖所必需的版本
          version?: string; // 当前资源所提供该依赖的版本
          shareKey?: string; // 依赖的共享别名
        };
      };
      [exposeKey: string]: {
        file: string; // 导出内容所在的文件在工程中的相对路径【**** 带文件后缀名 ****】
        name?: string; // 资源中心资源的导出标题
        desc?: string; // 资源中心资源的导出简介
      };
   *
   */
  shared: {
    react: {
      isSingleton: true,
      versions: '17.0.2',
    },
    'react-dom': {
      isSingleton: true,
      version: '17.0.2',
    },
    antd: {
      isSingleton: true,
      version: '4.24.8',
    },
    axios: {
      isSingleton: true,
    },
  },
  // 其他导出内容...

  // 该导出键是专门用于导出该自定义组件,也是该自定义组件在渲染端中展示的内容
  LcpFormRelateData: {
    file: './src/LcpFormRelateData/Render/index.tsx',    // 实现该自定义组件的文件相对路径,该路径仅为示例;注意!⚠️ 该路径需要写出完整的文件名
    name: '关联数据',
    desc: '关联其他表单的数据字段,并展示在当前表单中;关联的字段均为虚拟字段',
  },
  // 该导出键是专门用于导出该自定义组件的配置元数据
  LcpFormRelateDataConfigs: {
    file: './src/LcpFormRelateData/exportConfig.ts',    // 实现该自定义组件配置文件相对路径,该路径仅为示例;注意!⚠️ 该路径需要写出完整的文件名
    name: '关联数据配置',
    desc: '关联数据组件类型的配置',
  },
};

2.1.3 开发设计画布渲染组件

Q:为什么要开发它?
A:它将专门用于在天舟云的设计器画布中,展示一个自定义组件的简易渲染内容提高设计器的使用性能。使用者通过这个简易渲染内容,可以明确了解到该自定义组件的输出数据类型(下拉、文本框、数字等)。
A:⚠️注意,该文件只会在自定义组件注入配置中使用,不会被资源导出。

建立单独的文件SampleRender.tsx

其内容示例如下:

import React from 'react';
import FormMakeEntity from '@cvte/cir-form-make-resource-types/entities/formMake';
import { ILayout, ILayoutConfigBase } from '@cvte/cir-form-generator-resource-types/src/interface/main';

type FormMakeEntityType = typeof FormMakeEntity;

interface ISampleRenderProps {
  /**
   * 设计器的数据实体实例
   *
   * @type {FormMakeEntityType}
   * @memberof IProps
   */
  dataEntity?: FormMakeEntityType;
  /**
   * 设计元素的设计配置数据
   *
   * @type {ILayout}
   * @memberof IProps
   */
  config?: ILayout;
  /**
   * 设计器的全局样式配置
   *
   * @type {FormMakeEntityType['formMakeData']['globalConfig']['style']}
   * @memberof IProps
   */
  globalConfigStyle?: FormMakeEntityType['formMakeData']['globalConfig']['style'];
}

const CustomSampleRender = (props: ISampleRenderProps) => {
  return <>I'm CustomSampleRender</>;
};

export default CustomSampleRender;

展示效果(开发者须在完成集成后才能看到该效果)如下:

2.1.4 自定义组件注入配置(自定义组件在配置面板「自定义配置」中的配置项)

Q:为什么要开发它?
A:它作为一个自定义组件的配置元数据(配置项),展示其自定义组件的各类配置、设计器端(配置界面)展示该自定义组件的配置项等。

Q:如何实现组件配置项自定义?
A:自定义组件的独立配置项最终应该存放于dictConfig配置中,此配置项用于后端将配置数据同步模型配置数据中。

建立单独的文件exportConfig.ts

该文件必须要在rc.exposes.js中导出,作为自定义组件的配置内容,用于让插件市场读取自定义组件的配置元数据

其内容示例如下:

import { CustomComponentInjectConfig } from '@cvte/cir-lcp-sdk'; // @cvte/cir-lcp-sdk版本须>=1.1.0
import CustomSampleRender from './SampleRender'; // 用于设计画布展示的渲染组件
import CustomConfigItems from './CustomConfigItems'; // 用于配置面板中渲染自定义配置项的组件

const customCptInjectConfig = new CustomComponentInjectConfig({
  // 组件类型编码,内容须用大写字母+下划线表示,用于做唯一标识
  // 注意⚠️:此处的`code`值必须要和插件市场上架的组件「编码」一一对应!
  // 该编码在被上架天舟云插件市场中须保持唯一!
  code: 'TEST_RELATE_DATA',
  // 组件类型名称,用于在设计器中显示
  // 该`name`值作为自定义组件名称的默认值,在插件市场中的名称没有被填写时,就会展示此处配置的名称
  name: '关联数据',
});

customCptInjectConfig.setRenderConfig({
  // 组件在设计器画布区域中展示的渲染组件
  sampleRender: CustomSampleRender,
});

// 设置组件的在设计器端展示的基础配置项
customCptInjectConfig.setBaseConfig([
  // 需要设置一个自定义的dictConfig配置项,该配置项可以是个嵌套的复杂表单,也可以是个简单的配置组件
  // ⚠️ 开发者需要确保自定义组件的配置数据最终保存在dictConfig配置中
  {
          key: 'dictConfig',
        type: 'custom',
        render:(params)=>{
            return <CustomConfigItems {...{
                ...(params || {}),
            }} />
        }
  },
]);

export default customCptInjectConfig;

展示效果(开发者须在完成集成后才能看到该效果)如下:

来自@柯剑烽同学的优秀例子:

import { IInjectFormItemFnParamsContext } from '@cvte/cir-lcp-sdk/src/interface/customComponent/context';
import { IFormItem } from '@cvte/wuli-antd/src/WULIForm';

const WHETHER_OPTIONS = [
  {
    key: '1',
    label: '是',
    value: '1',
  },
  {
    key: '0',
    label: '否',
    value: '0',
  },
];
export const setDictConfig = (
  formRef: IInjectFormItemFnParamsContext['formRef'],
  key: string,
  value?: any,
  ctx?: {
    context?: IInjectFormItemFnParamsContext;
  }
) => {
  const dictConfig =
    formRef?.current?.getFormItem?.('dictConfig') || formRef?.current?.getInitFormData?.('dictConfig') || {};
  dictConfig[key] = value;
  formRef?.current?.setFormItem?.('dictConfig', dictConfig);
  ctx?.context?.onConfirm?.('dictConfig', dictConfig);
};

const customCptBaseConfig = (context?: IInjectFormItemFnParamsContext): IFormItem[] => {
  const { formRef } = context || {};
  setTimeout(() => {
    const { dictConfig: dictConfigByProps } = (context as any)?.baseFormProps?.componentItem?.config?.baseConfig || {};
    const { isShowValidateBtn, isAllowEditDish, allowEditDishNodes } = dictConfigByProps || {};
    const defFormData = formRef?.current?.getInitFormData?.();
    const curFormData = formRef?.current?.getFormItem?.();

    formRef?.current?.setFormItem?.(
      'dictConfig',
      curFormData?.dictConfig || defFormData?.dictConfig || dictConfigByProps
    );
    formRef?.current?.setFormItem?.(
      'dictConfigIsShowValidateBtn',
      curFormData?.dictConfigIsShowValidateBtn || defFormData?.dictConfigIsShowValidateBtn || isShowValidateBtn
    );
    formRef?.current?.setFormItem?.(
      'dictConfigIsAllowEditDish',
      curFormData?.dictConfigIsAllowEditDish || defFormData?.dictConfigIsAllowEditDish || isAllowEditDish
    );
    formRef?.current?.setFormItem?.(
      'dictConfigAllowEditDishNodes',
      curFormData?.dictConfigAllowEditDishNodes || defFormData?.dictConfigAllowEditDishNodes || allowEditDishNodes
    );
  });

  return [
    {
      type: 'hidecomp',
      key: 'dictConfig',
    },
    {
      type: 'select',
      key: 'dictConfigIsShowValidateBtn',
      label: '是否显示校验信息',
      configs: {
        options: WHETHER_OPTIONS,
        onSelect: () => {
          const dictConfigIsShowValidateBtn = formRef?.current?.getFormItem?.('dictConfigIsShowValidateBtn');
          context?.onConfirm?.('dictConfigIsShowValidateBtn', dictConfigIsShowValidateBtn);
          setDictConfig(formRef, 'isShowValidateBtn', dictConfigIsShowValidateBtn, {
            context,
          });
        },
      },
    },
    {
      type: 'select',
      key: 'dictConfigIsAllowEditDish',
      label: '是否允许编辑菜品',
      configs: {
        options: WHETHER_OPTIONS,
        onSelect: () => {
          const dictConfigIsAllowEditDish = formRef?.current?.getFormItem?.('dictConfigIsAllowEditDish');
          context?.onConfirm?.('dictConfigIsAllowEditDish', dictConfigIsAllowEditDish);
          setDictConfig(formRef, 'isAllowEditDish', dictConfigIsAllowEditDish, {
            context,
          });
        },
      },
    },
    {
      type: 'input',
      key: 'dictConfigAllowEditDishNodes',
      label: '允许编辑开启节点(流程节点编码用逗号隔开)',
      configs: {
        onBlur: () => {
          const dictConfigAllowEditDishNodes = formRef?.current?.getFormItem?.('dictConfigAllowEditDishNodes');
          context?.onConfirm?.('dictConfigAllowEditDishNodes', dictConfigAllowEditDishNodes);
          setDictConfig(formRef, 'allowEditDishNodes', dictConfigAllowEditDishNodes, {
            context,
          });
        },
      },
    },
  ];
};

export default customCptBaseConfig;

2.1.5 调试自定义组件

详情参考资源的浏览器插件调试本地资源

通过资源中心浏览器插件将线上流量转发至本地资源服务。

2.2 线上管理自定义组件

管理入口在各自域天舟云配置系统中的「平台配置管理」>「依赖管理」>「页面组件管理

2.2.1 上架自定义组件

详情配置:

资源名称」:填写自定义组件配置元数据所在的资源名称
插件配置导出」:填写自定义组件配置元数据在该资源中的导出键名
编码」:填写自定义组件配置元数据中配置的code
展示名称」:设计器组件列表中展示该自定义组件的名称
展示图标」:设计器组件列表中展示该自定义组件的图标
分组」:设计器组件列表中该自定义组件所属的分组
应用类型」:该自定义组件可使用在哪些渲染场景中

组件渲染内容导出列表:

终端类型」:该自定义组件可使用在哪些设备终端中
资源名称」:填写自定义组件渲染内容所在的资源名称
组件导出键名」:填写自定义组件在该资源中的导出键名
批量翻译方法导出键名」:填写自定义组件内置的批量翻译方法在该资源中的导出键名,用于在列表/视图等批量展示场景中,该组件的翻译方法

展示效果如下:

2.2.2 ⚠️ 注意上架环境

必须要注意的是,线上自定义组件(「平台配置管理」>「依赖管理」>「页面组件管理」)在各环境(FAT、SIT、UAT、PROD)下的数据是不一样的;

比如:在SIT环境配置了该自定义组件之后,如果想要在UAT环境生效,需要将SIT的「页面组件管理」列表数据同步到UAT环境;

同时,在各环境(FAT、UAT、PRO)中,资源的导出内容也是分开独立的;

比如:在FAT环境发布了该资源新的导出内容,如果想要在UAT环境生效,需要将FAT下对应版本的资源同步到UAT环境中,详情可参考👉 资源中心帮助文档 - 七、线上资源版本管理

3. 常见问题与解决方案

3.1 自定义组件资源发版了,设计器和渲染页面为什么没有?

1、自定义组件的资源发版之后,是否有进行上架自定义组件?如果没有请参见上文「2.2 线上管理自定义组件」部分;

2、如果有上架,请检查上架时配置的「编码」和「自定义组件配置元数据中配置的code值」是否一致,消费侧自定义组件时会强制将编码内容转换为大写,所以编码请严格按照要求(大写+下划线组成内容);

3、如果有上架,请检查上架时配置的「应用类型」是否是当前设计器/渲染页面所属的渠道类型;

3.2 自定义组件资源发版/修复了,为什么渲染页面没有更新最新改动?

1、资源发版/修复后,如果最新改动在新的版本号上,例如:1.0.0.11.0.1,那么需要在消费自定义组件资源的宿主资源修改对应环境的依赖资源版本号

2、资源hotfix后,那么最新的改动只是在DEV上,那么需要在自定义组件资源历史版本中,找到hotfix的对应版本,点击并同步到指定环境

3.3 自定义组件资源发版了也上架了,设计器为什么只展示一个「自定义组件」文本?

请参照上文中的「3.2 自定义组件资源发版/修复了,为什么渲染页面没有更新最新改动?」,检查消费的自定义组件资源版本是否在当前环境下为最新;

作者:tz_docs  创建时间:2024-07-02 10:56
最后编辑:袁子涵  更新时间:2025-05-12 18:04