提交 85061199 authored 作者: lihuihui's avatar lihuihui

update

上级 4e562a2e
...@@ -7,7 +7,14 @@ ...@@ -7,7 +7,14 @@
"$ref": true, "$ref": true,
"$shallowRef": true, "$shallowRef": true,
"$toRef": true, "$toRef": true,
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true, "EffectScope": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"asyncComputed": true, "asyncComputed": true,
"autoResetRef": true, "autoResetRef": true,
"computed": true, "computed": true,
...@@ -80,7 +87,6 @@ ...@@ -80,7 +87,6 @@
"refThrottled": true, "refThrottled": true,
"refWithControl": true, "refWithControl": true,
"resolveComponent": true, "resolveComponent": true,
"resolveDirective": true,
"resolveRef": true, "resolveRef": true,
"resolveUnref": true, "resolveUnref": true,
"shallowReactive": true, "shallowReactive": true,
...@@ -113,6 +119,7 @@ ...@@ -113,6 +119,7 @@
"useArrayMap": true, "useArrayMap": true,
"useArrayReduce": true, "useArrayReduce": true,
"useArraySome": true, "useArraySome": true,
"useArrayUnique": true,
"useAsyncQueue": true, "useAsyncQueue": true,
"useAsyncState": true, "useAsyncState": true,
"useAttrs": true, "useAttrs": true,
...@@ -196,12 +203,14 @@ ...@@ -196,12 +203,14 @@
"useParallax": true, "useParallax": true,
"usePermission": true, "usePermission": true,
"usePointer": true, "usePointer": true,
"usePointerLock": true,
"usePointerSwipe": true, "usePointerSwipe": true,
"usePreferredColorScheme": true, "usePreferredColorScheme": true,
"usePreferredContrast": true, "usePreferredContrast": true,
"usePreferredDark": true, "usePreferredDark": true,
"usePreferredLanguages": true, "usePreferredLanguages": true,
"usePreferredReducedMotion": true, "usePreferredReducedMotion": true,
"usePrevious": true,
"useRafFn": true, "useRafFn": true,
"useRefHistory": true, "useRefHistory": true,
"useResizeObserver": true, "useResizeObserver": true,
......
...@@ -81,7 +81,6 @@ declare global { ...@@ -81,7 +81,6 @@ declare global {
const refThrottled: typeof import('@vueuse/core')['refThrottled'] const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl'] const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent'] const resolveComponent: typeof import('vue')['resolveComponent']
const resolveDirective: typeof import('vue')['resolveDirective']
const resolveRef: typeof import('@vueuse/core')['resolveRef'] const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReactive: typeof import('vue')['shallowReactive']
...@@ -114,6 +113,7 @@ declare global { ...@@ -114,6 +113,7 @@ declare global {
const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome'] const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue')['useAttrs']
...@@ -197,12 +197,14 @@ declare global { ...@@ -197,12 +197,14 @@ declare global {
const useParallax: typeof import('@vueuse/core')['useParallax'] const useParallax: typeof import('@vueuse/core')['useParallax']
const usePermission: typeof import('@vueuse/core')['usePermission'] const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer'] const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
...@@ -271,3 +273,8 @@ declare global { ...@@ -271,3 +273,8 @@ declare global {
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever'] const whenever: typeof import('@vueuse/core')['whenever']
} }
// for type re-export
declare global {
// @ts-ignore
export type { Component,ComponentPublicInstance,ComputedRef,InjectionKey,PropType,Ref,VNode } from 'vue'
}
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.0.10",
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
"@vue-flow/additional-components": "^1.3.0",
"@vue-flow/core": "^1.5.0",
"@vueuse/core": "^9.12.0", "@vueuse/core": "^9.12.0",
"axios": "^1.3.3", "axios": "^1.3.3",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
...@@ -17,7 +19,9 @@ ...@@ -17,7 +19,9 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.0.30", "pinia": "^2.0.30",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-router": "^4.1.6" "vue-resizable": "^2.1.5",
"vue-router": "^4.1.6",
"vue3-smooth-dnd": "^0.0.2"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.2.0", "@rushstack/eslint-patch": "^1.2.0",
...@@ -1412,6 +1416,33 @@ ...@@ -1412,6 +1416,33 @@
"@volar/vue-language-core": "1.0.24" "@volar/vue-language-core": "1.0.24"
} }
}, },
"node_modules/@vue-flow/additional-components": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/@vue-flow/additional-components/-/additional-components-1.3.3.tgz",
"integrity": "sha512-AZhz0diM7VIN7MGKODiuqiu+xiujFQSs2UdiThgNI5vGSwwizd0g9dGzB+LK0Dt4FCRJ1g64xzxqbrAFFfzuFw==",
"dependencies": {
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
},
"peerDependencies": {
"@vue-flow/core": "^1.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue-flow/core": {
"version": "1.14.3",
"resolved": "https://registry.npmmirror.com/@vue-flow/core/-/core-1.14.3.tgz",
"integrity": "sha512-KvqKH9o17coY33LSuQ0xaHFycPkQF0CTL/+FPNUZtQlrkFIrEliWvqbDEV82HGrcdeF7r0voJ69O+htoNUWpSA==",
"dependencies": {
"@vueuse/core": "^9.11.0",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
},
"peerDependencies": {
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.2.47", "version": "3.2.47",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz", "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
...@@ -2133,6 +2164,102 @@ ...@@ -2133,6 +2164,102 @@
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
}, },
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/data-uri-to-buffer": { "node_modules/data-uri-to-buffer": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
...@@ -5232,6 +5359,11 @@ ...@@ -5232,6 +5359,11 @@
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
} }
}, },
"node_modules/smooth-dnd": {
"version": "0.12.1",
"resolved": "https://registry.npmmirror.com/smooth-dnd/-/smooth-dnd-0.12.1.tgz",
"integrity": "sha512-Dndj/MOG7VP83mvzfGCLGzV2HuK1lWachMtWl/Iuk6zV7noDycIBnflwaPuDzoaapEl3Pc4+ybJArkkx9sxPZg=="
},
"node_modules/socks": { "node_modules/socks": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmmirror.com/socks/-/socks-2.7.1.tgz", "resolved": "https://registry.npmmirror.com/socks/-/socks-2.7.1.tgz",
...@@ -6030,6 +6162,11 @@ ...@@ -6030,6 +6162,11 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/vue-resizable": {
"version": "2.1.7",
"resolved": "https://registry.npmmirror.com/vue-resizable/-/vue-resizable-2.1.7.tgz",
"integrity": "sha512-zEbWhRR8iXT8+nt3u8rkfrNpkPNsPkf7HteBh+AlPIsJ7rf9fyNwMqr0Q4FRzIpNIpZD5Zrr4+3+YELU0vc1Iw=="
},
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.1.6", "version": "4.1.6",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz",
...@@ -6067,6 +6204,17 @@ ...@@ -6067,6 +6204,17 @@
"typescript": "*" "typescript": "*"
} }
}, },
"node_modules/vue3-smooth-dnd": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/vue3-smooth-dnd/-/vue3-smooth-dnd-0.0.2.tgz",
"integrity": "sha512-5OlpoZ1fmpA1CcjvRwJ9P3MEPZ0nnnYuIf/9zMGKF8i7jTE6fQX3oeY0jDHNtOjuCmvTSbc0AYq18M9QIF32Ew==",
"dependencies": {
"smooth-dnd": "^0.12.1"
},
"peerDependencies": {
"vue": "^3.0.11"
}
},
"node_modules/webpack-sources": { "node_modules/webpack-sources": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz",
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
"cert": "node ./cert.js" "cert": "node ./cert.js"
}, },
"dependencies": { "dependencies": {
"@vue-flow/additional-components": "^1.3.0",
"@vue-flow/core": "^1.5.0",
"vue3-smooth-dnd": "^0.0.2",
"vue-resizable": "^2.1.5",
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.0.10",
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^9.12.0", "@vueuse/core": "^9.12.0",
......
<template>
<section>
<div class="flow-left">
<slot name="left" />
</div>
<div class="flow-main"></div>
<div class="flow-left">
<slot name="left" />
</div>
</section>
</template>
<script setup lang="ts">
import { BaseEdge, EdgeLabelRenderer, getBezierPath, useVueFlow } from '@vue-flow/core'
import { Delete, Setting } from '@element-plus/icons-vue'
const props: any = defineProps({
id: {
type: String,
required: true
},
sourceX: {
type: Number,
required: true
},
sourceY: {
type: Number,
required: true
},
targetX: {
type: Number,
required: true
},
targetY: {
type: Number,
required: true
},
sourcePosition: {
type: String,
required: true
},
targetPosition: {
type: String,
required: true
},
data: {
type: Object,
required: false
},
markerEnd: {
type: String,
required: false
},
style: {
type: Object,
required: false
}
})
const { applyEdgeChanges } = useVueFlow()
const onClick = (evt: any, id: any) => {
applyEdgeChanges([{ type: 'remove', id }])
evt.stopPropagation()
}
const getRightLefttEdge = (props: any) => {
return [
`
M ${props.sourceX - 7.5} ${props.sourceY}
L ${props.sourceX + 15} ${props.sourceY}
Q ${props.sourceX + 30} ${props.sourceY}
${props.sourceX + 30} ${props.sourceY + (props.sourceY > props.targetY ? -15 : +15)}
L ${props.sourceX + 30} ${props.targetY - (props.sourceY > props.targetY ? 45 : 75)}
Q ${props.sourceX + 30} ${props.targetY - 60}
${props.sourceX + 15} ${props.targetY - 60}
L ${props.targetX - 30} ${props.targetY - 60}
Q ${props.targetX - 45} ${props.targetY - 60}
${props.targetX - 45} ${props.targetY - 45}
L ${props.targetX - 45} ${props.targetY - 15}
Q ${props.targetX - 45} ${props.targetY}
${props.targetX - 30} ${props.targetY}
L ${props.targetX} ${props.targetY}`,
(props.sourceX + props.targetX) / 2,
props.targetY - 60
]
}
const getBottomLeftEdge = (props: any) => {
const countQ: any = props.sourceY < props.targetY - 30 ? 45 : 15
const countL: any = props.sourceY < props.targetY - 30 ? -15 : 15
return [
`
M ${props.sourceX} ${props.sourceY - 7.5}
L ${props.sourceX} ${props.sourceY + 15}
Q ${props.sourceX} ${props.sourceY + 30}
${props.sourceX - 15} ${props.sourceY + 30}
L ${props.targetX - 15} ${props.sourceY + 30}
Q ${props.targetX - 30} ${props.sourceY + 30}
${props.targetX - 30} ${props.sourceY + parseInt(countQ)}
L ${props.targetX - 30} ${props.targetY + parseInt(countL)}
Q ${props.targetX - 30} ${props.targetY}
${props.targetX - 15} ${props.targetY}
L ${props.targetX} ${props.targetY}
`,
props.targetX - 15,
props.targetY
]
}
const getLeftLeftEdge = (props: any) => {
return [
`M ${props.sourceX}, ${props.sourceY}
C ${props.targetX}, ${props.sourceY}
${props.targetX}, ${props.targetY}
${props.targetX}, ${props.targetY}`,
props.sourceX - 25,
props.sourceY
]
}
const getCurvedEdge = (props: any, margin = 8) => {
return [
`M${props.sourceX + margin}, ${props.sourceY} C ${props.sourceX} ${props.targetY} ${props.sourceX} ${
props.targetY
} ${props.targetX}, ${props.targetY}`,
(props.sourceX + props.targetX) / 2,
props.targetY
]
}
const getDirectLine = (props: any) => {
return [
`M ${props.sourceX} ${props.sourceY} L ${props.targetX} ${props.targetY}`,
(props.sourceX + props.targetX) / 2,
(props.sourceY + props.targetY) / 2
]
}
const path = computed(() => {
/* Q1, Q2, Q3, Q4 stands for Quandrants, the plane is divided by 4 zones.
Primary axis are vertical and horizontal axis passing through the point (sourceX, sourceY)*/
const [Q1, Q2, Q3, Q4] = [
props.sourceX < props.targetX && props.sourceY > props.targetY,
props.sourceX > props.targetX && props.sourceY > props.targetY,
props.sourceX > props.targetX && props.sourceY < props.targetY,
props.sourceX < props.targetX && props.sourceY < props.targetY
]
if (props.sourcePosition === 'left') {
if (props.targetPosition === 'left') {
if (Q2) {
return getLeftLeftEdge(props)
}
} else if (props.targetPosition === 'right') {
return getBezierPath(props)
}
return getCurvedEdge(props)
} else if (props.sourcePosition === 'right') {
if (props.targetPosition === 'left') {
if (/right-redirector/.test(props.id) && (Q2 || Q3)) {
// Redirector Edge
return getRightLefttEdge(props)
}
}
return getBezierPath(props)
} else if (props.sourcePosition === 'bottom') {
if (props.targetPosition === 'left') {
if (Q3 || Q2) {
return getBottomLeftEdge(props)
}
} else if (props.targetPosition === 'right') {
} else if (props.targetPosition === 'bottom') {
} else {
}
return getCurvedEdge(props, 0)
}
return getDirectLine(props)
})
let strokeColor: any = ref('')
let condition = $ref('1')
const handleCondition = function () {
const color = {
'1': 'red',
'2': 'green',
'3': 'yellow'
}
strokeColor.value = color[condition as '1']
}
</script>
<template>
<BaseEdge
:id="id"
:style="{
'stroke-width': 1,
stroke: strokeColor
}"
:path="path[0]"
marker-end="url(#triangle)"
markerWidth="1"
/>
<EdgeLabelRenderer>
<div
:style="{
pointerEvents: 'all',
position: 'absolute',
transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`,
'z-index': 9999
}"
class="nodrag nopan"
>
<div class="edge__button_delete">
<el-tooltip placement="top">
<template #content>
<div class="pop-box">
<div style="display: flex; align-items: center">
<el-radio-group @change="handleCondition" v-model="condition" class="ml-4">
<el-radio label="1" size="large"></el-radio>
<el-radio label="2" size="large"></el-radio>
<el-radio label="3" size="large">无条件</el-radio>
</el-radio-group>
<el-button style="margin-left: 20px" @click="event => onClick(event, id)">删除</el-button>
</div>
</div>
</template>
<el-icon><Setting /></el-icon>
</el-tooltip>
</div>
</div>
</EdgeLabelRenderer>
</template>
<style scoped>
svg {
transform: translate(-4%, -4%);
}
.edge__button_delete {
display: flex;
justify-content: center;
align-items: center;
border: 2px black solid;
border-radius: 1rem;
padding: 0.1rem;
background-color: #f2f5f7;
}
.edge__button_delete:hover {
transform: scale(1.2);
transition: transform 0.5s 0.1s;
}
</style>
<script setup lang="ts">
import SidebarVue from './Sidebar.vue'
</script>
<template>
<div class="border rounded w-25">
<SidebarVue></SidebarVue>
</div>
</template>
<style scoped>
.file-input label:hover {
transform: scale(1.02);
}
.file-input label {
display: block;
position: relative;
width: auto;
height: 3rem;
border-radius: 2rem;
padding: 1rem;
background: linear-gradient(40deg, #297cbc, #16d462);
box-shadow: 0 4px 7px rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
transition: transform 0.2s ease-out;
overflow: hidden;
}
.file {
opacity: 0%;
width: 100%;
height: 100%;
position: absolute;
cursor: pointer;
}
.btn {
font-size: small;
margin: 2px;
}
.btn:hover {
background-color: #eee;
}
.options {
display: flex;
flex-direction: column;
align-items: center;
border-radius: 1rem;
margin: 0.2rem;
padding: 0.5rem;
}
</style>
<script setup lang="ts">
// Icons
import ConnectionIcon from '@/components/ConnectionIcon.vue'
const onDragStart = (event: any, nodeType: any) => {
if (event.dataTransfer) {
console.log(nodeType, 'onDragStart')
event.dataTransfer.setData('application/vueflow', nodeType)
event.dataTransfer.effectAllowed = 'move'
}
}
</script>
<template>
<aside>
<div class="sidebar-box">
<!-- Simple Text Custom Template -->
<div class="sidebar-item">
<h3>触发条件</h3>
<div class="icons">
<div class="btn" :draggable="true" @dragstart="onDragStart($event, 'simple-text')">
<ConnectionIcon name="1" />
</div>
</div>
</div>
</div>
</aside>
</template>
<style scoped lang="scss">
.sidebar-box {
width: 300px;
h3 {
text-align: left;
}
.icons {
display: flex;
flex-wrap: wrap;
}
.sidebar-item {
margin-bottom: 30px;
}
.btn {
width: 50px;
height: 50px;
border: 1px solid #000;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin: 10px 10px 0 0;
}
}
</style>
<script setup lang="ts">
import { Handle, Position } from '@vue-flow/core'
import ConnectionIcon from '@/components/ConnectionIcon.vue'
// custom Top Menu import
import TopMenu from './ToolMenu.vue'
// Usage of Store Pinia
import { useStore } from '@/stores/main.js'
const store = useStore()
// Computed Values from Store.
let localStates: any = computed(() => {
return store.getMessageById(props.id)
})
////////////////////////////////////////////.
// Renderless resizable textarea
const textarea = ref(null) // Access the textarea by his ref.
const resizeTextarea = (event: any) => {
event.target.style.height = 'auto'
event.target.style.height = event.target.scrollHeight + 4 + 'px'
}
onMounted(() => {
// textarea.value.style.height = textarea.value.scrollHeight + 'px'
})
////////////////////////////////////////////.
// Watching Selected Manual event.
watch(
() => props.selected,
isSelected => (selectedColor.value = isSelected)
)
////////////////////////////////////////////.
// Local Variables and props related things.
const transparent = ref(true)
let selectedColor = ref(false)
const props = defineProps({
id: String,
selected: Boolean
})
</script>
<template>
<Handle id="right" class="handle" :position="Position.Right" />
<Handle id="left" class="handle" :position="Position.Left" />
<Handle id="bottom" class="handle" :position="Position.Bottom" style="top: 101%" />
<div @mouseenter="transparent = false" @mouseleave="transparent = true">
<div class="top-menu">
<TopMenu :eid="props.id" :transparent="transparent"></TopMenu>
</div>
<div :id="id + 'subtitle'" class="icon-item">
<ConnectionIcon name="1"></ConnectionIcon>
</div>
</div>
</template>
<style scoped lang="scss">
.icon-item {
width: 80px;
height: 80px;
border: 1px solid #000;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.top-menu {
display: flex;
align-items: center;
justify-content: center;
}
</style>
<script setup lang="ts">
import { useVueFlow } from '@vue-flow/core'
import { Setting, Delete } from '@element-plus/icons-vue'
// Icons
// import TrashIcon from '../assets/svg/TrashIcon.svg'
// import GearIcon from '../assets/svg/GearIcon.svg'
// import Check2All from '../assets/svg/check2-all.svg'
// import copyIcon from '../assets/svg/copyIcon.svg'
import { copyVueNode } from '@/utils/createVueNode'
// Usage of Store Pini
import { useStore } from '@/stores/main.js'
const store = useStore()
const { addNodes, applyEdgeChanges, applyNodeChanges, getNode, toObject }: any = useVueFlow()
// Computed Values from Store.
const localStates: any = computed(() => {
return store.getMessageById(props.eid)
})
const messages: any = computed(() => {
return store.getMessages()
})
////////////////////////////////////////////.
// Elements related methods.
const deleteElement = (event: any, id: any) => {
event.stopPropagation()
let connectedEdges = toObject().edges.filter((edge: any) => [edge.target, edge.source].some(item => item === id))
const changeEdgesObjectArray = connectedEdges.map((item: any) => ({
type: 'remove',
id: item.id
}))
applyNodeChanges([{ type: 'remove', id }])
applyEdgeChanges(changeEdgesObjectArray)
store.layers.messages = store.layers.messages.filter(element => {
return element.id !== id
})
}
////////////////////////////////////////////.
// Ref Targeting Hidden Color Input
const colorInput: any = ref(null)
const onClickColorInput = () => {
colorInput.value.click()
}
////////////////////////////////////////////.
// handling container in and out
const hanldeContainer = (e: any) => {
menu.value = !menu.value
function containerParser(containerNodes: any) {
return containerNodes.map((item: any) => {
const label = messages.value.find((element: any) => element.id === item.id).label
return { id: item.id, label: label }
})
}
let containersNodes = toObject().nodes.filter((item: any) => item.type === 'container')
containers.value = containerParser(containersNodes)
let currentObject = toObject().nodes.filter((item: any) => item.id === props.eid)
if (currentObject[0].parentNode) {
parent.value = currentObject[0].parentNode
} else {
parent.value = null
}
}
////////////////////////////////////////////.
// Local Variables and props related things.
const menu = ref(true)
const containers: any = ref(null)
const parent = ref(null)
const props = defineProps({
eid: String,
transparent: Boolean
})
////////////////////////////////////////////.
// Watch over transparent value
watch(
() => props.transparent,
transparent => {
if (transparent === true) {
menu.value = true
}
}
)
////////////////////////////////////////////.
// Set the parent of the current node.
const setParent = (parentId: any) => {
menu.value = true
const node = getNode.value(props.eid)
if (node.parentNode === parentId) {
delete node.parentNode
} else {
if (parentId !== props.eid) {
node.parentNode = parentId
} else {
return
}
}
}
</script>
<template>
<div class="button-menu-container" :class="{ transparent: transparent }">
<div @click="event => deleteElement(event, eid)">
<el-icon><Delete /></el-icon>
</div>
<div style="position: relative" @click.self="hanldeContainer">
<el-icon @click.stop="hanldeContainer"><Setting /></el-icon>
<div :class="{ transparent: menu }" class="menu-container">
<div style="width: 80%; border-bottom: 1px black solid">Parent Container</div>
<ul>
<li
v-for="item in containers"
@click="
() => {
setParent(item.id)
}
"
>
<div
:style="{
width: item.id === parent ? '90%' : '100%'
}"
>
{{ item.label }}
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped>
li div {
transition: opacity 0.5s 0s;
}
/* Button Menu Top Container */
.button-menu-container {
display: flex;
margin-bottom: 0.1rem;
background-color: white;
width: fit-content;
border-radius: 1rem;
overflow: visible;
transition: opacity 1s 0.5s;
}
.button-menu-container > div {
display: flex;
padding: 0.35rem;
}
.button-menu-container > div:hover {
background-color: #eee;
}
/* Button Menu Top Container */
/* Colored Color Input with input Hidden */
.color-input {
width: 0px;
height: 0px;
padding: 0.5rem;
border-radius: 50%;
overflow: hidden;
position: relative;
}
.container-color {
position: absolute;
width: 0;
height: 0;
transform: translate(-50%, -50%);
transform: scale(0);
}
/* Colored Color Input with input Hidden */
.menu-container {
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
background-color: white;
border-radius: 1rem;
width: 15rem;
left: 125%;
font-size: x-small;
border: 1px black solid;
padding: 0.2rem 0.4rem;
}
ul {
width: 100%;
padding: 0;
margin: 0.3rem;
}
li {
display: flex;
border: 1px rgb(162, 159, 159) dashed;
width: 100%;
border-radius: 0.3rem;
padding: 0.3rem;
margin: 0.1rem 0rem;
}
li::marker {
content: none;
}
li:hover {
background-color: #eee;
}
li:active {
transform: scale(1.1);
}
span:hover,
svg:hover {
cursor: pointer;
}
.transparent {
opacity: 0%;
transition: opacity 0.5s 0s;
}
</style>
...@@ -10,6 +10,13 @@ const routes: RouteRecordRaw[] = [ ...@@ -10,6 +10,13 @@ const routes: RouteRecordRaw[] = [
path: '/trip/my', path: '/trip/my',
component: Layout, component: Layout,
children: [{ path: '', component: () => import('./views/Index.vue') }] children: [{ path: '', component: () => import('./views/Index.vue') }]
},
{
path: '/template',
component: Layout,
children: [
{ path: '', component: () => import('./views/Template.vue') }
]
} }
] ]
......
<script setup lang="ts">
import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background, Controls, MiniMap } from '@vue-flow/additional-components'
import SimpleTextVue from '../components/SimpleText.vue'
import GlobalMenu from '../components/GlobalMenu.vue'
// Externalise node creation process on Drop here
import { createVueNode } from '@/utils/createVueNode'
////////////////////////////////////////////.
// Usage of Store Pinia
import { useStore } from '@/stores/main.js'
// Custom Connection line and Custom Edge
import CustomEdgeVue from '../components/CustomEdge.vue'
const store: any = useStore()
const { setInteractive, onConnect, addEdges, addNodes, project, onPaneReady } = useVueFlow()
// Methods that helps, centering the vue.
onPaneReady(({ fitView }) => {
fitView()
})
////////////////////////////////////////////.
// The dragAndDrop function that helps creating new nodes
// Just by dragging elements into the canvas.
// DragOver from the Sidebars.
const onDragOver = (event: any) => {
event.preventDefault()
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
////////////////////////////////////////////.
// The onDrop event handler that is responsible for the creation
const onDrop = (event: any) => {
// console.log(event.target.parentNode);
createVueNode(event, addNodes, project, store)
}
////////////////////////////////////////////.
// OnConnect node event, there is more work to do here.
onConnect((params: any) => {
;(params.type = 'custom'), (params.animated = false)
addEdges([params])
})
////////////////////////////////////////////.
// Handling Clicked message to the message editor
// OnClick : connect message clicked to the message editor.
const onClick = (event: any) => {
if (event.node.type == 'facebook-message') {
if (messageToEdit.value == event.node.id) {
messageToEdit.value = ''
} else {
messageToEdit.value = event.node.id
}
}
store.messageToEdit = messageToEdit.value
}
////////////////////////////////////////////.
// Implementation of a global key listener
let onKeyUp = (event: any) => {
switch (event.key) {
case 'AltGraph':
setInteractive(true)
break
// Close the editor if Escape key is pressed
case ' ':
messageToEdit.value = ''
break
default:
break
}
}
let onKeyDown = (event: any) => {
switch (event.key) {
case 'AltGraph':
setInteractive(false)
break
default:
break
}
}
onMounted(() => {
window.addEventListener('keydown', onKeyDown)
window.addEventListener('keyup', onKeyUp)
})
////////////////////////////////////////////.
// Local Variables and props related things.
let messageToEdit = ref('')
const elements = ref([
{
type: 'simple-text',
dimensions: { width: 100, height: 100 },
handleBounds: {
source: [
{ id: 'right', position: 'right', x: 281.99987873053556, y: 73.5937466308119, width: 16, height: 16 },
{ id: 'left', position: 'left', x: -3.999995328414287, y: 73.5937466308119, width: 16, height: 16 },
{
id: 'bottom',
position: 'bottom',
x: 138.99998331652628,
y: 104.20308685759466,
width: 16,
height: 16
}
],
input: [
{ id: 'right', position: 'right', x: 282, y: 73.59375, width: 16, height: 16 },
{ id: 'left', position: 'left', x: -4, y: 73.59375, width: 16, height: 16 },
{ id: 'bottom', position: 'bottom', x: 139, y: 100.15625, width: 16, height: 16 }
]
},
computedPosition: { x: 180, y: 210, z: 0 },
selected: false,
dragging: false,
resizing: false,
initialized: true,
data: {},
events: {},
id: 'simple-textc9xkhtp5yypo7qsnp9e1gh',
position: { x: 180, y: 210 },
label: 'simple-text node'
},
{
type: 'simple-text',
dimensions: { width: 294, height: 103 },
handleBounds: {
source: [
{
id: 'right',
position: 'right',
x: 281.99987873053556,
y: 73.59372582307907,
width: 16,
height: 16
},
{ id: 'left', position: 'left', x: -3.999995328414287, y: 73.59372582307907, width: 16, height: 16 },
{
id: 'bottom',
position: 'bottom',
x: 138.99998331652628,
y: 104.20308685759466,
width: 16,
height: 16
}
],
input: [
{
id: 'right',
position: 'right',
x: 281.99987873053556,
y: 73.59372582307907,
width: 16,
height: 16
},
{ id: 'left', position: 'left', x: -3.999995328414287, y: 73.59372582307907, width: 16, height: 16 },
{
id: 'bottom',
position: 'bottom',
x: 138.99990008559496,
y: 100.15621170711313,
width: 16,
height: 16
}
]
},
computedPosition: { x: -45, y: 60, z: 0 },
selected: false,
dragging: false,
resizing: false,
initialized: true,
data: {},
events: {},
id: 'simple-textfuxp9gjvnc78ru0n1tkhul',
position: { x: -45, y: 60 },
label: 'simple-text node'
}
])
////////////////////////////////////////////.
// Removing data from the message store if delete button used
const onChange = (event: any) => {
event.forEach((element: any) => {
if (element.type == 'remove') {
store.layers.messages = store.layers.messages.filter((item: any) => {
return item.id != element.id
})
}
})
}
////////////////////////////////////////////.
</script>
<template>
<!-- {{ store }} -->
<div class="d-flex border" style="height: 100vh">
<GlobalMenu></GlobalMenu>
<div
class="m-1 border"
id="vue_flow"
oncontextmenu="return false;"
style="position: relative; width: 100%; height: 98%"
>
<VueFlow
v-model="elements"
class="customnodeflow"
:snap-to-grid="true"
:select-nodes-on-drag="true"
:only-render-visible-elements="true"
:max-zoom="50"
:min-zoom="0.05"
@dragover="onDragOver"
@drop="onDrop"
@nodeDoubleClick="onClick"
@nodesChange="onChange"
>
<Background pattern-color="#999" :gap="16" :size="1.2" />
<!-- Custom Edge from example -->
<template #edge-custom="props">
<CustomEdgeVue v-bind="props" />
</template>
<template #node-simple-text="props">
<SimpleTextVue :id="props.id" :selected="props.selected" />
</template>
<!-- End of importing Custom templates -->
<Controls />
<MiniMap v-show="messageToEdit === ''" />
</VueFlow>
</div>
</div>
</template>
<style>
.d-flex {
display: flex;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s 0.1s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
html,
body,
#app {
margin: 0;
height: 100%;
}
#app {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
/* Vueflow additional components style */
.vue-flow__minimap {
background-color: #2c3e50a6;
transform: scale(75%);
transform-origin: bottom right;
}
.customnodeflow button {
padding: 5px;
width: 25px;
height: 25px;
border-radius: 25px;
box-shadow: 0 5px 10px #0000004d;
cursor: pointer;
}
.customnodeflow button:hover {
opacity: 0.9;
transform: scale(105%);
transition: 0.25s all ease;
}
/* VueFlow Specifics */
.vue-flow {
background-color: #f2f5f7;
}
.vue-flow__edges {
z-index: 9999 !important;
}
/* Customize Handle */
.handle {
cursor: pointer !important;
}
/* Class used to select Control and Control Button */
.vue-flow__controls {
background-color: white;
padding: 0.15rem;
border-radius: 1rem;
}
.vue-flow__controls-button {
margin: 0.15rem;
border: 1px grey solid;
}
</style>
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => {
return {
layers: {
messageToEdit: '',
elements: {},
messages: [
{ id: 'simple-textc9xkhtp5yypo7qsnp9e1gh', type: 'simple-text', subtitle: 'bbb', color: '#000000' },
{ id: 'simple-textfuxp9gjvnc78ru0n1tkhul', type: 'simple-text', subtitle: 'aaa', color: '#000000' }
],
default_values: {
image:
'https://images.unsplash.com/photo-1545703399-4313b14625d9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8NHx8fGVufDB8fHx8&w=1000&q=80',
video: 'https://drive.google.com/uc?export=view&confirm=yTib&id=1g8uCFA1CDKMvVsPwb8V8ayk-3Mop3_Hu'
}
}
}
},
getters: {
getMessages: state => {
return () => state.layers.messages
},
getMessageById: state => {
return messageId => state.layers.messages.find(element => element.id == messageId)
},
getItemById: state => {
return (messageId, itemId) => state.getMessageById(messageId).items.find(element => element.id == itemId)
},
getDefaultValues: state => {
return () => state.layers.default_values
}
},
actions: {
setMessageItems(messageId, items) {
let result = this.layers.messages.find(element => element.id === messageId)
result.items = items
}
}
})
import getId from './radomId'
const createVueNode = (event, addNodes, project, store) => {
let id = getId()
const type = event.dataTransfer?.getData('application/vueflow')
const position = project({ x: event.clientX - 450, y: event.clientY - 20 })
let newNode = {
id: type + id,
type,
position,
label: `${type} node`
}
//////////////////////////////////////////.
switch (type) {
case 'facebook-message':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'facebook-message',
label: 'Label',
color: '#ffffff',
items: [
{
id: getId(),
type: 'messengerTextVue',
text: 'Enter Message Text',
buttons: []
}
]
})
})
break
case 'starting-step':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'starting-step',
label: 'Label',
content: 'Type',
color: '#ffffff',
items: []
})
})
break
case 'container':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'container',
label: 'Label',
width: '20rem',
height: '10rem',
color: '#3A8CC7'
})
})
break
case 'redirector':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'redirector',
label: 'Label',
color: '#000000'
})
})
break
case 'node-image':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'node-image',
label: 'Label',
src: '',
width: '340px',
height: '240px',
color: '#000000'
})
})
break
case 'free-mind':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'free-mind',
label: 'Label',
src: '',
width: '340px',
height: '240px',
color: '#40CE03'
})
})
break
case 'box-with-title':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'box-with-title',
label: 'Label',
title: 'Title',
subtitle: 'Subtitle',
color: '#000000'
})
})
break
case 'simple-text':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'simple-text',
subtitle: 'Subtitle',
color: '#000000'
})
})
break
case 'quick-reply':
store.$patch(state => {
state.layers.messages.push({
id: newNode.id,
type: 'quick-reply',
text: 'Quick Reply',
color: '#ffffff'
})
})
break
simpleText
default:
break
}
//////////////////////////////////////////.
// Implementation of a basic container catching
if (event.target.parentNode.id.substring(-1, 9) === 'container') {
newNode.parentNode = event.target.parentNode.id
}
////////////////////////////////////////////.
addNodes([newNode])
}
const copyVueNode = (addNodes, eid, getNode, store) => {
let id = getId() // Create a New UUid
const nodeById = getNode.value(eid) // Get The node to copy by its Id (eid)
const type = nodeById.type // Get the node's type
// When we copy, we need to create it above the old one (translate +50 x y)
const position = {
...nodeById.position,
x: nodeById.position.x + 50,
y: nodeById.position.y - 50
}
// Create a new message in the store
store.$patch(state => {
const currentMessage = state.layers.messages.filter(item => item.id === eid) // Get all the old message info
state.layers.messages = [
...state.layers.messages,
{
...JSON.parse(JSON.stringify(currentMessage))[0], // The element is copied by reference do we need to dereference it
id: type + id
}
]
})
addNodes([
{
id: type + id,
type,
position,
label: `${type} node`
}
])
}
export { createVueNode, copyVueNode }
export default function () {
let date = new Date();
return [
date.getDate(),
date.getMonth() + 1,
date.getFullYear(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
].join("-");
}
\ No newline at end of file
export default function() {
return (
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15)
)
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论