平台:designable设计器
github:designable
目录
- 1 背景
- 2 技术栈
- 3 组件拖拽和放置
- 3.1 类型定义
- 3.2 拖拽
- 3.3 放置
 
1 背景
由于业务需求,我们需要实现designable平台的一个简易版的组件拖拽功能。

功能列表:
- 拖拽区域 
  - 渲染组件列表,包括组件的名称和icon
- 组件可以拖动
 
- 放置区域 
  - 拖动后的组件可以放置,并且放置区域的组件依旧存在
- 放置区域的组件可以正确渲染相应样式,使用formily的schema json渲染
- 放置区域的组件可以上下移动排序,同时可以进行删除和编辑操作
 
2 技术栈
antd
 formily:表单引擎,可以根据schema json直接渲染表单
 react-beautiful-dnd:常用于列表的拖拽,支持排序
 react-dnd:拖拽和放置功能,比如上面截图的组件拖拽
3 组件拖拽和放置
3.1 类型定义
右侧组件类型:id唯一标识,scheme存放渲染表单的json文件
export interface ComponentConfig {
  id?: string; // 唯一标识,随机生成,且不可更改
  key: string; // 表单字段key,用户可以更改
  title: string; // 拖拽区域的文案,不可更改
  component_type: ComponentType; // RN侧的组件标识,不可更改
  schema: ISchema;
}
export enum ComponentType {
  TextInputRow = 'TextInputRow', // 文本输入框
  DateInputRow = 'DateInputRow', // 时间选择器
  CheckBox = 'CheckBox',
}
右侧组件列表:
export const ComponentConfigs: ComponentConfig[] = [
  {
    key: ComponentType.TextInputRow,
    schema: {
      title: ComponentType.TextInputRow,
      type: 'string',
      'x-component': 'Input',
      'x-decorator': 'FormItem',
      'x-rn-component': ComponentType.TextInputRow, // RN侧的组件名称,必须要保持一致
    },
  },
  {
    key: ComponentType.DateInputRow,
    schema: {
      title: ComponentType.DateInputRow,
      type: 'string',
      'x-component': 'DatePicker',
      'x-decorator': 'FormItem',
      'x-rn-component': ComponentType.DateInputRow,
    },
  },
  {
    key: ComponentType.CheckBox,
    schema: {
      title: ComponentType.CheckBox,
      type: 'string',
      'x-component': 'Checkbox',
      'x-decorator': 'FormItem',
      'x-rn-component': ComponentType.CheckBox,
    },
  },
].map((i) => ({ ...i, title: i.key, component_type: i.key }));
3.2 拖拽
useDrag:让DOM实现拖拽能力的构子
- 请求参数: 
  - type: 指定元素的类型,只有 类型相同的元素 才能进行drop操作
- item: 元素在拖拽过程中,描述该对象的数据。可以在- useDrop中的- drop接收到该数据
- collect: 返回一个描述状态的普通对象,然后返回以注入到组件中。它接收两个参数,一个- DragTargetMonitor实例和拖拽元素描述信息- item
 
- 返回参数: 
  - 第一个返回值:是一个对象 表示关联在拖拽过程中的变量,需要在传入useDrag的规范方法的collect属性中进行映射绑定, 比如:isDraging,canDrag等
- 第二个返回值: 代表拖拽元素的ref
- 第三个返回值: 代表拖拽元素拖拽后实际操作到的dom
 
- 第一个返回值:是一个对象 表示关联在拖拽过程中的变量,需要在传入
// 用于包裹每一个可以拖拽的组件
export const WrapComponent = (props: DndComponentDndItem) => {
  const [, drag] = useDrag(() => ({
    type: ItemTypes.CARD,
    item: props.config,
    // collect中可以监控drag状态变更,并把状态暴露给组件
    collect: (monitor) => ({ isDragging: !!monitor.isDragging() }),
  }));
  return (
    <div
      style={{
        width: 100, // todo: 卡片无法居中
        cursor: 'move',
        height: 50,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: 'white',
        borderRadius: 4,
      }}
      ref={drag} // dom元素实例
    >
      {props.children}
    </div>
  );
};
3.3 放置
useDrop:让拖拽物放置的构子
- 请求参数: 
  - type: 指定元素的类型,只有 类型相同的元素 才能进行drop操作
- item: 元素在拖拽过程中,描述该对象的数据。可以在- useDrop中的- drop接收到该数据
- collect: 返回一个描述状态的普通对象,然后返回以注入到组件中。它接收两个参数,一个- DragTargetMonitor实例和拖拽元素描述信息- item
 
- 返回参数: 
  - 第一个返回值:是一个对象 表示关联在拖拽过程中的变量,需要在传入useDrag的规范方法的collect属性中进行映射绑定, 比如:isDraging,canDrag等
- 第二个返回值: 代表拖拽元素的ref
- 第三个返回值: 代表拖拽元素拖拽后实际操作到的dom
 
- 第一个返回值:是一个对象 表示关联在拖拽过程中的变量,需要在传入



















