31 changed files with 792 additions and 396 deletions
-
4.env.production
-
4public/static/config.js
-
6src/components/Crud/CRUD.operation.vue
-
4src/components/Crud/UD.operation.vue
-
73src/neo4j.js
-
162src/views/AIAssistant/AICataloging/index.vue
-
8src/views/archiveKeeping/deviceManage/index.vue
-
4src/views/archiveKeeping/deviceManage/tableList.vue
-
8src/views/archivesManage/managementLibrary/module/collectHeader.vue
-
85src/views/collectReorganizi/collectionLibrary/module/collectHeader.vue
-
291src/views/components/echarts/graph.vue
-
425src/views/components/echarts/graph222.vue
-
6src/views/storeManage/deviceManage/index.vue
-
4src/views/storeManage/deviceManage/module/deviceDetail.vue
-
2src/views/storeManage/tagManage/tagLog/index.vue
-
6src/views/system/archiveScopeManage/module/tableList.vue
-
2src/views/system/archivesCategory/fieldManage/index.vue
-
8src/views/system/archivesCategory/fileNoFormat/index.vue
-
8src/views/system/archivesCategory/index.vue
-
8src/views/system/archivesCategory/listBrowsing/index.vue
-
8src/views/system/archivesCategory/orderingRule/index.vue
-
8src/views/system/archivesClassify/module/tableList.vue
-
4src/views/system/dict/dictDetail.vue
-
2src/views/system/fileLibraryManage/fieldManage/index.vue
-
8src/views/system/fileLibraryManage/index.vue
-
8src/views/system/fileLibraryManage/listBrowsing/index.vue
-
8src/views/system/fileLibraryManage/orderingRule/index.vue
-
6src/views/system/metadata/index.vue
-
2src/views/system/subjectLibraryManage/fieldManage/index.vue
-
8src/views/system/subjectLibraryManage/listBrowsing/index.vue
-
8src/views/system/subjectLibraryManage/orderingRule/index.vue
@ -0,0 +1,425 @@ |
|||||
|
<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);"> |
||||
|
<!-- :on-node-click="onNodeClick" |
||||
|
:on-line-click="onLineClick" |
||||
|
:on-canvas-click="onCanvasClick" |
||||
|
:on-node-expand="onNodeExpand" --> |
||||
|
<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)"> |
||||
|
<!-- :class="node.id === '1' ? 'c-my-rg-node c-big-style': 'c-my-rg-node '" --> |
||||
|
<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;padding-left: 10px;color: #888888;font-size: 12px;">节点名称:{{ currentNode.text }}</div> |
||||
|
<div class="c-node-menu-item">id:{{ currentNode.id }}</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
</RelationGraph> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { getGraphData } from '../../../neo4j.js' |
||||
|
import RelationGraph from 'relation-graph' |
||||
|
// https://relation-graph.com/#/docs/start |
||||
|
const graph_json_data = { |
||||
|
// 'rootId': 1, |
||||
|
'nodes': [ |
||||
|
{ id: '1', text: '文书档案', force_weight: 10000, color: '#ec6941', borderColor: '#ff875e', width: 150, height: 150 }, |
||||
|
|
||||
|
{ id: '2', text: '档案分类' }, |
||||
|
{ id: '21', text: '行政管理' }, |
||||
|
{ id: '22', text: '党群工作' }, |
||||
|
{ id: '23', text: '经营管理' }, |
||||
|
{ id: '24', text: '生成管理' }, |
||||
|
|
||||
|
{ id: '3', text: '保管期限' }, |
||||
|
{ id: '31', text: '永久' }, |
||||
|
{ id: '32', text: '定期30年' }, |
||||
|
{ id: '33', text: '定期10年' }, |
||||
|
|
||||
|
{ id: '4', text: '年度' }, |
||||
|
{ id: '41', text: '2022' }, |
||||
|
{ id: '42', text: '2023' }, |
||||
|
{ id: '43', text: '2024' }, |
||||
|
|
||||
|
{ id: '5', text: '密级' }, |
||||
|
{ id: '51', text: '公开' }, |
||||
|
{ id: '52', text: '限制' }, |
||||
|
{ id: '53', text: '秘密' }, |
||||
|
{ id: '54', text: '机密' }, |
||||
|
{ id: '55', text: '绝密' } |
||||
|
], |
||||
|
'lines': [ |
||||
|
{ from: '1', to: '2', text: 'class', dashType: 1 }, |
||||
|
{ from: '1', to: '3', text: 'retention', dashType: 2 }, |
||||
|
{ from: '1', to: '4', text: 'year', dashType: 3 }, |
||||
|
{ from: '1', to: '5', text: 'classification', dashType: 4 }, |
||||
|
{ from: '2', to: '21', text: 'class_01' }, |
||||
|
{ from: '2', to: '22', text: 'class_02' }, |
||||
|
{ from: '2', to: '23', text: 'class_03' }, |
||||
|
{ from: '2', to: '24', text: 'class_04' }, |
||||
|
{ from: '3', to: '31', text: 'retention_01' }, |
||||
|
{ from: '3', to: '32', text: 'retention_02' }, |
||||
|
{ from: '3', to: '33', text: 'retention_03' }, |
||||
|
{ from: '4', to: '41', text: 'year_01' }, |
||||
|
{ from: '4', to: '42', text: 'year_02' }, |
||||
|
{ from: '4', to: '43', text: 'year_03' }, |
||||
|
{ from: '5', to: '51', text: 'classification_01' }, |
||||
|
{ from: '5', to: '52', text: 'classification_02' }, |
||||
|
{ from: '5', to: '53', text: 'classification_03' }, |
||||
|
{ from: '5', to: '54', text: 'classification_04' }, |
||||
|
{ from: '5', to: '55', text: 'classification_05' } |
||||
|
] |
||||
|
} |
||||
|
export default { |
||||
|
name: 'Demo', |
||||
|
components: { RelationGraph }, |
||||
|
data() { |
||||
|
return { |
||||
|
allData: graph_json_data, |
||||
|
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', |
||||
|
// 'maxLayoutTimes': 10000, |
||||
|
// 'force_node_repulsion': 1.5, |
||||
|
'force_line_elastic': 0.5 |
||||
|
}, |
||||
|
allowSwitchLineShape: true, |
||||
|
allowSwitchJunctionPoint: true, |
||||
|
defaultLineColor: 'rgba(0,0,0,0.5)', |
||||
|
|
||||
|
defaultNodeBorderWidth: 0, |
||||
|
defaultNodeBorderColor: 'transpanret', |
||||
|
defaultNodeFontColor: '#000', // 节点颜色 |
||||
|
// defaultNodeColor: 'transparent', |
||||
|
// defaultNodeShape: 0, |
||||
|
defaultFocusRootNode: false // 默认是否选中根节点 |
||||
|
// lineUseTextPath: true |
||||
|
|
||||
|
}, |
||||
|
isLoading: false, |
||||
|
errorMessage: '', |
||||
|
nodes: [], |
||||
|
edges: [] |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
// this.showGraph() |
||||
|
this.resizeTimer = setInterval(async() => { |
||||
|
// const graphInstance = this.$refs.graphRef.getInstance(); |
||||
|
// await graphInstance.zoomToFit(); |
||||
|
}, 3000) |
||||
|
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) |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
clearInterval(this.resizeTimer) |
||||
|
}, |
||||
|
methods: { |
||||
|
async fetchData(query) { |
||||
|
this.isLoading = true |
||||
|
this.errorMessage = '' |
||||
|
try { |
||||
|
// 调用 getGraphData 函数获取数据 |
||||
|
|
||||
|
const { nodes, edges } = await getGraphData(query) |
||||
|
this.nodes = nodes |
||||
|
this.edges = edges |
||||
|
// 数据获取成功后调用渲染函数 |
||||
|
// this.renderGraph() |
||||
|
|
||||
|
// const colors = ['#FD8042', '#0348f3', '#F65163', '#563BE1', '#2B67DD', '#1AAE93', '#0FBED9', '#ffa4a4'] |
||||
|
|
||||
|
const colors = ['rgb(225,151,102)', 'rgb(194,159,87)', 'rgb(253,183,112)', 'rgb(127,181,123)', 'rgb(224,178,159)', 'rgb(196,149,72)', 'rgb(255,162,24)', 'rgb(189,237,229)'] |
||||
|
|
||||
|
const typeColorMap = {} |
||||
|
|
||||
|
this.allData.nodes = nodes.map(item => { |
||||
|
const newItem = { ...item, id: String(item.id) } |
||||
|
// if (item.type === 'CategoryClass') { |
||||
|
// // newItem.color = '#ec6941' |
||||
|
// // newItem.borderColor = '#ff875e' |
||||
|
// } else if (item.type === 'Category') { |
||||
|
// newItem.color = '#ec6941' |
||||
|
// newItem.borderColor = '#ff875e' |
||||
|
// } |
||||
|
|
||||
|
if (item.type) { |
||||
|
if (!typeColorMap[item.type]) { |
||||
|
typeColorMap[item.type] = colors[Object.keys(typeColorMap).length % colors.length] |
||||
|
} |
||||
|
newItem.color = typeColorMap[item.type] |
||||
|
newItem.borderColor = typeColorMap[item.type] |
||||
|
} |
||||
|
return newItem |
||||
|
}) |
||||
|
|
||||
|
this.allData.nodes.sort((a, b) => { |
||||
|
return parseInt(a.id) - parseInt(b.id) |
||||
|
}) |
||||
|
|
||||
|
this.allData.lines = edges.map(item => { |
||||
|
return { |
||||
|
...item, |
||||
|
from: String(item.from), |
||||
|
to: String(item.to) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
console.log('this.allData', this.allData) |
||||
|
this.showGraph() |
||||
|
} catch (error) { |
||||
|
// 捕获并处理错误 |
||||
|
this.errorMessage = `Error fetching data from Neo4j: ${error.message}` |
||||
|
} finally { |
||||
|
this.isLoading = false |
||||
|
} |
||||
|
}, |
||||
|
renderGraph() { |
||||
|
try { |
||||
|
const container = document.getElementById('graph-container') |
||||
|
console.log('container', container) |
||||
|
if (!container) { |
||||
|
throw new Error('Graph container not found') |
||||
|
} |
||||
|
const graph = new RelationGraph(container, { |
||||
|
nodes: this.nodes, |
||||
|
edges: this.edges |
||||
|
}) |
||||
|
console.lo('graph', graph) |
||||
|
// 调用渲染方法 |
||||
|
graph.render() |
||||
|
} catch (error) { |
||||
|
this.errorMessage = `Error rendering graph: ${error.message}` |
||||
|
} |
||||
|
}, |
||||
|
async showGraph() { |
||||
|
const rootId = this.allData.nodes[0].id |
||||
|
this.allData.rootId = rootId |
||||
|
// this.allData.nodes.forEach(n => { |
||||
|
// if (n.id !== rootId) { |
||||
|
// this.allData.lines.push({ from: rootId, to: n.id, opacity: 0 }) |
||||
|
// } |
||||
|
// }) |
||||
|
|
||||
|
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) |
||||
|
}, |
||||
|
// onNodeExpand(node, e) { |
||||
|
// // // 判断是否已经动态加载数据了 |
||||
|
// if (node.data.childrenLoaded) { |
||||
|
// console.log('这个节点的子节点已经加载过了') |
||||
|
// return |
||||
|
// } |
||||
|
// // 根据具体的业务需要决定是否需要从后台加载数据 |
||||
|
// if (!node.data.isNeedLoadDataFromRemoteServer) { |
||||
|
// console.log('是否需要从后台加载数据') |
||||
|
// alert('未查到更多相关信息!') |
||||
|
// return |
||||
|
// } |
||||
|
// }, |
||||
|
onNodeClick(nodeObject, $event) { |
||||
|
console.log('节点数据:', nodeObject) |
||||
|
const nodeId = nodeObject.id |
||||
|
const query = `MATCH (n) WHERE id(n) = ${nodeId} MATCH (n)-[r]-(m) RETURN n, r,m;` |
||||
|
this.fetchData(query) |
||||
|
console.log('query', query) |
||||
|
|
||||
|
const _all_nodes = this.$refs.graphRef.getInstance().getNodes() |
||||
|
// const _all_lines = this.$refs.graphRef.getLines() |
||||
|
const { lot } = nodeObject |
||||
|
// 子集高亮 |
||||
|
if (lot.childs && lot.childs.length) { |
||||
|
_all_nodes.forEach((thisNode) => { |
||||
|
let _isHideThisNode = false |
||||
|
lot.childs.forEach((childNode) => { |
||||
|
if (thisNode.id === childNode.id || thisNode.id === nodeObject.id) { |
||||
|
_isHideThisNode = true |
||||
|
} |
||||
|
if (lot.parent && thisNode.id === lot.parent.id) { |
||||
|
_isHideThisNode = true |
||||
|
} |
||||
|
}) |
||||
|
thisNode.opacity = _isHideThisNode ? 1 : 0.1 |
||||
|
}) |
||||
|
} else { |
||||
|
// 父级及本身高亮 |
||||
|
_all_nodes.forEach((thisNode) => { |
||||
|
let _isHideThisNode = false |
||||
|
if (thisNode.id === nodeObject.id) { |
||||
|
_isHideThisNode = true |
||||
|
} |
||||
|
if (lot.parent && thisNode.id === lot.parent.id) { |
||||
|
_isHideThisNode = true |
||||
|
} |
||||
|
thisNode.opacity = _isHideThisNode ? 1 : 0.1 |
||||
|
}) |
||||
|
} |
||||
|
// console.log('onNodeClick:', nodeObject) |
||||
|
// const allChildIds = this.deepGeAlltChildIds(nodeObject) |
||||
|
// console.log('allChildIds', allChildIds) |
||||
|
// const graphInstance = this.$refs.graphRef.getInstance() |
||||
|
// for (const node of graphInstance.getNodes()) { |
||||
|
// if (allChildIds.includes(node.id)) { |
||||
|
// node.opacity = 1 |
||||
|
// node.color = 'rgb(116,2,173)' |
||||
|
// } else { |
||||
|
// node.opacity = 0.1 |
||||
|
// node.color = undefined |
||||
|
// } |
||||
|
// } |
||||
|
// for (const link of graphInstance.getLinks()) { |
||||
|
// if (allChildIds.includes(link.fromNode.id) && allChildIds.includes(link.toNode.id)) { |
||||
|
// link.relations.forEach(line => { |
||||
|
// line.color = 'rgb(116,2,173)' |
||||
|
// }) |
||||
|
// } else { |
||||
|
// link.relations.forEach(line => { |
||||
|
// line.color = '' |
||||
|
// }) |
||||
|
// } |
||||
|
// } |
||||
|
}, |
||||
|
// deepGeAlltChildIds(node, ids = []) { |
||||
|
// if (ids.includes(node.id)) return |
||||
|
// ids.push(node.id) |
||||
|
// // node.lot.childs |
||||
|
// // node.targetNodes |
||||
|
// console.log('node.lot.childs', node.lot.childs) |
||||
|
// for (const cNode of node.lot.childs) { |
||||
|
// this.deepGeAlltChildIds(cNode, ids) |
||||
|
// } |
||||
|
// return ids |
||||
|
// }, |
||||
|
onCanvasClick($event) { |
||||
|
// console.log('onCanvasClick:') |
||||
|
// const graphInstance = this.$refs.graphRef.getInstance() |
||||
|
// for (const node of graphInstance.getNodes()) { |
||||
|
// node.opacity = 1 |
||||
|
// this.allData.nodes.forEach((item, index) => { |
||||
|
// if (item.id === node.id) { |
||||
|
// node.color = item.color |
||||
|
// } |
||||
|
// }) |
||||
|
// } |
||||
|
// for (const link of graphInstance.getLinks()) { |
||||
|
// link.relations.forEach(line => { |
||||
|
// line.color = undefined |
||||
|
// }) |
||||
|
// } |
||||
|
|
||||
|
const _all_nodes = this.$refs.graphRef.getInstance().getNodes() |
||||
|
_all_nodes.forEach((thisNode) => { |
||||
|
const _isHideThisNode = true |
||||
|
thisNode.opacity = _isHideThisNode ? 1 : 0.1 |
||||
|
}) |
||||
|
}, |
||||
|
// onLineClick(lineObject, linkObject, $event) { |
||||
|
// console.log('onLineClick:', lineObject) |
||||
|
// }, |
||||
|
showNodeTips(nodeObject, $event) { |
||||
|
this.currentNode = nodeObject |
||||
|
const _base_position = this.$refs.graphRef.getInstance().options.fullscreen ? { x: 0, y: 0 } : this.$refs.myPage.getBoundingClientRect() |
||||
|
// console.log('showNodeMenus:', this.$refs.graphRef.getInstance().options.fullscreen, $event.clientX, $event.clientY, _base_position) |
||||
|
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> |
||||
|
</style> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
::v-deep .rel-toolbar{ |
||||
|
color: #000; |
||||
|
.c-current-zoom{ |
||||
|
color: #000; |
||||
|
} |
||||
|
} |
||||
|
// .my-graph{ |
||||
|
// // background: linear-gradient(to right, rgb(16, 185, 129), rgb(101, 163, 13)); |
||||
|
// } |
||||
|
|
||||
|
.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; |
||||
|
// fill: red !important; |
||||
|
} |
||||
|
|
||||
|
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue