|
@@ -0,0 +1,376 @@
|
|
|
+<script setup>
|
|
|
+import { nextTick, ref } from 'vue'
|
|
|
+import { apiETAChart } from '@/api/etaChart'
|
|
|
+import { useRoute, useRouter } from 'vue-router'
|
|
|
+import { Graph } from '@antv/x6';
|
|
|
+import { register, getTeleport } from '@antv/x6-vue-shape'
|
|
|
+import dagre from "dagre"
|
|
|
+import EdbSourceToolTip from '@/components/EdbSourceToolTip.vue';
|
|
|
+
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+const router = useRouter()
|
|
|
+
|
|
|
+const treeData = ref({})
|
|
|
+async function getDetail() {
|
|
|
+ if (!route.query.code) return
|
|
|
+ const res = await apiETAChart.getEdbSource({
|
|
|
+ UniqueCode: route.query.code
|
|
|
+ })
|
|
|
+ if (res.Ret === 200) {
|
|
|
+ treeData.value = res.Data;
|
|
|
+ nextTick(() => {
|
|
|
+ init()
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+getDetail()
|
|
|
+
|
|
|
+let graph = null, mould = ref(null), mouldText = ref(null);
|
|
|
+let params = {
|
|
|
+ stopNodeClick: false
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const TeleportContainer = getTeleport()
|
|
|
+
|
|
|
+function init() {
|
|
|
+ graph = new Graph({
|
|
|
+ container: document.getElementById('sand-chart-body'),
|
|
|
+ autoResize: false,
|
|
|
+ async: true,//不异步时,显示隐藏会卡死
|
|
|
+ panning: true,
|
|
|
+ background: {
|
|
|
+ color: '#fff',
|
|
|
+ },
|
|
|
+ scroller: {
|
|
|
+ enabled: true,
|
|
|
+ pannable: true,
|
|
|
+ minVisibleWidth: 50,
|
|
|
+ minVisibleHeight: 50,
|
|
|
+ },
|
|
|
+ interacting: {
|
|
|
+ nodeMovable: false,
|
|
|
+ magnetConnectable: false,
|
|
|
+ edgeMovable: false,
|
|
|
+ edgeLabelMovable: false,
|
|
|
+ arrowheadMovable: false,
|
|
|
+ vertexMovable: false,
|
|
|
+ vertexMovable: false,
|
|
|
+ vertexDeletable: false
|
|
|
+ },
|
|
|
+ mousewheel: {
|
|
|
+ enabled: true,
|
|
|
+ modifiers: ['ctrl', 'meta']
|
|
|
+ }, //滚轮缩放
|
|
|
+ scaling: {
|
|
|
+ min: 0.5,
|
|
|
+ max: 2
|
|
|
+ },
|
|
|
+ //小地图
|
|
|
+ // minimap: {
|
|
|
+ // enabled: true,
|
|
|
+ // container: document.getElementById("minimap"),
|
|
|
+ // }
|
|
|
+ })
|
|
|
+
|
|
|
+ graph.on('node:mousemove', () => {
|
|
|
+ // 拖动画布的时候 阻止节点的点击操作
|
|
|
+ params.stopNodeClick = true
|
|
|
+ })
|
|
|
+
|
|
|
+ graph.on('node:mouseup', () => {
|
|
|
+ // 延迟释放
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ params.stopNodeClick = false
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ register({
|
|
|
+ shape: 'vue-shape',
|
|
|
+ component: EdbSourceToolTip,
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+ Graph.registerEdge(
|
|
|
+ 'org-edge',
|
|
|
+ {
|
|
|
+ attrs: {
|
|
|
+ line: {
|
|
|
+ strokeWidth: 1,
|
|
|
+ stroke: '#dddddd',
|
|
|
+ sourceMarker: null,
|
|
|
+ targetMarker: null,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ zIndex: 0
|
|
|
+ },
|
|
|
+ true,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+ mouldText.value = document.getElementById('mould-text')
|
|
|
+ let edbName = treeData.value.EdbName + (treeData.value.IsStop ? '(暂停更新)' : '')
|
|
|
+ mouldText.value.innerText = edbName
|
|
|
+ mould.value = document.getElementById('mould')
|
|
|
+
|
|
|
+ let node = graph.createNode({
|
|
|
+ shape: 'vue-shape',
|
|
|
+ component: 'custom-rect',
|
|
|
+ width: mould.value.offsetWidth + 1,
|
|
|
+ height: mould.value.offsetHeight,
|
|
|
+ attrs: {
|
|
|
+ body: {
|
|
|
+ rx: 4,
|
|
|
+ ry: 4,
|
|
|
+ strokeWidth: 1,
|
|
|
+ class: 'body-class'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ EdbName: edbName,
|
|
|
+ RuleTitle: treeData.value.RuleTitle,
|
|
|
+ routeQuery: {
|
|
|
+ ClassifyId: treeData.value.ClassifyId,
|
|
|
+ UniqueCode: treeData.value.UniqueCode,
|
|
|
+ EdbInfoId: treeData.value.EdbInfoId,
|
|
|
+ EdbInfoType: treeData.value.EdbInfoType,
|
|
|
+ HaveOperaAuth: treeData.value.HaveOperaAuth
|
|
|
+ },
|
|
|
+ isRoot: true,
|
|
|
+ isLeaf: (treeData.value.Child && treeData.value.Child.length > 0) ? false : true,
|
|
|
+ style: {
|
|
|
+ color: treeData.value.IsStop ? "red!important" : '#ffffff!important',
|
|
|
+ },
|
|
|
+ }
|
|
|
+ })
|
|
|
+ let cells = [node, ...createCells(treeData.value.Child, node)]
|
|
|
+ graph.resetCells(cells)
|
|
|
+ layout()
|
|
|
+ graph.positionCell(node, 'top', { padding: { top: 20 } })
|
|
|
+}
|
|
|
+
|
|
|
+function createCells(list, parentNode) {
|
|
|
+ if (!(list && list.length > 0)) {
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ let dataList = []
|
|
|
+ list.forEach(element => {
|
|
|
+ let edbName = element.EdbName + (element.IsStop ? '(暂停更新)' : '')
|
|
|
+
|
|
|
+ mouldText.value.innerText = edbName
|
|
|
+ let node = graph.createNode({
|
|
|
+ shape: 'vue-shape',
|
|
|
+ component: 'custom-rect',
|
|
|
+ width: mould.value.offsetWidth + 1,
|
|
|
+ height: mould.value.offsetHeight,
|
|
|
+ attrs: {
|
|
|
+ body: {
|
|
|
+ rx: 4,
|
|
|
+ ry: 4,
|
|
|
+ strokeWidth: 1,
|
|
|
+ class: 'body-class'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ style: {
|
|
|
+ backgroundColor: '#f2f6fa',
|
|
|
+ color: element.IsStop ? "red!important" : '#000000',
|
|
|
+ },
|
|
|
+ EdbName: edbName,
|
|
|
+ RuleTitle: element.RuleTitle,
|
|
|
+ routeQuery: {
|
|
|
+ ClassifyId: element.ClassifyId,
|
|
|
+ UniqueCode: element.UniqueCode,
|
|
|
+ EdbInfoId: element.EdbInfoId,
|
|
|
+ EdbInfoType: element.EdbInfoType,
|
|
|
+ HaveOperaAuth: treeData.value.HaveOperaAuth
|
|
|
+ },
|
|
|
+ isRoot: false,
|
|
|
+ isLeaf: (element.Child && element.Child.length > 0) ? false : true
|
|
|
+ }
|
|
|
+ })
|
|
|
+ let side = graph.createEdge({
|
|
|
+ shape: 'org-edge',
|
|
|
+ source: { cell: parentNode.id },
|
|
|
+ target: { cell: node.id },
|
|
|
+ })
|
|
|
+ dataList.push(node)
|
|
|
+ dataList.push(side)
|
|
|
+ dataList = [...dataList, ...createCells(element.Child, node)]
|
|
|
+ });
|
|
|
+ return dataList
|
|
|
+}
|
|
|
+
|
|
|
+function layout() {
|
|
|
+ const dir = 'TB'
|
|
|
+ const nodes = graph.getNodes()
|
|
|
+ const edges = graph.getEdges()
|
|
|
+ const g = new dagre.graphlib.Graph()
|
|
|
+
|
|
|
+ g.setDefaultEdgeLabel(() => ({}))
|
|
|
+ nodes.forEach((node) => {
|
|
|
+ g.setNode(node.id, { width: node.size().width, height: node.size().height })
|
|
|
+ })
|
|
|
+
|
|
|
+ let heights = nodes.map(item => {
|
|
|
+ // 为了防止重叠,不知道他内部怎么排的,先调整这样。
|
|
|
+ if (item.data.isLeaf) {
|
|
|
+ return item.size().height * 1.5 - 100
|
|
|
+ } else {
|
|
|
+ return item.size().height / 2
|
|
|
+ }
|
|
|
+ })
|
|
|
+ let maxHeight = Math.max(...heights)
|
|
|
+ // console.log(maxHeight,'maxHeight');
|
|
|
+ g.setGraph({ rankdir: dir, nodesep: 50, ranksep: maxHeight })
|
|
|
+ edges.forEach((edge) => {
|
|
|
+ const source = edge.getSource()
|
|
|
+ const target = edge.getTarget()
|
|
|
+ g.setEdge(source.cell, target.cell)
|
|
|
+ })
|
|
|
+
|
|
|
+ dagre.layout(g)
|
|
|
+
|
|
|
+ g.nodes().forEach((id) => {
|
|
|
+ const node = graph.getCellById(id)
|
|
|
+ if (node) {
|
|
|
+ const pos = g.node(id)
|
|
|
+ node.position(pos.x, pos.y)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ edges.forEach((edge) => {
|
|
|
+ const source = edge.getSourceNode()
|
|
|
+ const target = edge.getTargetNode()
|
|
|
+ const sourceBBox = source.getBBox()
|
|
|
+ const targetBBox = target.getBBox()
|
|
|
+
|
|
|
+ if ((dir === 'LR' || dir === 'RL') && sourceBBox.y !== targetBBox.y) {
|
|
|
+ const gap =
|
|
|
+ dir === 'LR'
|
|
|
+ ? targetBBox.x - sourceBBox.x - sourceBBox.width
|
|
|
+ : -sourceBBox.x + targetBBox.x + targetBBox.width
|
|
|
+ const fix = dir === 'LR' ? sourceBBox.width : 0
|
|
|
+ const x = sourceBBox.x + fix + gap / 2
|
|
|
+ edge.setVertices([
|
|
|
+ { x, y: sourceBBox.center.y },
|
|
|
+ { x, y: targetBBox.center.y },
|
|
|
+ ])
|
|
|
+ } else if (
|
|
|
+ (dir === 'TB' || dir === 'BT')) {
|
|
|
+ const gap =
|
|
|
+ dir === 'TB'
|
|
|
+ ? targetBBox.y - sourceBBox.y - sourceBBox.height
|
|
|
+ : -sourceBBox.y + targetBBox.y + targetBBox.height
|
|
|
+ const fix = dir === 'TB' ? sourceBBox.height : 0
|
|
|
+ const y = sourceBBox.y + fix + gap / 2
|
|
|
+
|
|
|
+ edge.setVertices([
|
|
|
+ { x: sourceBBox.center.x, y },
|
|
|
+ { x: targetBBox.center.x, y },
|
|
|
+ ])
|
|
|
+ } else {
|
|
|
+ edge.setVertices([])
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="edb-source-page">
|
|
|
+ <div class="edb-title">{{ treeData.EdbName || "" }}</div>
|
|
|
+ <div class="edb-source-wrap">
|
|
|
+ <div class="sandbox-body">
|
|
|
+ <div class="sand-chart-body" id="sand-chart-body"></div>
|
|
|
+ <TeleportContainer />
|
|
|
+ <div id="minimap" class="minimap"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 确定每个Node节点大小用 -->
|
|
|
+ <div id="mould"><span id="mould-text"></span></div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.sandbox-body {
|
|
|
+ .x6-graph-scroller {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .x6-port-body {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* reseize 框样式 */
|
|
|
+ .x6-widget-transform {
|
|
|
+ .x6-widget-transform-resize {
|
|
|
+ border-radius: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .x6-widget-minimap-viewport {
|
|
|
+ border-color: red;
|
|
|
+ .x6-widget-minimap-viewport-zoom {
|
|
|
+ border-color: red;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .x6-widget-minimap {
|
|
|
+ width: auto !important;
|
|
|
+ height: auto !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style scoped lang="scss">
|
|
|
+.edb-source-page {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: calc(100vh - 120px);
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #c8cdd9;
|
|
|
+ box-sizing: border-box;
|
|
|
+ // padding:30px;
|
|
|
+ .edb-title {
|
|
|
+ margin: 0 -30px;
|
|
|
+ text-align: center;
|
|
|
+ padding: 30px;
|
|
|
+ border-bottom: 1px solid #c8cdd9;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ .edb-source-wrap {
|
|
|
+ text-align: center;
|
|
|
+ flex: 1;
|
|
|
+ overflow: auto;
|
|
|
+ .sandbox-body {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ position: relative;
|
|
|
+ .minimap {
|
|
|
+ position: absolute;
|
|
|
+ right: 6px;
|
|
|
+ bottom: 6px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ #sand-chart-body {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #mould {
|
|
|
+ position: absolute;
|
|
|
+ max-width: 100px;
|
|
|
+ padding: 20px;
|
|
|
+ background-color: red;
|
|
|
+ font-size: 16px;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 4px;
|
|
|
+ top: -10000px;
|
|
|
+ opacity: 0;
|
|
|
+ word-break: break-all;
|
|
|
+ box-sizing: content-box;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|