头图

项目需求背景

最近接了一个需求,说要实现组件的拖拽效果。我们项目中没人接,我由于没做过比较感兴趣就接了。本项目实现了一个基于 React DnD 的断面组件拖拽功能,包含 6 个断面区域和左侧组件栏。系统支持不同类型组件的拖放限制和参数配置,实现了复杂的业务规则控制。幸不辱命,最终做出来了。

效果图

1.1 业务目标

  • 实现组件的拖拽放置功能
  • 支持不同断面的组件限制规则
  • 提供组件参数配置界面
  • 确保拖拽操作的流畅性和可靠性

功能需求

2.1 基础需求

  1. 断面 6 左侧区域限制

    • 仅支持取水泵组件
    • 最多接受一个组件
  2. 断面 2-5 限制

    • 支持所有类型组件
    • 每个断面最多一个取水泵组件
  3. 断面 1 限制

    • 不接受任何组件

2.2 组件配置需求

  • 支持组件参数的动态配置
  • 不同类型组件显示不同配置项
  • 参数修改实时生效
  • 每个断面设置的最大组件数为 5 个
  • 填完之后要将每个断面的信息回显
  • 断面 2-5 每个断面有一个初始值组件
  • 取水泵组件要放在断面最上方

代码设计

3.1 组件配置面板实现

左侧目前支持三个组件及取水泵组件的拖拽组件。

const DraggableItem: React.FC<{ name: string; type: string }> = ({
  name,
  type,
}) => {
  const [{ isDragging }, dragRef] = useDrag<
    DragItem,
    unknown,
    { isDragging: boolean }
  >({
    type,
    item: { name, type: type as ComponentType },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  return (
    <div
      ref={dragRef}
      style={{
        padding: '8px 16px',
        margin: '8px',
        backgroundColor: isDragging ? '#f0f0f0' : '#ffffff',
        cursor: 'grab',
        opacity: isDragging ? 0.5 : 1,
      }}
    >
      <img style={{ width: 60 }} src={`/public/images/${type}.png`} alt="" />
      {name}
    </div>
  );
};

const DraggableItemList: React.FC = () => {
  return (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <DraggableItem name="组件A" type="comp1" />
      <DraggableItem name="组件B" type="comp2" />
      <DraggableItem name="组件C" type="comp3" />
    </div>
  );
};

export default DraggableItemList;

3.2 断面结构设计

断面数量根据表单配置动态生成。

const { sections, containerConfigs } = useMemo(() => {
  const generatedSections = Array.from(
    { length: store.tabList.length - 1 },
    (_, i) => `${i + 1}-${i + 2}`,
  );

  const generatedContainerConfigs = Array.from(
    { length: store.tabList.length },
    (_, i) => ({
      id: i + 1,
      acceptTypes: i < store.tabList.length ? ['comp1', 'comp2', 'comp3', 'comp4'] : [],
    }),
  );

  return {
    sections: generatedSections,
    containerConfigs: generatedContainerConfigs.reverse(),
  };
}, [store.tabList.length]);

3.3 拖拽源组件实现

使用 react-dnd 插件。

<DndProvider backend={HTML5Backend}>
  <div>
    {store.isAllParams && <DraggableItemList />}

    {containerConfigs.map((config) => (
      <div key={config.id}>
        {config.id === containerConfigs.length && (
          <PumpOnlyDropArea
            containerId={config.id}
            sideContainerState={sideContainerState}
            setSideContainerState={setSideContainerState}
            setSectionData={setSectionData}
          />
        )}
        <DropContainer
          containerId={config.id}
          acceptTypes={config.acceptTypes as ComponentType[]}
          sectionData={sectionData}
          setSectionData={setSectionData}
          sections={sections}
          form={form}
          containerConfigs={containerConfigs as ContainerConfig[]}
        />
      </div>
    ))}
  </div>
</DndProvider>

3.4 参数回显实现

实现复杂参数回显逻辑,支持多个相同类型组件参数以逗号隔开。

useEffect(() => {
  const formValues: FormValues = {};

  sections.forEach((section) => {
    const matchConfig = containerConfigs.find(
      (config) => section === `${config.id - 1}-${config.id}`,
    );

    if (matchConfig) {
      const matchingSection = sectionData.find(
        (s) => s.sectionId === matchConfig.id,
      );

      if (matchingSection) {
        formValues[`section_${section}`] = {
          ditchLength: matchingSection.ditchLength,
        };

        const componentParams = {
          waterIntakeArea: [] as number[],
          lossOfBlock: [] as number[],
        };

        matchingSection.components.forEach((component: Component) => {
          switch (component.type) {
            case 'comp2':
              if (component.params?.lossOfBlock) {
                componentParams.lossOfBlock.push(
                  component.params.lossOfBlock,
                );
              }
              break;
          }
        });

        formValues[`section_${section}`] = {
          ...formValues[`section_${section}`],
          lossOfBlock: componentParams.lossOfBlock.join(','),
        };
      }
    }
  });

  form.setFieldsValue(formValues);
}, [sectionData, form, sections, containerConfigs]);

回显参数效果如下
image.png

总结

遇到需求不要怕。人和代码总有一个能跑,只有想不到的,没有做不到的。只要你想、相信就一定能如愿以偿。还有开发的时候心态一定要好。最终如愿做出来了、得到产品和主管的表扬。
留下一个问题
如果客户不提供服务器的情况下,前端怎么把前后端服务打包成一个exe文件。交付出去的时候客户打开exe就能跑这个计算工具。


moye
1.3k 声望49 粉丝

不积硅步无以至千里