Skip to content

自定义配置项开发

如当前 表单类数据源类 配置项无法满足自定义组件的特殊属性配置交互(例如:特殊的数值格式、特殊的取值交互方式等),支持在自定义组件工程中 开发自定义配置项,并通过 amis 的 FormItem 注册为新的配置项类型 type,再在组件模型文件(model.ts)的属性配置(propsSchema) 中添加使用。

如需了解平台内置的配置项,请参阅 表单类配置项数据源类配置项

整体流程:开发自定义配置项组件 → 使用 @FormItem 注册并导出 → 在组件模型文件中 import 此自定义配置项组件,并在 propsSchema 中添加这个新增的配置项类型。

步骤一:开发自定义配置项组件

自定义组件项目(非 src/components 直接子目录,避免识别成自定义组件)中开发自定义配置项组件:

  • 展示只读摘要或占位输入,以及打开弹窗、抽屉等复杂编辑区;
  • 在确认保存时,将结构化结果通过 onChange 写回表单;
  • 需要读取页面/表单上下文时,可通过 RendererProps(如 datadisabled)获取。

完整可参考示例工程中的实现(弹窗 + JSON Schema 编辑器):

targetNumber__c/customStyleConfig/index.tsx(GitHub)

示例中 propsSchema 里传入的 viewStylewideScreen 等字段,会作为普通 props 进入该组件,可按需扩展。

步骤二:使用 FormItem 注册为 amis 表单项并导出

使用 amis 提供的 FormItem 装饰器(或等价 API)将组件注册为表单项,type 字段必须与后续 propsSchema 中的 type 完全一致

tsx
// @ts-ignore
import { FormItem, RendererProps } from 'amis';
import React from 'react';
import { Input, Button, Modal, message } from 'antd';
import { SettingOutlined } from '@ant-design/icons';
// @ts-ignore
import JSONEditor from '@wibetter/json-editor';
import '@wibetter/json-editor/lib/index.css';

import { configSchema } from './configSchema';
import './index.scss';

interface ICustomStyleConfigProps {
  onChange?: (value: unknown) => void;
  value?: unknown;
  name: string;
  /** 可由 propsSchema 传入的扩展配置 */
  viewStyle?: string;
  wideScreen?: boolean;
}

interface ICustomStyleConfigState {
  isModalVisible: boolean;
  editorValue: unknown;
}

@FormItem({
  type: 'customStyleConfig',
  /** 当列出的 props 变化时需要重渲染时在此声明,例如依赖表单 data */
  detectProps: ['data'],
})
export default class CustomStyleConfigRenderer extends React.Component<
  RendererProps & ICustomStyleConfigProps,
  ICustomStyleConfigState
> {
  constructor(props: RendererProps & ICustomStyleConfigProps) {
    super(props);
    this.state = {
      isModalVisible: false,
      editorValue: props.value ?? {},
    };
    this.showModal = this.showModal.bind(this);
    this.handleModalOk = this.handleModalOk.bind(this);
    this.handleModalCancel = this.handleModalCancel.bind(this);
    this.handleEditorChange = this.handleEditorChange.bind(this);
    this.getDisplayValue = this.getDisplayValue.bind(this);
  }

  handleEditorChange(value: unknown) {
    this.setState({ editorValue: value });
  }

  handleModalOk() {
    const { editorValue } = this.state;
    const { onChange } = this.props;
    try {
      onChange?.(editorValue);
      this.setState({ isModalVisible: false });
    } catch {
      message.error('保存配置失败');
    }
  }

  handleModalCancel() {
    const value = this.props.value ?? {};
    this.setState({ isModalVisible: false, editorValue: value });
  }

  showModal() {
    const value = this.props.value ?? {};
    this.setState({ isModalVisible: true, editorValue: value });
  }

  getDisplayValue() {
    const { value } = this.props;
    if (!value || typeof value !== 'object' || Object.keys(value as object).length === 0) {
      return '';
    }
    return '已配置自定义样式';
  }

  render() {
    const { disabled, viewStyle, wideScreen, value } = this.props;
    const { isModalVisible } = this.state;

    return (
      <div className="properties-panel-custom-style-config">
        <div className="custom-style-config-input-container">
          <Input
            value={this.getDisplayValue()}
            placeholder="请配置自定义样式"
            disabled={disabled}
            readOnly
            className="custom-style-config-input"
          />
          <Button
            type="text"
            icon={<SettingOutlined />}
            onClick={this.showModal}
            disabled={disabled}
            className="custom-style-config-setting-btn"
          />
        </div>
        <Modal
          title="自定义样式配置"
          visible={isModalVisible}
          onOk={this.handleModalOk}
          onCancel={this.handleModalCancel}
          width={800}
          centered
          okText="确定"
          cancelText="取消"
          destroyOnClose={false}
        >
          <div className="custom-style-config-modal-content">
            {isModalVisible && (
              <JSONEditor
                schemaData={configSchema}
                jsonData={value}
                onChange={this.handleEditorChange}
                viewStyle={viewStyle || 'tabs'}
                tabPosition="left"
                wideScreen={wideScreen ?? false}
              />
            )}
          </div>
        </Modal>
      </div>
    );
  }
}

与属性面板的约定

约定说明
当前值通过 this.props.value 读取(与 defaultComProps 中同名字段的初始值对应)。
写回表单用户确认配置后调用 this.props.onChange(newValue),否则设计器无法持久化。
detectProps若渲染依赖 data 等上下文字段变化,需在 @FormItem 中声明,否则可能不会更新。
标签与布局labeldescription、校验提示、表单项布局等由 amis 表单统一处理,无需在自定义组件内重复实现。

更多扩展方式见 amis 官方文档:自定义 React — 表单项扩展

步骤三:在组件模型文件 / 属性配置(propsSchema)中添加使用

  1. 引入注册侧产物:确保包含 @FormItem 注册的模块被执行一次(通常通过 import '.../customStyleConfig' 或包导出的 side-effect 入口),样式文件按需单独引入。
  2. propsSchema 中声明type@FormItemtype 相同,namedefaultComProps 中的属性键一致,保证画布默认值与面板编辑的是同一字段。
ts
// 侧效 import:注册 amis 表单项(路径以实际构建产物为准)
import 'neo-bi-cmps/lib/customStyleConfig';
import 'neo-bi-cmps/lib/customStyleConfig.css';

export class TargetNumberModel {
  isAmisCmp: boolean = true;
  label: string = '数值指标';
  description: string =
    '用于展示关键数值指标,支持从 XObject 实体对象获取动态数据,支持绑定多个字段进行展示';
  tags: string[] = ['业务组件'];
  iconUrl: string = 'https://custom-widgets.bj.bcebos.com/TargetNumber.svg';
  targetPage: string[] = ['all'];
  targetDevice: string = 'all';

  defaultComProps = {
    entityApiKey: '',
    targetNumberStyle: {
      baseStyle: {
        backgroundColor: '#ffffff',
        paddingMargin: { margin: '0', padding: '0' },
      },
      layoutStyle: { alignClass: 'flex-col' },
      titleStyle: {
        show: false,
        text: '',
        fontSize: 24,
        fontWeight: 400,
        color: '#000',
      },
      numberStyle: {
        fontSize: 32,
        fontWeight: 600,
        color: '#262626',
      },
      numberTitleStyle: {
        fontSize: 14,
        fontWeight: 400,
        color: '#8c8c8c',
      },
    },
  };

  functions = [
    {
      apiKey: 'loadData',
      label: '刷新数据',
      helpTextKey: '重新加载数值指标数据',
    },
  ];

  propsSchema = [
    {
      type: 'xObjectDetailApi',
      name: 'entityApiKey',
      label: '绑定实体业务数据',
      placeholder: '绑定实体业务数据源',
      disabledFieldSelect: true,
      disabledAutoFetchData: true,
    },
    {
      type: 'selectFieldDescApi',
      name: 'selectFieldDesc',
      xObjectApiKey: 'entityApiKey.xObjectApiKey',
      mode: 'tags',
      label: '绑定字段',
      placeholder: '请至少选择一个要显示的字段',
    },
    {
      type: 'customStyleConfig',
      name: 'targetNumberStyle',
      label: '自定义样式配置',
      viewStyle: 'tabs',
      wideScreen: false,
    },
  ];
}

export default TargetNumberModel;

说明

  • import 的入口路径需与项目打包配置一致(示例为 neo-bi-cmps 发布包路径;单体仓库可为相对路径指向注册文件)。
  • 自定义 type 名称在全局需唯一,避免与平台或其他组件重复。
  • 若业务使用 antd 5+,Modal 等组件 API 可能与 antd 4(如 visibleopen)不同,需按实际版本调整。