模块演示
图片
技术实现
拖拽模块我采用了 movable, 并研究了它的大量 API,最终实现了我想要的效果,当然我还设计了一套数据结构,如果大家对可视化搭建感兴趣,也可以扩展成自己的拖拽搭建结构。
图片
元素多选我采用了 selecto 模块,成组管理器我采用了 @moveable/helper, 当然在使用这些库的时候也踩了不少坑,好在已经完美解决。
下面分享一个简单的数据结构,以支持我们的元素自由搭建:
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 工具条选项对应的功能方法
接下来看看工具条的配置:
const toolbar = {
base: [
{
key: 'group',
icon: ,
text: '成组',
},
{
key: 'ungroup',
icon: ,
text: '取消成组'
},
{
key: 'left',
icon: ,
text: '左对齐'
},
// ... 其他工具条配置
{
key: 'v-space',
icon: ,
text: '垂直分布空间'
},
{
key: 'h-space',
icon: ,
text: '水平分布空间'
},
]
}
工具条方法封装:
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 {
child.request("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 {
child.request("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 {
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("draggable", {
x: left,
}, true);
left += rect.width + gap;
});
moveableRef.current?.updateRect();
return
}
}
通过以上的封装方式我们就能轻松扩展自己的工具条啦~
接下来我们看看工具条实现的效果:
图片
当然代码我已经提交到 github 上了, 大家感兴趣可以参考研究一下。
开源地址:https://github.com/MrXujiang/next-admin
多选 & 成组实现
图片
下面直接上代码:
{
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;
}}
notallow={e => {
const {
startAdded,
startRemoved,
isDragStartEnd,
} = e;
if (isDragStartEnd) {
return;
}
const nextChilds = groupManager.selectSameDepthChilds(
e.data.startTargets,
startAdded,
startRemoved,
);
setSelectedTargets(nextChilds.targets());
}}
notallow={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());
}}
>
完整代码都同步到 Next-Admin 了, 如果大家感兴趣也可以研究一下。