本章主要讲解属性栏的设计与开发
如图一,不同节点有不同的属性数据
图一
那如何去设计这个属性界面,在保证美观的前提下,还要满足下面的要求
- 代码复用性
- 代码可维护性
- 功能可扩展性
首先要说明一点,我们的画布组件和属性栏组件之间,传递消息不能直接调用,消息的传递要通过主进程,熟悉electron的同学应该知道
如果画布组件和消息栏组件是通过pinia直接进行消息传递,虽然很方便,但是会导致一个问题,就是一旦属性栏变成一个独立的窗口,那么消息通信将直接失效,因为不同的窗口之间要想进行消息传递,必须通过主进程,这是很重要的一点,后面还有很多组件相互通信也是通过主进程
设计思路
- 主进程开发两个接口,一个接收消息接口,一个发送消息接口
- 在画布组件中,我们就需要对显示属性消息进行一个统一的处理,将这些消息分成不同的type
- 在属性栏组件中,我们就要去监听主进程发送消息的接口
- 根据消息里面不同的type,再显示不同的组件
消息传递如图二
图二
接下来我们从代码层面看一下如何实现
代码层面
发消息
比如说我们要显示画布属性或者说节点的属性,在我们点击画布时,就需要做出相应的处理,在前面的章节中,我已经讲过,画布实例有很多内置的监听事件
//画布-点击事件
canvas.on('blank:click', () => {
//打开画布属性面板
renderShowCanvasProperty()
})
然后进行简单的处理
//显示画布属性
export const renderShowCanvasProperty = (): void => {
const canvasProperties = window.api.handleCanvasData('getCanvasProperties', 'canvas2D', false, [])
const data = {
title: '画布属性',
id: generateUUID(),
type: 'canvas',
data: canvasProperties
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
这一行代码就是向主进程发送消息,当然除了显示画布属性外,我们还可以显示其他元素的属性,比如说
//显示节点属性
export const renderShowNodeProperty = (
node: Node,
type: 'node' | 'freeEdit' | 'plaintext' = 'node'
): void => {
const nodeProperties = window.api.handleCanvasData('getNodes', 'canvas2D', false, ['id', node.id])
const data = {
title: '节点属性',
id: generateUUID(),
type: type,
data: nodeProperties[0]
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
}
//显示节点批量编辑属性
export const renderNodeBulkEdit = (): void => {
const data = {
title: '批量节点',
id: generateUUID(),
type: 'bulkNode'
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
}
//显示边属性
export const renderShowEdgeProperty = (edge: Edge): void => {
const edgeProperties = window.api.handleCanvasData('getEdges', 'canvas2D', false, ['id', edge.id])
if (edge.shape === 'conveyor-belt') {
const data = {
title: '边属性',
id: generateUUID(),
type: 'conveyor',
data: edgeProperties[0]
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
} else if (edge.shape === 'road-edge') {
const data = {
title: '边属性',
id: generateUUID(),
type: 'road',
data: edgeProperties[0]
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
} else {
const data = {
title: '边属性',
id: generateUUID(),
type: 'edge',
data: edgeProperties[0]
}
window.electron.ipcRenderer.send('openAttributeWindow', data)
}
}
这些方法无一例外都是通过openAttributeWindow接口向主进程发送消息,然后我们在主进程中看看怎么接收和发送
中间代理
代码如下
// 监听打开属性栏窗口的事件, 拿到数据不做处理,直接发给属性栏窗口
ipcMain.on('openAttributeWindow', (e, data) => {
mainWindow.webContents.send('attributeWindowData', data)
})
代码只有三行,也就是收到消息后,再发出去,不需要做任何处理,那么在我们的属性栏组件中,就需要去监听attributeWindowData接口发过来的消息
属性栏组件
我们需要在属性栏生命周期钩子函数里面注册一个监听函数
onMounted(() => {
//监听主进程的属性栏数据处理事件
window.electron.ipcRenderer.on('attributeWindowData', (e, data) => {
switchAttribute(data)
})
})
然后对于发过来的消息,我们这样处理
interface type {
title: string
id: string
type:
| 'canvas'
| 'node'
| 'edge'
| 'conveyor'
| 'road'
| 'bulkNode'
| 'freeEdit'
| 'plaintext'
| null
data: any
}
const attributeData = ref<type>({
title: '模型属性',
id: '',
type: null,
data: {}
})
//在这里判断是否需要切换数据或是隐藏
const switchAttribute = (data: any): void => {
console.log(data)
if (isPushpin.value) {
//这里是固钉状态,然后判断传进来的type如果是null
if (data.type === null && data.id === attributeData.value.id) {
attributeData.value = data
}
} else {
attributeData.value = data
}
}
然后再template里面就可以直接判断type的类型来显示哪一部分
<template>
<div
class="property-home"
:style="{
backgroundColor: themeStyle[theme].backgroundColor1,
borderLeft: `1px solid ${themeStyle[theme].borderColor1}`
}"
>
<div
class="property-home-title"
:style="{
backgroundColor: themeStyle[theme].backgroundColor2,
color: themeStyle[theme].textColor3
}"
>
<span>属性</span>
<div class="icon-group">
<div class="pushpin" :class="isPushpin ? 'pushpinActive' : ''" @click="switchPushpin">
<svg-icon
name="fixedNail"
size="1em"
:color="themeStyle[theme].textColor1"
class="icon"
></svg-icon>
</div>
<svg-icon
name="funClose"
size="0.9em"
:color="themeStyle[theme].textColor1"
class="icon"
@click="closeAttribute"
></svg-icon>
</div>
</div>
<template v-if="attributeData.type === 'canvas'">
<CanvasProperty :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'node'">
<NodeProTem :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'edge'">
<EdgeProTem :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'conveyor'">
<ConveyorProperty :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'road'">
<RoadProperty :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'bulkNode'">
<BulkNode :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'freeEdit'">
<FreeEdit :data="attributeData.data" />
</template>
<template v-if="attributeData.type === 'plaintext'">
<Plaintext :data="attributeData.data" />
</template>
</div>
</template>
<script setup lang="ts">
import { useStyleStore } from '@renderer/store/globalStyle/style'
import { storeToRefs } from 'pinia'
import { onMounted, watch, ref } from 'vue'
import CanvasProperty from './canvasPro/CanvasProperty.vue'
import NodeProTem from './nodePro/NodeProTem.vue'
import ConveyorProperty from './conveyorPro/ConveyorProperty.vue'
import BulkNode from './nodePro/BulkNode.vue'
import FreeEdit from './nodePro/FreeEdit.vue'
import EdgeProTem from './edgePro/EdgeProTem.vue'
import RoadProperty from './roadPro/RoadProperty.vue'
import Plaintext from './nodePro/Plaintext.vue'