阅行客电子档案
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

332 lines
11 KiB

<template>
<div>
<!-- <div id="graph-container">
<div v-if="isLoading">Loading data from Neo4j...</div>
<div v-if="errorMessage">{{ errorMessage }}</div>
</div> -->
<div ref="myPage" class="my-graph" style="height: calc(100vh - 184px);">
<RelationGraph
ref="graphRef"
:options="graphOptions"
:on-node-click="onNodeClick"
:on-canvas-click="onCanvasClick"
>
<template #node="{node}">
<div @mouseover="showNodeTips(node, $event)" @mouseout="hideNodeTips(node, $event)">
<div class="c-my-rg-node" :style="{'height': node.width + 'px', 'line-height': node.width + 'px'}">
{{ node.text }}
</div>
</div>
</template>
<template #graph-plug>
<div v-if="isShowNodeTipsPanel" :style="{left: nodeMenuPanelPosition.x + 'px', top: nodeMenuPanelPosition.y + 'px' }" style="z-index: 999;padding:10px;background-color: #ffffff;border:#eeeeee solid 1px;box-shadow: 0px 0px 8px #cccccc;position: absolute;">
<div style="line-height: 25px;color: #888888;font-size: 12px;">节点类型:{{ currentNode.typeText }}</div>
<div style="line-height: 25px;color: #888888;font-size: 12px;">节点名称{{ currentNode.text }}</div>
</div>
</template>
</RelationGraph>
</div>
</div>
</template>
<script>
// import { getGraphData } from '../../../neo4j.js'
import RelationGraph from 'relation-graph'
import { FetchInitShowByCategory } from '@/api/ai/ai'
export default {
name: 'Demo',
components: { RelationGraph },
props: {
},
data() {
return {
graphData: {},
isShowCodePanel: false,
isShowNodeTipsPanel: false,
nodeMenuPanelPosition: { x: 0, y: 0 },
currentNode: {},
graphOptions: {
useAnimationWhenExpanded: true,
useAnimationWhenRefresh: true,
placeOtherGroup: true,
disableNodeclickEffect: true,
reLayoutWhenExpandedOrCollapsed: true,
defaultExpandHolderPosition: 'bottom',
zoomToFitWhenRefresh: true,
layout: {
layoutName: 'force',
'force_line_elastic': 0.5
},
allowSwitchLineShape: true,
allowSwitchJunctionPoint: true,
defaultLineColor: 'rgba(0,0,0,0.5)',
defaultNodeBorderWidth: 0,
defaultNodeBorderColor: 'transpanret',
defaultNodeFontColor: '#fff',
defaultFocusRootNode: false
},
isLoading: false,
errorMessage: '',
nodes: [],
edges: [],
allData: { 'nodes': [], 'lines': [] }
}
},
watch: {
// 'graphData': {
// handler(val) {
// setTimeout(() => {
// this.setData()
// }, 100)
// },
// immediate: true,
// deep: true
// }
},
mounted() {
this.resizeTimer = setInterval(async() => {
// const graphInstance = this.$refs.graphRef.getInstance();
// await graphInstance.zoomToFit();
}, 3000)
// this.allData.nodes = []
// this.allData.lines = []
// const query = "MATCH path = (target:Archives {id: '0047B3542CE88B895E41DE'})-[rels:HAS_KEYWORD*1..4]-(related:Archives) WHERE target <> related UNWIND relationships(path) AS r WITH startNode(r) AS n, r, endNode(r) AS m, length(path)/2 AS depth RETURN DISTINCT n, r, m;"
// this.fetchData(query)
// this.showGraph()
},
beforeDestroy() {
clearInterval(this.resizeTimer)
},
methods: {
resetAllData() {
this.allData = { 'nodes': [], 'lines': [] }
},
getOrigionGraphData(nodeId, id, tableName) {
const parmas = {
'searchKey': 'id',
'searchValue': id,
'tableName': tableName, // 节点名称
'querySize': 10
}
FetchInitShowByCategory(parmas).then((res) => {
this.graphData = res
this.setData(this.graphData, nodeId)
}).catch(err => {
console.log(err)
})
},
setData(data, selectedNodeId = null) {
const { nodes, lines } = data
// 档案Archives、档案门类Category、档案分类CategoryClass、全宗Fonds、关键词Keywords、保管期限Retention、保密期限SecrecyPeriod、密级SecurityClass
const typeToTextMap = {
Archives: '档案',
Category: '档案门类',
CategoryClass: '档案分类',
Fonds: '全宗',
Keywords: '关键词',
Retention: '保管期限',
SecrecyPeriod: '保密期限',
SecurityClass: '密级'
}
const newNodes = nodes.map(item => {
const newItem = {
...item,
id: String(item.id),
originId: item.properties.id,
text: item.properties.title,
type: item.labels[0]
}
const typeToColorMap = {
Archives: '#0348F3',
Category: '#14C9C9',
CategoryClass: '#F8B722',
Fonds: '#722ED1',
Keywords: '#F4647B',
Retention: '#018BFF',
SecrecyPeriod: '#FEBD98',
SecurityClass: '#B1EBDF'
}
const defaultColor = '#000000' // 默认颜色
if (typeToColorMap[item.labels[0]]) {
newItem.color = typeToColorMap[item.labels[0]]
newItem.borderColor = typeToColorMap[item.labels[0]]
} else {
newItem.color = defaultColor
newItem.borderColor = defaultColor
}
// 根据 type 给 newItem 加上 typeText
newItem.typeText = typeToTextMap[newItem.type]
return newItem
})
const newEdges = lines.map(item => {
return {
...item,
from: String(item.source),
to: String(item.target),
text: item.type
}
})
const existingNodeIds = new Set(this.allData.nodes.map(node => node.id))
const allNodesExist = newNodes.every(node => existingNodeIds.has(node.id))
let validNewEdges = []
let uniqueNewNodes = []
if (!allNodesExist) {
uniqueNewNodes = newNodes.filter(node => !existingNodeIds.has(node.id))
const existingLineKeys = new Set(this.allData.lines.map(line => `${line.from}-${line.to}-${line.text}`))
// 过滤掉 to 为选中节点及其关联节点的边
if (selectedNodeId) {
const relatedNodeIds = [selectedNodeId, ...this.getRelatedNodeIds(selectedNodeId)]
validNewEdges = newEdges.filter(edge => {
return !relatedNodeIds.includes(edge.to) && !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`)
})
} else {
validNewEdges = newEdges.filter(edge => !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`))
}
}
const originalNodesLength = this.allData.nodes.length
const originalLinesLength = this.allData.lines.length
this.allData.nodes = [...this.allData.nodes, ...uniqueNewNodes]
this.allData.lines = [...this.allData.lines, ...validNewEdges]
this.allData.nodes.sort((a, b) => {
return parseInt(a.id) - parseInt(b.id)
})
// 判断数据是否有变化
if (this.allData.nodes.length > originalNodesLength || this.allData.lines.length > originalLinesLength) {
this.showGraph()
} else {
console.log('数据无变化,不更新图表')
}
},
getRelatedNodeIds(nodeId) {
return this.allData.lines
.filter(line => line.from === nodeId || line.to === nodeId)
.map(line => line.from === nodeId ? line.to : line.from)
},
async showGraph() {
console.log('this.allData', this.allData)
// const rootId = this.allData.nodes[0].id
// this.allData.rootId = rootId
const graphInstance = this.$refs.graphRef.getInstance()
// await this.stopForceIfNeed()
await graphInstance.setJsonData(this.allData)
// setTimeout(async() => {
// await graphInstance.setZoom(100)
// await graphInstance.moveToCenter()
// await graphInstance.zoomToFit()
// }, 1800)
},
async stopForceIfNeed() {
const graphInstance = this.$refs.graphRef.getInstance()
await graphInstance.stopAutoLayout()
},
async updateLayouterOptions() {
await this.stopForceIfNeed()
const graphInstance = this.$refs.graphRef.getInstance()
graphInstance.layouter.maxLayoutTimes = this.graphOptions.layout.maxLayoutTimes
graphInstance.layouter.force_node_repulsion = this.graphOptions.layout.force_node_repulsion
graphInstance.layouter.force_line_elastic = this.graphOptions.layout.force_line_elastic
setTimeout(async() => {
await graphInstance.startAutoLayout()
}, 500)
},
onNodeClick(nodeObject, $event) {
console.log('this.allData', this.allData)
console.log('节点数据:', nodeObject)
const nodeId = nodeObject.id
const targetNode = this.allData.nodes.find(node => node.id === nodeId)
if (targetNode) {
const originId = targetNode.originId
console.log(originId)
// this.$emit('getGraphData', originId, targetNode.type)
this.getOrigionGraphData(nodeId, originId, targetNode.type)
} else {
console.log('未找到对应的节点')
}
// const query = `MATCH (n) WHERE id(n) = ${nodeId} MATCH (n)-[r]-(m) RETURN n, r,m;`
// this.fetchData(query, nodeId)
// console.log('query', query)
},
onCanvasClick($event) {
const _all_nodes = this.$refs.graphRef.getInstance().getNodes()
_all_nodes.forEach((thisNode) => {
const _isHideThisNode = true
thisNode.opacity = _isHideThisNode ? 1 : 0.1
})
},
showNodeTips(nodeObject, $event) {
this.currentNode = nodeObject
const nodeId = nodeObject.id
const targetNode = this.allData.nodes.find(node => node.id === nodeId)
if (!targetNode) {
console.error(`未找到 ID 为 ${nodeId} 的节点`)
return
}
this.currentNode.typeText = targetNode.typeText
const _base_position = this.$refs.graphRef.getInstance().options.fullscreen ? { x: 0, y: 0 } : this.$refs.myPage.getBoundingClientRect()
this.isShowNodeTipsPanel = true
this.nodeMenuPanelPosition.x = $event.clientX - _base_position.x + 10
this.nodeMenuPanelPosition.y = $event.clientY - _base_position.y + 10
},
hideNodeTips(nodeObject, $event) {
this.isShowNodeTipsPanel = false
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .rel-toolbar{
color: #000;
.c-current-zoom{
color: #000;
}
}
.c-my-rg-node {
font-size: 13px;
border-radius: 50%;
cursor: pointer;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
}
.c-node-menu-item{
line-height: 30px;
padding-left: 10px;
cursor: pointer;
color: #444444;
font-size: 14px;
border-top:#efefef solid 1px;
}
.c-node-menu-item:hover{
background-color: rgba(66,187,66,0.2);
}
.c-big-style{
font-size: 30px;
}
::v-deep .c-rg-line-text{
font-size: 10px !important;
}
</style>