hello,大家好,我是徐小夕,之前和大家分享了很多可视化低代码 的最佳实践,以及前端工程化 的实战项目,今天继续和大家分享一下我开源的比较有价值的项目------Next-Admin ,目前已经支持拖拽搭建模块,并且支持:
- 参考线吸附
- 组件成组和取消成组
- 组件对齐
- 支持多选,键盘多选
- 开箱即用的拖拽搭建方案
开源地址:https://github.com/MrXujiang/next-admin
在线demo:http://next-admin.com
往期精彩
- 独立开发(裸辞)100天,我的阶段性复盘
- 文档引擎+AI可视化打造下一代文档编辑器
- 爆肝1000小时, Dooring零代码搭建平台3.5正式上线
- 从零打造一款基于Nextjs+antd5.0的中后台管理系统
模块演示

技术实现
拖拽模块我采用了 movable
, 并研究了它的大量 API
,最终实现了我想要的效果,当然我还设计了一套数据结构,如果大家对可视化搭建感兴趣,也可以扩展成自己的拖拽搭建结构。

元素多选 我采用了 selecto
模块,成组管理器 我采用了 @moveable/helper
, 当然在使用这些库的时候也踩了不少坑,好在已经完美解决。
下面分享一个简单的数据结构,以支持我们的元素自由搭建:
js
const schema = {
"Button": {
id: 'wep_001',
name: 'Button',
type: 'base', // 基础类型组件
base: {
width: 120,
height: 36,
transform: 'translate(100px,100px)'
}
},
"Image": {
id: 'wep_002',
name: 'Image',
type: 'base', // 基础类型组件
base: {
width: 120,
height: 120,
url: '',
transform: 'translate(300px,160px)'
}
}
}
export default schema
工具条实现

对于工具条的实现,我做了统一的封装,以便后期可能更低成本的维护和管理:
- config 工具条配置
- actions 工具条选项对应的功能方法
接下来看看工具条的配置:
js
const toolbar = {
base: [
{
key: 'group',
icon: <GroupOutlined />,
text: '成组',
},
{
key: 'ungroup',
icon: <UngroupOutlined />,
text: '取消成组'
},
{
key: 'left',
icon: <AlignLeftOutlined />,
text: '左对齐'
},
// ... 其他工具条配置
{
key: 'v-space',
icon: <PicCenterOutlined />,
text: '垂直分布空间'
},
{
key: 'h-space',
icon: <PicCenterOutlined style={{transform: 'rotate(-90deg)'}} />,
text: '水平分布空间'
},
]
}
工具条方法封装:
js
const handleOperate = (key: string) => {
// ... some function
// 顶对齐实现
if(key === 'top') {
const rect = moveableRef.current!.getRect();
// console.log(rect)
const moveables = moveableRef.current!.getMoveables();
if (moveables.length <= 1) {
return;
}
moveables.forEach(child => {
child.request<DraggableRequestParam>("draggable", {
y: rect.top,
}, true);
});
moveableRef.current?.updateRect();
return
}
// 底对齐
if(key === 'bottom') {
const rect = moveableRef.current!.getRect();
const moveables = moveableRef.current!.getMoveables();
if (moveables.length <= 1) {
return;
}
moveables.forEach(child => {
child.request<DraggableRequestParam>("draggable", {
y: rect.top + rect.height - (child.props?.target ? (child.props.target as any).offsetHeight : 0),
}, true);
});
moveableRef.current?.updateRect();
return
}
// ... 其他工具条方法
// 水平分布
if(key === 'h-space') {
const groupRect = moveableRef.current!.getRect();
const moveables = moveableRef.current!.getMoveables();
let left = groupRect.left;
if (moveables.length <= 1) {
return;
}
const gap = (groupRect.width - groupRect.children!.reduce((prev, cur) => {
return prev + cur.width;
}, 0)) / (moveables.length - 1);
moveables.sort((a, b) => {
return a.state.left - b.state.left;
});
moveables.forEach(child => {
const rect = child.getRect();
child.request<DraggableRequestParam>("draggable", {
x: left,
}, true);
left += rect.width + gap;
});
moveableRef.current?.updateRect();
return
}
}
通过以上的封装方式我们就能轻松扩展自己的工具条啦~
接下来我们看看工具条实现的效果:

当然代码我已经提交到 github
上了, 大家感兴趣可以参考研究一下。
开源地址:https://github.com/MrXujiang/next-admin
多选 & 成组实现

下面直接上代码:
js
<Selecto
ref={selectoRef}
// dragContainer={container.current}
selectableTargets={[".wep-area .cube"]}
hitRate={0}
selectByClick={true}
selectFromInside={false}
toggleContinueSelect={["shift"]}
ratio={0}
onDragStart={e => {
const moveable = moveableRef.current!;
const target = e.inputEvent.target;
const flatted = deepFlat(targets);
if (
target.tagName === "BUTTON"
|| moveable.isMoveableElement(target)
|| flatted.some(t => t === target || t.contains(target))
) {
e.stop();
}
e.data.startTargets = targets;
}}
onSelect={e => {
const {
startAdded,
startRemoved,
isDragStartEnd,
} = e;
if (isDragStartEnd) {
return;
}
const nextChilds = groupManager.selectSameDepthChilds(
e.data.startTargets,
startAdded,
startRemoved,
);
setSelectedTargets(nextChilds.targets());
}}
onSelectEnd={e => {
const {
isDragStartEnd,
isClick,
added,
removed,
inputEvent,
} = e;
const moveable = moveableRef.current!;
if (isDragStartEnd) {
inputEvent.preventDefault();
moveable.waitToChangeTarget().then(() => {
moveable.dragStart(inputEvent);
});
}
let nextChilds: TargetList;
if (isDragStartEnd || isClick) {
if (isCommand) {
nextChilds = groupManager.selectSingleChilds(targets, added, removed);
} else {
nextChilds = groupManager.selectCompletedChilds(targets, added, removed, isShift);
}
} else {
nextChilds = groupManager.selectSameDepthChilds(e.data.startTargets, added, removed);
}
e.currentTarget.setSelectedTargets(nextChilds.flatten());
setSelectedTargets(nextChilds.targets());
}}
></Selecto>
完整代码都同步到 Next-Admin
了, 如果大家感兴趣也可以研究一下。
后期规划
后续会在 Next-Admin
中集成更多最佳实践,也欢迎感兴趣的朋友一起交流讨论。
如果你对 next
开发或者需要开发一套管理系统, 我相信 Next-Admin
会给你开发和学习的灵感。
同时也欢迎和我一起贡献, 让它变得更优秀~
github
地址: https://github.com/MrXujiang/next-admin
演示地址:http://next-admin.com
由于服务器在国外, 所以建议大家git到本地体验~
欢迎star + 反馈~