前文
这篇相对来讲就稍微平凡了一点,只要有前端的一些基础就能够轻松完成上图中左侧的菜单,但是为了能够让前后文章能够连贯起来,所以还是要厚着脸皮再写一篇。
有人可能要问了,为啥不将图中的功能完全实现呢,那是因为会直接导致篇幅过长,不利于阅读,思路不够清晰。
下一章节将实现 自定义节点
。
LogicFlow 介绍
LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端研发自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。
实现基础界面框架
<template>
<div class="demo-wrp">
<div class="demo-nemu"></div>
<div class="demo-container">
<div class="demo-control-wrp"></div>
<div class="demo-canvas-wrp" ref="diagram"></div>
</div>
</div>
</template>
<script>
// 引入LogicFlow核心包
import LogicFlow from '@logicflow/core';
import '@logicflow/core/dist/style/index.css';
export default ({
name: 'demo',
data () {
return {
// 模拟数据
mockData: {
nodes: [
{ id: "1", type: "rect", x: 100, y: 100, text: "节点1" },
{ id: "2", type: "circle", x: 300, y: 100, text: "节点2" },
],
edges: [
{
sourceNodeId: "1",
targetNodeId: "2",
type: "polyline",
text: "连线",
startPoint: {
x: 140,
y: 100,
},
endPoint: {
x: 250,
y: 100,
},
},
],
}
}
},
mounted () {
// 创建实例
const lf = new LogicFlow({
container: this.$refs.diagram,
// ... 其他的一些配置
})
/*
开启渲染
如果不要模拟数据,直接使用 lf.render() 即可。
**/
lf.render(this.mockData);
// 渲染到视图中心为止,否则在左上角显示
lf.translateCenter();
}
})
</script>
<style scoped lang="less">
// 定义操控区域高度
@HEADER_HEIGHT: 50px;
// 定义菜单和操控区域背景色
@BG_COLOR: #f3f3f3;
.demo-wrp{
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-items: flex-start;
justify-content: space-between;
.demo-nemu{
width: 200px;
height: 100%;
overflow-y: auto;
background-color: @BG_COLOR;
border-right: 1px solid #ddd;
&::-webkit-scrollbar {
width: 4px;
background-color: #eeeeee;
}
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.01);
border-radius: 0;
background: rgba(0, 0, 0, 0);
}
&::-webkit-scrollbar-thumb {
border-radius: 40px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
background: rgba(0, 0, 0, 0);
}
}
.demo-container{
flex: 1;
height: 100%;
background-color: @BG_COLOR;
.demo-control-wrp{
width: 100%;
height: @HEADER_HEIGHT;
display: flex;
align-items: center;
justify-content: flex-end;
border-bottom: 1px solid #ddd;
padding: 0 20px;
}
.demo-canvas-wrp{
width: 100%;
height: calc(100% - @HEADER_HEIGHT);
}
}
}
</style>
实现上面的界面布局还是相当简单的,如果对样式感觉很困惑,那就只有多敲多练了。
另外根据如上代码在 vscode 中,样式部分可能会标红,不影响使用,代码也是生效的。
如果觉得看着不顺眼那也很简单,只需要在同级目录下创建一个 less
格式的文件,然后把样式拷贝进去,再将这个 less
文件引入到当前使用的组件中,如下所示:
<style lang="less" scoped>
@import url('./index.less');
</style>
实现左侧菜单组件
基础的界面框架已经搭建好了,那么接下来就要实现左侧菜单了,为了让界面结构逻辑更加的清晰,建议是将菜单部分单独作为一个组件进行使用。
在 demo 目录下创建 components 目录,专门用来处理 LogicFlow 相关的组件,到时候如需其他项目也要使用的时候,直接拷贝过去就能投产,极大便利降低使用成本。
<template>
<ul class="diagram-sidebar">
<li v-for="groupItem in leftDataList" :key="groupItem.key">
<h1 class="node-category-title">{{ groupItem.groupName }}</h1>
<template v-if="Array.isArray(groupItem.nodes) && groupItem.nodes.length > 0">
<div
v-for="item in groupItem.nodes"
:key="item.type"
class="left-bar-item"
:class="getClassFn"
:style="getItemStyleFn(item)"
@mousedown="dragInNode(item.type)"
>
<img class="icon" :src="item.icon" alt="">
<p class="name" :style="`color:${item.borderColor};`">{{item.name}}</p>
</div>
</template>
</li>
</ul>
</template>
<script>
import { InitNodeData, CanNotEdit } from './NodeEnum';
export default ({
name: 'leftMenu',
props: {
taskDetail: Object,
},
data() {
return {
leftDataList: [
{
groupName: '分组-1',
key: 'marketing',
nodes: [
{
icon: require('../assets/lfCanvas/leftIcon/singleCrowd.svg'),
type: 'SINGLE_CROWD',
borderColor: 'rgba(71, 98, 254, 1)',
backgroundColor: 'rgba(71, 98, 254, 0.1)',
name: '单个信息',
},
{
icon: require('../assets/lfCanvas/leftIcon/batchCrowd.svg'),
type: 'BATCH_CROWD',
borderColor: 'rgba(71, 98, 254, 1)',
backgroundColor: 'rgba(71, 98, 254, 0.1)',
name: '批量信息',
},
{
icon: require('../assets/lfCanvas/leftIcon/ruleCrowd.svg'),
type: 'RULE_CROWD',
borderColor: 'rgba(71, 98, 254, 1)',
backgroundColor: 'rgba(71, 98, 254, 0.1)',
name: '信息规则',
},
],
},
{
groupName: '分组-2',
key: 'target',
nodes: [
{
icon: require('../assets/lfCanvas/leftIcon/goalCrowd.svg'),
type: 'GOAL_CROWD',
borderColor: 'rgba(64, 158, 255, 1)',
backgroundColor: 'rgba(64, 158, 255, 0.1)',
name: '目标',
},
],
},
{
groupName: '分组-3',
key: 'compute',
nodes: [
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-merge.svg'),
type: 'MERGE',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '合并',
},
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-split.svg'),
type: 'SPLIT',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '拆分',
},
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-unique.svg'),
type: 'UNIQUE',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '排重',
},
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-intersection.svg'),
type: 'AND',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '交集',
},
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-exclude.svg'),
type: 'EXCLUDE',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '排除',
},
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-spread.svg'),
type: 'SPREAD',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '智能扩散',
},
{
icon: require('../assets/lfCanvas/leftIcon/compute/icon-compute-sort.svg'),
type: 'SORT',
borderColor: 'rgba(255, 180, 30, 1)',
backgroundColor: 'rgba(255, 180, 30, 0.15)',
name: '高能排序',
},
],
},
{
groupName: '分组-4',
key: 'touch',
nodes: [
{
icon: require('../assets/lfCanvas/leftIcon/dongdong.svg'),
type: 'DONGDONG',
borderColor: 'rgba(0, 200, 100, 1)',
backgroundColor: 'rgba(0, 200, 100, 0.1)',
name: '消息',
},
{
icon: require('../assets/lfCanvas/leftIcon/sms.svg'),
type: 'SMS',
borderColor: 'rgba(0, 200, 100, 1)',
backgroundColor: 'rgba(0, 200, 100, 0.1)',
name: '短信',
},
],
},
],
}
},
computed: {
// 拖拽和禁用拖拽按钮状态
getClassFn(){
return this.canDrag ? 'cursor-not-allowed' : 'cursor-pointer'
},
// 判断是否可被拖拽(先注释)
canDrag() {
// return this.canvasType === 'detail' || CanNotEdit.includes(this.taskDetail?.canvasStatus?.toString());
},
canvasType() {
const QUERY_TYPE = this.$router.history.current.query.type;
console.log('QUERY_TYPE::', QUERY_TYPE);
return QUERY_TYPE;
},
},
mounted() {
},
methods: {
getMenuIconObj(item) {
return {
icon: item.icon,
name: item.name
}
},
// 动态设置样式
getItemStyleFn(item){
return `border: 1px solid ${item.borderColor};background-color:${item.backgroundColor};`
},
// 数据按下事件,主要是获取菜单的type类型
dragInNode(type) {
const that = this;
console.log('dragInNode', that.canDrag, that.taskDetail);
if (that.canDrag) return;
// that.$emit('dragInNode', {
// type,
// properties: {
// ...InitNodeData[type],
// },
// });
},
}
})
</script>
<style lang="less" scoped>
.diagram-sidebar {
user-select: none;
flex-basis: 200px;
padding: 13px 25px;
.node-category-title {
font-family: PingFang SC;
font-size: 14px;
font-weight: 600;
line-height: 22px;
text-align: left;
margin-bottom: 12px;
}
.cursor-pointer {
cursor: pointer;
}
.cursor-not-allowed {
cursor: not-allowed;
}
.left-bar-item{
width: 140px;
height: 38px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
margin-bottom: 12px;
padding-left: 24px;
.icon{
width: 16px;
height: 16px;
}
.name{
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 22px;
text-align: left;
}
}
}
</style>
实现效果如下:
这样就实现了一个自定义的左侧菜单,这样可以根据自己的需要手动引入到对应的组件中进行使用,但是需要注意一点的是在 script标签
中引入的外部文件是否存在,地址是否正确。
将左侧菜单引入到demo组件中
<template>
<div class="demo-wrp">
<div class="demo-nemu">
<LeftMenu :taskDetail="taskDetail" />
</div>
<div class="demo-container">
<div class="demo-control-wrp"></div>
<div class="demo-canvas-wrp" ref="diagram"></div>
</div>
</div>
</template>
<script>
// 引入LogicFlow核心包
import LogicFlow from '@logicflow/core';
import '@logicflow/core/dist/style/index.css';
// 引入组件
import LeftMenu from './components/LeftMenu';
export default ({
name: 'demo',
data () {
return {
// ...
taskDetail: {},
// ...
}
},
components: {
LeftMenu,
},
// 其他...
})
</script>
最后
关于自定义左侧菜单到这里也就结束了,只要会点 Vue 都能够看懂,一点都不难,所以也没啥好讲的。
下一篇将讲解如何将左侧的按钮拖拽到右侧的画布。
这一篇到此结束,拜拜。