阅行客电子档案
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

4 months ago
  1. <template>
  2. <div>
  3. <!-- <div id="graph-container">
  4. <div v-if="isLoading">Loading data from Neo4j...</div>
  5. <div v-if="errorMessage">{{ errorMessage }}</div>
  6. </div> -->
  7. <div ref="myPage" class="my-graph" style="height: calc(100vh - 184px);">
  8. <RelationGraph
  9. ref="graphRef"
  10. :options="graphOptions"
  11. :on-node-click="onNodeClick"
  12. :on-canvas-click="onCanvasClick"
  13. >
  14. <template #node="{node}">
  15. <div @mouseover="showNodeTips(node, $event)" @mouseout="hideNodeTips(node, $event)">
  16. <div class="c-my-rg-node" :style="{'height': node.width + 'px', 'line-height': node.width + 'px'}">
  17. {{ node.text }}
  18. </div>
  19. </div>
  20. </template>
  21. <template #graph-plug>
  22. <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;">
  23. <div style="line-height: 25px;color: #888888;font-size: 12px;">节点类型{{ currentNode.typeText }}</div>
  24. <div style="line-height: 25px;color: #888888;font-size: 12px;">节点名称{{ currentNode.text }}</div>
  25. </div>
  26. </template>
  27. </RelationGraph>
  28. </div>
  29. </div>
  30. </template>
  31. <script>
  32. // import { getGraphData } from '../../../neo4j.js'
  33. import RelationGraph from 'relation-graph'
  34. import { FetchInitShowByCategory } from '@/api/ai/ai'
  35. export default {
  36. name: 'Demo',
  37. components: { RelationGraph },
  38. props: {
  39. },
  40. data() {
  41. return {
  42. graphData: {},
  43. isShowCodePanel: false,
  44. isShowNodeTipsPanel: false,
  45. nodeMenuPanelPosition: { x: 0, y: 0 },
  46. currentNode: {},
  47. graphOptions: {
  48. useAnimationWhenExpanded: true,
  49. useAnimationWhenRefresh: true,
  50. placeOtherGroup: true,
  51. disableNodeclickEffect: true,
  52. reLayoutWhenExpandedOrCollapsed: true,
  53. defaultExpandHolderPosition: 'bottom',
  54. zoomToFitWhenRefresh: true,
  55. layout: {
  56. layoutName: 'force',
  57. 'force_line_elastic': 0.5
  58. },
  59. allowSwitchLineShape: true,
  60. allowSwitchJunctionPoint: true,
  61. defaultLineColor: 'rgba(0,0,0,0.5)',
  62. defaultNodeBorderWidth: 0,
  63. defaultNodeBorderColor: 'transpanret',
  64. defaultNodeFontColor: '#fff',
  65. defaultFocusRootNode: false
  66. },
  67. isLoading: false,
  68. errorMessage: '',
  69. nodes: [],
  70. edges: [],
  71. allData: { 'nodes': [], 'lines': [] }
  72. }
  73. },
  74. watch: {
  75. // 'graphData': {
  76. // handler(val) {
  77. // setTimeout(() => {
  78. // this.setData()
  79. // }, 100)
  80. // },
  81. // immediate: true,
  82. // deep: true
  83. // }
  84. },
  85. mounted() {
  86. this.resizeTimer = setInterval(async() => {
  87. // const graphInstance = this.$refs.graphRef.getInstance();
  88. // await graphInstance.zoomToFit();
  89. }, 3000)
  90. // this.allData.nodes = []
  91. // this.allData.lines = []
  92. // 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;"
  93. // this.fetchData(query)
  94. // this.showGraph()
  95. },
  96. beforeDestroy() {
  97. clearInterval(this.resizeTimer)
  98. },
  99. methods: {
  100. resetAllData() {
  101. this.allData = { 'nodes': [], 'lines': [] }
  102. },
  103. getOrigionGraphData(nodeId, id, tableName) {
  104. const parmas = {
  105. 'searchKey': 'id',
  106. 'searchValue': id,
  107. 'tableName': tableName, // 节点名称
  108. 'querySize': 10
  109. }
  110. FetchInitShowByCategory(parmas).then((res) => {
  111. this.graphData = res
  112. this.setData(this.graphData, nodeId)
  113. }).catch(err => {
  114. console.log(err)
  115. })
  116. },
  117. setData(data, selectedNodeId = null) {
  118. const { nodes, lines } = data
  119. // 档案Archives、档案门类Category、档案分类CategoryClass、全宗Fonds、关键词Keywords、保管期限Retention、保密期限SecrecyPeriod、密级SecurityClass
  120. const typeToTextMap = {
  121. Archives: '档案',
  122. Category: '档案门类',
  123. CategoryClass: '档案分类',
  124. Fonds: '全宗',
  125. Keywords: '关键词',
  126. Retention: '保管期限',
  127. SecrecyPeriod: '保密期限',
  128. SecurityClass: '密级'
  129. }
  130. const newNodes = nodes.map(item => {
  131. const newItem = {
  132. ...item,
  133. id: String(item.id),
  134. originId: item.properties.id,
  135. text: item.properties.title,
  136. type: item.labels[0]
  137. }
  138. const typeToColorMap = {
  139. Archives: '#0348F3',
  140. Category: '#14C9C9',
  141. CategoryClass: '#F8B722',
  142. Fonds: '#722ED1',
  143. Keywords: '#F4647B',
  144. Retention: '#018BFF',
  145. SecrecyPeriod: '#FEBD98',
  146. SecurityClass: '#B1EBDF'
  147. }
  148. const defaultColor = '#000000' // 默认颜色
  149. if (typeToColorMap[item.labels[0]]) {
  150. newItem.color = typeToColorMap[item.labels[0]]
  151. newItem.borderColor = typeToColorMap[item.labels[0]]
  152. } else {
  153. newItem.color = defaultColor
  154. newItem.borderColor = defaultColor
  155. }
  156. // 根据 type 给 newItem 加上 typeText
  157. newItem.typeText = typeToTextMap[newItem.type]
  158. return newItem
  159. })
  160. const newEdges = lines.map(item => {
  161. return {
  162. ...item,
  163. from: String(item.source),
  164. to: String(item.target),
  165. text: item.type
  166. }
  167. })
  168. const existingNodeIds = new Set(this.allData.nodes.map(node => node.id))
  169. const allNodesExist = newNodes.every(node => existingNodeIds.has(node.id))
  170. let validNewEdges = []
  171. let uniqueNewNodes = []
  172. if (!allNodesExist) {
  173. uniqueNewNodes = newNodes.filter(node => !existingNodeIds.has(node.id))
  174. const existingLineKeys = new Set(this.allData.lines.map(line => `${line.from}-${line.to}-${line.text}`))
  175. // 过滤掉 to 为选中节点及其关联节点的边
  176. if (selectedNodeId) {
  177. const relatedNodeIds = [selectedNodeId, ...this.getRelatedNodeIds(selectedNodeId)]
  178. validNewEdges = newEdges.filter(edge => {
  179. return !relatedNodeIds.includes(edge.to) && !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`)
  180. })
  181. } else {
  182. validNewEdges = newEdges.filter(edge => !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`))
  183. }
  184. }
  185. const originalNodesLength = this.allData.nodes.length
  186. const originalLinesLength = this.allData.lines.length
  187. this.allData.nodes = [...this.allData.nodes, ...uniqueNewNodes]
  188. this.allData.lines = [...this.allData.lines, ...validNewEdges]
  189. this.allData.nodes.sort((a, b) => {
  190. return parseInt(a.id) - parseInt(b.id)
  191. })
  192. // 判断数据是否有变化
  193. if (this.allData.nodes.length > originalNodesLength || this.allData.lines.length > originalLinesLength) {
  194. this.showGraph()
  195. } else {
  196. console.log('数据无变化,不更新图表')
  197. }
  198. },
  199. getRelatedNodeIds(nodeId) {
  200. return this.allData.lines
  201. .filter(line => line.from === nodeId || line.to === nodeId)
  202. .map(line => line.from === nodeId ? line.to : line.from)
  203. },
  204. async showGraph() {
  205. console.log('this.allData', this.allData)
  206. // const rootId = this.allData.nodes[0].id
  207. // this.allData.rootId = rootId
  208. const graphInstance = this.$refs.graphRef.getInstance()
  209. // await this.stopForceIfNeed()
  210. await graphInstance.setJsonData(this.allData)
  211. // setTimeout(async() => {
  212. // await graphInstance.setZoom(100)
  213. // await graphInstance.moveToCenter()
  214. // await graphInstance.zoomToFit()
  215. // }, 1800)
  216. },
  217. async stopForceIfNeed() {
  218. const graphInstance = this.$refs.graphRef.getInstance()
  219. await graphInstance.stopAutoLayout()
  220. },
  221. async updateLayouterOptions() {
  222. await this.stopForceIfNeed()
  223. const graphInstance = this.$refs.graphRef.getInstance()
  224. graphInstance.layouter.maxLayoutTimes = this.graphOptions.layout.maxLayoutTimes
  225. graphInstance.layouter.force_node_repulsion = this.graphOptions.layout.force_node_repulsion
  226. graphInstance.layouter.force_line_elastic = this.graphOptions.layout.force_line_elastic
  227. setTimeout(async() => {
  228. await graphInstance.startAutoLayout()
  229. }, 500)
  230. },
  231. onNodeClick(nodeObject, $event) {
  232. console.log('this.allData', this.allData)
  233. console.log('节点数据:', nodeObject)
  234. const nodeId = nodeObject.id
  235. const targetNode = this.allData.nodes.find(node => node.id === nodeId)
  236. if (targetNode) {
  237. const originId = targetNode.originId
  238. console.log(originId)
  239. // this.$emit('getGraphData', originId, targetNode.type)
  240. this.getOrigionGraphData(nodeId, originId, targetNode.type)
  241. } else {
  242. console.log('未找到对应的节点')
  243. }
  244. // const query = `MATCH (n) WHERE id(n) = ${nodeId} MATCH (n)-[r]-(m) RETURN n, r,m;`
  245. // this.fetchData(query, nodeId)
  246. // console.log('query', query)
  247. },
  248. onCanvasClick($event) {
  249. const _all_nodes = this.$refs.graphRef.getInstance().getNodes()
  250. _all_nodes.forEach((thisNode) => {
  251. const _isHideThisNode = true
  252. thisNode.opacity = _isHideThisNode ? 1 : 0.1
  253. })
  254. },
  255. showNodeTips(nodeObject, $event) {
  256. this.currentNode = nodeObject
  257. const nodeId = nodeObject.id
  258. const targetNode = this.allData.nodes.find(node => node.id === nodeId)
  259. if (!targetNode) {
  260. console.error(`未找到 ID 为 ${nodeId} 的节点`)
  261. return
  262. }
  263. this.currentNode.typeText = targetNode.typeText
  264. const _base_position = this.$refs.graphRef.getInstance().options.fullscreen ? { x: 0, y: 0 } : this.$refs.myPage.getBoundingClientRect()
  265. this.isShowNodeTipsPanel = true
  266. this.nodeMenuPanelPosition.x = $event.clientX - _base_position.x + 10
  267. this.nodeMenuPanelPosition.y = $event.clientY - _base_position.y + 10
  268. },
  269. hideNodeTips(nodeObject, $event) {
  270. this.isShowNodeTipsPanel = false
  271. }
  272. }
  273. }
  274. </script>
  275. <style lang="scss" scoped>
  276. ::v-deep .rel-toolbar{
  277. color: #000;
  278. .c-current-zoom{
  279. color: #000;
  280. }
  281. }
  282. .c-my-rg-node {
  283. font-size: 13px;
  284. border-radius: 50%;
  285. cursor: pointer;
  286. height: 80px;
  287. display: flex;
  288. justify-content: center;
  289. align-items: center;
  290. }
  291. .c-node-menu-item{
  292. line-height: 30px;
  293. padding-left: 10px;
  294. cursor: pointer;
  295. color: #444444;
  296. font-size: 14px;
  297. border-top:#efefef solid 1px;
  298. }
  299. .c-node-menu-item:hover{
  300. background-color: rgba(66,187,66,0.2);
  301. }
  302. .c-big-style{
  303. font-size: 30px;
  304. }
  305. ::v-deep .c-rg-line-text{
  306. font-size: 10px !important;
  307. }
  308. </style>