项目需求背景
最近接了一个需求,说要实现组件的拖拽效果。我们项目中没人接,我由于没做过比较感兴趣就接了。本项目实现了一个基于 React DnD 的断面组件拖拽功能,包含 6 个断面区域和左侧组件栏。系统支持不同类型组件的拖放限制和参数配置,实现了复杂的业务规则控制。幸不辱命,最终做出来了。
1.1 业务目标
- 实现组件的拖拽放置功能
- 支持不同断面的组件限制规则
- 提供组件参数配置界面
- 确保拖拽操作的流畅性和可靠性
功能需求
2.1 基础需求
断面 6 左侧区域限制:
- 仅支持取水泵组件
- 最多接受一个组件
断面 2-5 限制:
- 支持所有类型组件
- 每个断面最多一个取水泵组件
断面 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]);
回显参数效果如下
总结
遇到需求不要怕。人和代码总有一个能跑,只有想不到的,没有做不到的。只要你想、相信就一定能如愿以偿。还有开发的时候心态一定要好。最终如愿做出来了、得到产品和主管的表扬。
留下一个问题
如果客户不提供服务器的情况下,前端怎么把前后端服务打包成一个exe文件。交付出去的时候客户打开exe就能跑这个计算工具。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。