import Components from '@/components' import ElementPlus from 'element-plus' import * as ElementPlusIcons from '@element-plus/icons-vue' import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import { HtmlResize } from '@logicflow/extension' import { h as lh } from '@logicflow/core' import { createApp, h } from 'vue' import directives from '@/directives' import i18n from '@/locales' import { WorkflowType } from '@/enums/workflow' import { nodeDict } from '@/workflow/common/data' class AppNode extends HtmlResize.view { isMounted r app constructor(props: any, VueNode: any) { super(props) this.isMounted = false this.r = h(VueNode, { properties: props.model.properties, nodeModel: props.model }) this.app = createApp({ render: () => this.r }) this.app.use(ElementPlus, { locale: zhCn }) this.app.use(Components) this.app.use(directives) this.app.use(i18n) for (const [key, component] of Object.entries(ElementPlusIcons)) { this.app.component(key, component) } if (props.model.properties.noRender) { delete props.model.properties.noRender } else { const filterNodes = props.graphModel.nodes.filter((v: any) => v.type === props.model.type) if (filterNodes.length - 1 > 0) { getNodesName(filterNodes.length - 1) } } function getNodesName(num: number) { let number = num const name = props.model.properties.stepName + number if (!props.graphModel.nodes?.some((node: any) => node.properties.stepName === name.trim())) { props.model.properties.stepName = name } else { number += 1 getNodesName(number) } } props.model.properties.config = nodeDict[props.model.type].properties.config if (props.model.properties.height) { props.model.height = props.model.properties.height } } getAnchorShape(anchorData: any) { const { x, y, type } = anchorData let isConnect = false if (type == 'left') { isConnect = this.props.graphModel.edges.some((edge) => edge.targetAnchorId == anchorData.id) } else { isConnect = this.props.graphModel.edges.some((edge) => edge.sourceAnchorId == anchorData.id) } return lh( 'foreignObject', { ...anchorData, x: x - 10, y: y - 12, width: 30, height: 30 }, [ lh('div', { style: { zindex: 0 }, onClick: () => { if (type == 'right') { this.props.model.openNodeMenu(anchorData) } }, dangerouslySetInnerHTML: { __html: isConnect ? ` ` : ` ` } }) ] ) } setHtml(rootEl: HTMLElement) { if (!this.isMounted) { this.isMounted = true const node = document.createElement('div') rootEl.appendChild(node) this.app?.mount(node) } else { if (this.r && this.r.component) { this.r.component.props.properties = this.props.model.getProperties() } } } } class AppNodeModel extends HtmlResize.model { refreshDeges() { // 更新节点连接边的path this.incoming.edges.forEach((edge: any) => { // 调用自定义的更新方案 edge.updatePathByAnchor() }) this.outgoing.edges.forEach((edge: any) => { edge.updatePathByAnchor() }) } set_position(position: { x?: number; y?: number }) { const { x, y } = position if (x) { this.x = x } if (y) { this.y = y } this.refreshDeges() } getResizeOutlineStyle() { const style = super.getResizeOutlineStyle() style.stroke = 'none' return style } getControlPointStyle() { const style = super.getControlPointStyle() style.stroke = 'none' style.fill = 'none' return style } getNodeStyle() { return { overflow: 'visible' } } getOutlineStyle() { const style = super.getOutlineStyle() style.stroke = 'none' if (style.hover) { style.hover.stroke = 'none' } return style } // 如果不用修改锚地形状,可以重写颜色相关样式 getAnchorStyle(anchorInfo: any) { const style = super.getAnchorStyle(anchorInfo) if (anchorInfo.type === 'left') { style.fill = 'red' style.hover.fill = 'transparent' style.hover.stroke = 'transpanrent' style.className = 'lf-hide-default' } else { style.fill = 'green' } return style } setHeight(height: number) { const sourceHeight = this.height const targetHeight = height + 100 this.height = targetHeight this.properties['height'] = targetHeight this.move(0, (targetHeight - sourceHeight) / 2) this.outgoing.edges.forEach((edge: any) => { // 调用自定义的更新方案 edge.updatePathByAnchor() }) this.incoming.edges.forEach((edge: any) => { // 调用自定义的更新方案 edge.updatePathByAnchor() }) } get_width() { return this.properties?.width || 340 } setAttributes() { this.width = this.get_width() const isLoop = (node_id: string, target_node_id: string) => { const up_node_list = this.graphModel.getNodeIncomingNode(node_id) for (const index in up_node_list) { const item = up_node_list[index] if (item.id === target_node_id) { return true } else { const result = isLoop(item.id, target_node_id) if (result) { return true } } } return false } const circleOnlyAsTarget = { message: '只允许从右边的锚点连出', validate: (sourceNode: any, targetNode: any, sourceAnchor: any) => { return sourceAnchor.type === 'right' } } this.sourceRules.push({ message: '不可循环连线', validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => { return !isLoop(sourceNode.id, targetNode.id) } }) this.sourceRules.push(circleOnlyAsTarget) this.targetRules.push({ message: '只允许连接左边的锚点', validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => { return targetAnchor.type === 'left' } }) } getDefaultAnchor() { const { id, x, y, width } = this const showNode = this.properties.showNode === undefined ? true : this.properties.showNode const anchors: any = [] if (this.type !== WorkflowType.Base) { if (this.type !== WorkflowType.Start) { anchors.push({ x: x - width / 2 + 10, y: showNode ? y : y - 15, id: `${id}_left`, edgeAddable: false, type: 'left' }) } anchors.push({ x: x + width / 2 - 10, y: showNode ? y : y - 15, id: `${id}_right`, type: 'right' }) } return anchors } } export { AppNodeModel, AppNode }