d7ab1dc6 by lihua

添加关系网和桑基图

1 parent d98bd724
...@@ -28,6 +28,7 @@ declare module '@vue/runtime-core' { ...@@ -28,6 +28,7 @@ declare module '@vue/runtime-core' {
28 FileUpload: typeof import('./src/components/FileUpload/index.vue')['default'] 28 FileUpload: typeof import('./src/components/FileUpload/index.vue')['default']
29 FixedActionBar: typeof import('./src/components/FixedActionBar/index.vue')['default'] 29 FixedActionBar: typeof import('./src/components/FixedActionBar/index.vue')['default']
30 Form: typeof import('./src/components/Form/index.vue')['default'] 30 Form: typeof import('./src/components/Form/index.vue')['default']
31 GraphTopbar: typeof import('./src/components/RelationNetwork/graphTopbar.vue')['default']
31 Hour: typeof import('./src/components/Schedule/component/hour.vue')['default'] 32 Hour: typeof import('./src/components/Schedule/component/hour.vue')['default']
32 ImagePreview: typeof import('./src/components/ImagePreview/index.vue')['default'] 33 ImagePreview: typeof import('./src/components/ImagePreview/index.vue')['default']
33 ImagesUpload: typeof import('./src/components/ImagesUpload/index.vue')['default'] 34 ImagesUpload: typeof import('./src/components/ImagesUpload/index.vue')['default']
...@@ -42,6 +43,7 @@ declare module '@vue/runtime-core' { ...@@ -42,6 +43,7 @@ declare module '@vue/runtime-core' {
42 PageNav: typeof import('./src/components/PageNav/index.vue')['default'] 43 PageNav: typeof import('./src/components/PageNav/index.vue')['default']
43 PcasCascader: typeof import('./src/components/PcasCascader/index.vue')['default'] 44 PcasCascader: typeof import('./src/components/PcasCascader/index.vue')['default']
44 Popover: typeof import('./src/components/Popover/index.vue')['default'] 45 Popover: typeof import('./src/components/Popover/index.vue')['default']
46 RelationNetwork: typeof import('./src/components/RelationNetwork/index.vue')['default']
45 RouterLink: typeof import('vue-router')['RouterLink'] 47 RouterLink: typeof import('vue-router')['RouterLink']
46 RouterView: typeof import('vue-router')['RouterView'] 48 RouterView: typeof import('vue-router')['RouterView']
47 Schedule: typeof import('./src/components/Schedule/index.vue')['default'] 49 Schedule: typeof import('./src/components/Schedule/index.vue')['default']
......
1 <script lang="ts" setup name="topbar">
2 import { ref, watch } from 'vue';
3
4 const props = defineProps({
5 isGraphDisplay: {
6 type: Boolean,
7 default: true
8 },
9 });
10
11 const isGraph = ref(false);
12
13 watch(() => props.isGraphDisplay, (val) => {
14 isGraph.value = val;
15 }, {
16 immediate: true
17 })
18
19 const emits = defineEmits(["displaySwitchChange"]);
20
21 const switchChange = (val) => {
22 isGraph.value = val
23 emits('displaySwitchChange', val);
24 }
25
26 </script>
27
28 <template>
29 <div className='g6-component-topbar-content'>
30 <div :class="isGraph ? 'selected g6-component-topbar-item' : 'g6-component-topbar-item'" @click="switchChange(true)">
31 关系网
32 </div>
33 <div :class="!isGraph ? 'selected g6-component-topbar-item' : 'g6-component-topbar-item'" @click="switchChange(false)">
34 桑基图
35 </div>
36 </div>
37 </template>
38
39 <style scoped lang="scss">
40 .g6-component-topbar-content {
41 display: flex;
42 flex-direction: row;
43 justify-content: center;
44 align-items: center;
45 background: #fff;
46 border: 1px solid var(--el-color-primary);
47 border-radius: 32px;
48 padding: 4px;
49 width: 138px;
50 height: 32px;
51 }
52
53 .g6-component-topbar-item {
54 width: 50%;
55 height: 100%;
56 display: flex;
57 align-items: center;
58 justify-content: center;
59 font-size: 14px;
60 color: #999999;
61 cursor: pointer;
62
63 &.selected {
64 background: #4FA1A4;
65 border-radius: 32px;
66 color: #fff;
67 }
68 }
69 </style>
...\ No newline at end of file ...\ No newline at end of file
...@@ -28,6 +28,7 @@ declare module '@vue/runtime-core' { ...@@ -28,6 +28,7 @@ declare module '@vue/runtime-core' {
28 FileUpload: typeof import('./../components/FileUpload/index.vue')['default'] 28 FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
29 FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default'] 29 FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default']
30 Form: typeof import('./../components/Form/index.vue')['default'] 30 Form: typeof import('./../components/Form/index.vue')['default']
31 GraphTopbar: typeof import('./../components/RelationNetwork/graphTopbar.vue')['default']
31 Hour: typeof import('./../components/Schedule/component/hour.vue')['default'] 32 Hour: typeof import('./../components/Schedule/component/hour.vue')['default']
32 ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default'] 33 ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
33 ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default'] 34 ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default']
...@@ -42,6 +43,7 @@ declare module '@vue/runtime-core' { ...@@ -42,6 +43,7 @@ declare module '@vue/runtime-core' {
42 PageNav: typeof import('./../components/PageNav/index.vue')['default'] 43 PageNav: typeof import('./../components/PageNav/index.vue')['default']
43 PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default'] 44 PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default']
44 Popover: typeof import('./../components/Popover/index.vue')['default'] 45 Popover: typeof import('./../components/Popover/index.vue')['default']
46 RelationNetwork: typeof import('./../components/RelationNetwork/index.vue')['default']
45 RouterLink: typeof import('vue-router')['RouterLink'] 47 RouterLink: typeof import('vue-router')['RouterLink']
46 RouterView: typeof import('vue-router')['RouterView'] 48 RouterView: typeof import('vue-router')['RouterView']
47 Schedule: typeof import('./../components/Schedule/index.vue')['default'] 49 Schedule: typeof import('./../components/Schedule/index.vue')['default']
......
1 <template>
2 <div ref="containerRef" class='canvas-wrapper'>
3
4 </div>
5 </template>
6
7 <script lang="ts" setup name="topbar">
8 import { ref, watch } from 'vue';
9 import * as echarts from "echarts";
10
11 const props = defineProps({
12 treeData: {
13 type: Array<any>,
14 default: [
15 {
16 source: 'a',
17 target: 'a1',
18 value: 5
19 },
20 {
21 source: 'a',
22 target: 'a2',
23 value: 3
24 },
25 {
26 source: 'b',
27 target: 'b1',
28 value: 8
29 },
30 {
31 source: 'a',
32 target: 'b1',
33 value: 3
34 },
35 {
36 source: 'b1',
37 target: 'a1',
38 value: 1
39 },
40 {
41 source: 'b1',
42 target: 'c',
43 value: 2
44 }
45 ]
46 },
47 names: {
48 type: Array<any>,
49 default: [
50 {
51 name: 'a'
52 },
53 {
54 name: 'b'
55 },
56 {
57 name: 'a1'
58 },
59 {
60 name: 'a2'
61 },
62 {
63 name: 'b1'
64 },
65 {
66 name: 'c'
67 }
68 ]
69 }
70 })
71
72 watch(() => props.treeData, (val) => {
73 setChartsOption();
74 })
75
76 const sankeyInstance: any = ref();
77
78 const containerRef = ref();
79
80 const setChartsOption = () => {
81 let option = {
82 tooltip: {
83 trigger: 'item'
84 },
85 series: [
86 {
87 type: 'sankey',
88 top: 80,
89 bottom: 40,
90 left: 50,
91 right: 50,
92 data: props.names,
93 links: props.treeData,
94 lineStyle: {
95 color: 'source',
96 curveness: 0.5
97 },
98 }
99 ]
100 }
101 sankeyInstance.value.setOption(option);
102 sankeyInstance.value.resize();
103 }
104
105 const resizeObserver = ref();
106
107 const observeResize = () => {
108 resizeObserver.value = new ResizeObserver(() => {
109 sankeyInstance.value?.resize();
110 });
111 resizeObserver.value.observe(containerRef.value);
112 }
113
114 onMounted(() => {
115 nextTick(() => {
116 sankeyInstance.value = echarts.init(containerRef.value);
117 setChartsOption();
118 observeResize();
119 })
120 })
121
122 </script>
123
124 <style lang="scss" scoped>
125 .canvas-wrapper {
126 width: 100%;
127 height: 100%;
128 position: relative;
129 }
130 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: metadataStandardQuery
3 </route>
4
5 <script lang="ts" setup name="metadataStandardQuery">
6 import { ref } from 'vue';
7 import { ElMessage, ElMessageBox } from "element-plus";
8 import Sankey from './components/Sankey.vue';
9 import Tree from '@/components/Tree/index.vue';
10 import RelationNetwork from '@/components/RelationNetwork/index.vue';
11 import {
12 } from '@/api/modules/dataMetaService';
13
14 import { useRouter, useRoute } from "vue-router";
15 import useDataMetaStore from "@/store/modules/dataMeta"
16
17 const router = useRouter();
18 const route = useRoute()
19
20 const switchGraphDisplay = ref(true); //true表示关系网,false表示桑基图
21
22 const { proxy } = getCurrentInstance() as any;
23
24 const relationNetworkRef = ref();
25
26 const treeInfo = ref({
27 id: "data-meta-standard-tree",
28 filter: true,
29 queryValue: "",
30 queryPlaceholder: "输入库/表名称搜索",
31 props: {
32 label: "name",
33 value: "guid",
34 isLeaf: "isLeaf",
35 },
36 nodeKey: 'guid',
37 expandedKey: ['1'],
38 currentNodeKey: '',
39 expandOnNodeClick: false,
40 data: <any>[],
41 loading: false
42 });
43
44 /** 获取左侧树数据. */
45 const getTreeData = async () => {
46 treeInfo.value.loading = true
47 // let params = {}
48 // const res: any = await getMetaTreeData(params)
49 // if (res.code == proxy.$passCode) {
50 // const data = res.data || [];
51 // let treeData: any = Object.keys(data).length ? [data] : [];
52 // treeInfo.value.data = treeData;
53 // allTreeData.value = treeData;
54 // if (treeData.length) {
55 // treeInfo.value.currentNodeKey = treeData[0].guid;
56 // treeInfo.value.expandedKey = <any>[treeData[0].guid];
57 // }
58 // } else {
59 // ElMessage.error(res.msg);
60 // }
61 treeInfo.value.data = [{
62 guid: '1',
63 name: '第一级1',
64 children: [{
65 guid: '1-1',
66 name: '第二级1-1',
67 children: [{
68 guid: '1-1-1',
69 name: '第三级1-1-1'
70 }]
71 }, {
72 guid: '1-2',
73 name: '第二级1-2'
74 }, {
75 guid: '1-3',
76 name: '第二级1-3'
77 }]
78 }, {
79 guid: '2',
80 name: '第一级2',
81 children: [{
82 guid: '2-1',
83 name: '第二级1-1'
84 }, {
85 guid: '2-2',
86 name: '第二级1-2'
87 }]
88 }];
89 treeInfo.value.loading = false
90 treeInfo.value.currentNodeKey = '1';
91 nodeClick(treeInfo.value.data[0])
92 }
93 /** 左侧树的的组件引用. */
94 const treeInfoRef = ref();
95
96 /** 当前选中的树节点数据data */
97 const lastClickNode: any = ref({});
98
99 const treeDataLoading = ref(false);
100
101 /** 数据血缘关系图组件 */
102 const lineageGraph: any = ref();
103
104 /** 点击左侧树节点,更新对应的血缘关系图. */
105 const nodeClick = (data) => {
106 console.log(data);
107 const ele = <HTMLElement>document.querySelector(".g6-component-contextmenu")
108 if (ele) {
109 ele.style.display = "none"
110 }
111 nextTick(() => {
112 lineageGraph.value?.tooltip1.hide()
113 })
114 treeInfo.value.currentNodeKey = data.guid;
115 treeInfo.value.expandedKey = <any>[data.guid];
116 lastClickNode.value = data;
117 }
118
119 /** 选中树节点后自动滚动到可视范围内. */
120 const scrollToNode = (nodeId) => {
121 nextTick(() => {
122 const nodeElement = treeInfoRef.value.treeRef.$el.querySelector(`[data-key="${nodeId}"]`);
123 if (nodeElement) {
124 nodeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
125 }
126 });
127 }
128
129 /** 处理从详情处跳转而来的默认展示. */
130 const processRouter = () => {
131 const { guid, databaseName, tableName, databaseChName, databaseGuid, fieldGuid, fieldEnName, set } = useDataMetaStore()
132 let isFL = useDataMetaStore().isFieldLineage;
133 if (fieldGuid) {//查看字段血缘的
134 nextTick(() => {
135 treeInfo.value.expandedKey = <any>[databaseGuid, guid];
136 treeInfo.value.currentNodeKey = fieldGuid as string;
137 scrollToNode(fieldGuid);
138 treeInfoRef.value.setCurrentKey(fieldGuid);
139 })
140 lastClickNode.value = { guid: fieldGuid, tableGuid: guid, databaseGuid: databaseGuid, enName: fieldEnName, tableName, databaseName, type: 4, isLeaf: true, databaseChName };
141 // getTableFieldLineageMap();
142 set()
143 } else if (guid) {
144 treeInfo.value.currentNodeKey = guid as string;
145 treeInfo.value.expandedKey = <any>[databaseGuid, guid];
146 lastClickNode.value = { guid: guid, tableName, databaseName, type: 3, databaseChName };
147 scrollToNode(guid);
148 switchGraphDisplay.value = isFL;
149 // if (isFL) {
150 // getAllTableFieldLineageMap();
151 // } else {
152 // getTableLineageMap()
153 // }
154 set()
155 }
156 }
157
158 onActivated(() => {
159 processRouter();
160 });
161
162 onBeforeMount(async () => {
163 await getTreeData()
164 processRouter();
165 })
166
167 onMounted(() => { })
168
169 const isGraphDisplay = ref(true);
170
171 const displaySwitchChange = (val) => {
172 isGraphDisplay.value = val;
173 }
174
175 const handleNodeItemClick = (graph, nodeItem) => {
176 const nodeId = nodeItem.get('id');
177 const parentData = graph.findDataById(nodeId);
178 if (!parentData.children) {
179 parentData.children = [];
180 }
181 treeDataLoading.value = true;
182 let childData = [{
183 guid: '33',
184 isField: true,
185 name: '字段1'
186 }, {
187 guid: '44',
188 isField: true,
189 name: '字段2字段2字段2字段2字段2字段2字段2字段2字段2xx2字段22字段22字段22字段22字段22字段2'
190 }, {
191 guid: '55',
192 isField: true,
193 name: '字段3'
194 }]
195 // parentData.collapsed = true;
196 // nodeItem.getModel().collapsed = true;
197 parentData.children = childData;
198 setTimeout(() => {
199 treeDataLoading.value = false;
200 graph.changeData();
201 // parentData.collapsed = true;
202 // graph.updateChildren(childData, parentData.id);
203 // graph.changeData(lastClickNode.value);
204 //graph.data(lastClickNode.value);
205 }, 2000)
206 }
207
208 const handleContextMenu = (nodeData) => {
209 //TODO,新建引用数据集
210 }
211
212 onBeforeUnmount(() => {
213 relationNetworkRef.value.destroy();
214 })
215
216 </script>
217
1 <template> 218 <template>
2 <div>元数据标准查询</div> 219 <div class="container_wrap full flex">
220 <div class="aside_wrap">
221 <div class="aside_title">数据库目录列表</div>
222 <Tree ref="treeInfoRef" :treeInfo="treeInfo" @nodeClick="nodeClick" />
223 </div>
224 <div class="main_wrap">
225 <div className='g6-component-topbar'>
226 <graphTopbar ref="topBarRef" @displaySwitchChange="displaySwitchChange" :isGraphDisplay="isGraphDisplay" />
227 </div>
228 <RelationNetwork v-show="isGraphDisplay" ref="relationNetworkRef" :tree-data="lastClickNode"
229 v-loading="treeDataLoading" @nodeItemClick="handleNodeItemClick" @contextMenu="handleContextMenu">
230 </RelationNetwork>
231 <Sankey v-show="!isGraphDisplay"></Sankey>
232 <!-- <div v-show="lastClickNode && lastClickNode?.type !== 3 && lastClickNode?.type !== 4" class="main-placeholder">
233 <img src="../../assets/images/no-data.png" :style="{ width: '96px', height: '96px' }" />
234 <div class="empty-text">暂无标准数据</div>
235 </div> -->
236 </div>
237 </div>
3 </template> 238 </template>
4 239
5 <script> 240 <style scoped lang="scss">
6 export default { 241 .container_wrap {
242
243 .aside_wrap {
244 width: 200px;
245 margin-right: 1px;
246 }
247
248 .main_wrap {
249 position: relative;
250
251 :deep(.canvas-wrapper) {
252 background-color: #f7f7f9;
253 }
254
255 .main-placeholder {
256 height: 100%;
257 display: flex;
258 justify-content: center;
259 align-items: center;
260 flex-direction: column;
261
262 .empty-text {
263 font-size: 14px;
264 color: #b2b2b2;
265 }
266 }
267 }
268
7 } 269 }
8 </script>
9 270
10 <style>
11 </style>
...\ No newline at end of file ...\ No newline at end of file
271 .g6-component-topbar {
272 position: absolute;
273 left: 24px;
274 bottom: unset;
275 top: 14px;
276 padding: 0;
277 text-align: center;
278 z-index: 999;
279 }
280
281 .container_wrap.flex .main_wrap {
282 padding: 0px;
283 }
284
285 .tree_panel {
286 height: calc(100% - 36px);
287 padding-top: 0;
288
289 :deep(.el-tree) {
290 margin: 0;
291 overflow: hidden auto;
292 }
293 }
294
295 .card-noData {
296 height: 100%;
297 width: 100%;
298 background: #fafafa;
299 display: flex;
300 flex-direction: column;
301 justify-content: center;
302 align-items: center;
303 color: #909399;
304 font-size: 14px;
305 }
306
307 :deep(.el-form .el-form-item) {
308 width: calc(100%);
309 // margin-right: 8px;
310 }
311
312 :deep(.el-message) {
313 position: fixed;
314 /* 使用fixed或absolute定位 */
315 z-index: 10000;
316 /* 设置一个较高的z-index值确保在最上层显示 */
317 }
318 </style>
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!