<template>
|
<div style="width: 100%; height: 100%">
|
<titleBoxVue style="background-color: #fff" :title="titleName" v-if="showTitle">
|
<template v-slot:left>
|
<el-button
|
icon="ele-ArrowLeft"
|
text
|
style="margin-right: 10px; margin-left: 10px; width: 40px"
|
size="small"
|
@click="emits('handleExitFlow')"
|
>
|
</el-button>
|
</template>
|
</titleBoxVue>
|
<div id="mountNode" style="width: 100%; height: 100%"></div>
|
<pre v-if="showTip && tooltip && !isMouseDown" class="g6-tooltip" :style="`top: ${top}px; left: ${left}px;`">{{ tooltip }}</pre>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import type { Graph, Item, Node, NodeConfig } from '@antv/g6';
|
import G6 from '@antv/g6';
|
import type { Ref } from 'vue';
|
import { nextTick, onMounted, onUnmounted, ref, toRefs } from 'vue';
|
import registerFactory from '/@/components/g6Display/graph/graph';
|
import { SHAPE2TYPE, shapeWithInfo, x6ToG6 } from '/@/components/g6Display/utils';
|
import { $ } from '/@/utils/istation/common';
|
import type {
|
CatalogFaultEvent,
|
EventNode,
|
EventNodeResponse,
|
DiagnosisListView,
|
FaultEventView,
|
DiagnosisListOnline,
|
Mcs,
|
Paras,
|
PureFaultEvent,
|
} from '/@/projectCom/fault/types';
|
import { DiagnosisListResponse, EventEnum, McsView } from '/@/projectCom/fault/types';
|
import { GetInheritAndSelfByCatalogID, GetFaultTreeStructure, GetViewVersionContent } from '/@/api/fault/faultTreeInfo';
|
import { ElMessage } from 'element-plus';
|
import { deepClone } from '/@/utils/other';
|
import titleBoxVue from '/@/components/titleBox.vue';
|
import { GetVersionContentByVersionID } from '/@/api/fault/faultDiagnosis/diagnosisOnline';
|
import type { AxiosResponse } from 'axios';
|
|
let graph: Graph = null;
|
const isMouseDown = ref(false);
|
const tooltip = ref('');
|
const top = ref(0);
|
const left = ref(0);
|
const CorpID = 13;
|
const currentCatalogEvents: Ref<CatalogFaultEvent[]> = ref([]);
|
|
const props = defineProps({
|
catalogID: String,
|
VersionID: String,
|
x6CellsJSON: Object,
|
titleName: String,
|
ProductID: String,
|
showTitle: {
|
type: Boolean,
|
default: () => true,
|
},
|
/** 在线诊断需要从别处接口获取数据 */
|
isOnline: {
|
type: Boolean,
|
default: null,
|
},
|
/**是否悬浮展示信息 */
|
showTip: {
|
type: Boolean,
|
default: false,
|
},
|
});
|
// const props = defineProps(['catalogID', 'VersionID', 'x6CellsJSON', 'titleName', 'showTitle']);
|
const { x6CellsJSON, catalogID, VersionID, titleName, showTitle, isOnline, ProductID, showTip } = toRefs(props);
|
const emits = defineEmits(['handleExitFlow', 'updateNodesData', 'nodeSelect', 'canvasClick']);
|
const mcsList = ref([] as Mcs[]);
|
const diagnosisList = ref([] as DiagnosisListOnline[]);
|
defineExpose({
|
graph,
|
mcsList,
|
diagnosisList,
|
});
|
// const g6Data = x6ToG6(x6JSONData1);
|
onMounted(() => {
|
nextTick(async () => {
|
!isOnline.value && (await getCatalogEvents());
|
createGraphic();
|
initGraphEvent();
|
|
// console.log(graph.save(), 923);
|
});
|
});
|
onUnmounted(() => {
|
graph.destroy();
|
});
|
const createGraphic = async () => {
|
let { width, height } = $('#mountNode').getBoundingClientRect();
|
// 不确定为何总是多出宽高
|
width = width - 5;
|
height = height - 5;
|
const grid = new G6.Grid();
|
const menu = new G6.Menu({
|
offsetX: -20,
|
offsetY: -50,
|
itemTypes: ['node', 'edge'],
|
getContent(e) {
|
const outDiv = document.createElement('div');
|
|
outDiv.style.width = '80px';
|
outDiv.style.cursor = 'pointer';
|
outDiv.innerHTML = '<p id="deleteNode">删除节点</p>';
|
return outDiv;
|
},
|
handleMenuClick(target, item) {
|
const { id } = target;
|
|
if (id) {
|
deleteNode(item);
|
}
|
},
|
});
|
// const minimap = new G6.Minimap({
|
// size: [200, 100],
|
// });
|
const cfg = registerFactory(G6, {
|
container: 'mountNode',
|
width: width,
|
height: height,
|
// renderer: 'svg',
|
layout: {
|
type: '', // 位置将固定
|
},
|
// 所有节点默认配置
|
defaultNode: {
|
type: 'rect-node',
|
style: {
|
radius: 10,
|
width: 100,
|
height: 50,
|
cursor: 'move',
|
fill: '#ecf3ff',
|
},
|
labelCfg: {
|
fontSize: 20,
|
style: {
|
cursor: 'move',
|
},
|
},
|
},
|
// 所有边的默认配置
|
defaultEdge: {
|
type: 'polyline-edge', // 扩展了内置边, 有边的事件
|
style: {
|
radius: 5,
|
offset: 15,
|
stroke: '#aab7c3',
|
lineAppendWidth: 10, // 防止线太细没法点中
|
endArrow: true,
|
},
|
},
|
// 覆盖全局样式
|
nodeStateStyles: {
|
'nodeState:default': {
|
opacity: 1,
|
},
|
'nodeState:hover': {
|
opacity: 0.8,
|
// cursor: 'pointer',
|
},
|
'nodeState:selected': {
|
opacity: 0.9,
|
},
|
},
|
// 默认边不同状态下的样式集合
|
edgeStateStyles: {
|
'edgeState:default': {
|
stroke: '#aab7c3',
|
},
|
'edgeState:selected': {
|
stroke: '#1890FF',
|
},
|
'edgeState:hover': {
|
animate: false,
|
animationType: 'ball',
|
stroke: '#1890FF',
|
},
|
},
|
modes: {
|
// 支持的 behavior
|
default: [
|
'drag-canvas',
|
'drag-shadow-node',
|
'canvas-event',
|
'delete-item',
|
'select-node',
|
'hover-node',
|
'active-edge',
|
'zoom-canvas',
|
],
|
originDrag: [
|
'drag-canvas',
|
'drag-node',
|
'canvas-event',
|
// 'delete-item',
|
'select-node',
|
// 'hover-node',
|
'active-edge',
|
'zoom-canvas',
|
],
|
},
|
plugins: [menu, grid],
|
// ... 其他G6原生入参
|
});
|
// // 创建 G6 图实例
|
// graph = new G6.Graph({
|
// container: container, // 指定图画布的容器 id,与第 9 行的容器对应
|
// // 画布宽高
|
// width: 800,
|
// height: 500
|
// })
|
|
graph = new G6.Graph(cfg);
|
// console.log(g6Data, 523);
|
const g6Data = x6ToG6(x6CellsJSON.value);
|
|
graph.read(g6Data); // 读取数据
|
const graphNodesJSON = graph.save().nodes as NodeConfig[];
|
if (!isOnline.value) {
|
// initFaultTreeStructure(graphNodesJSON);
|
const res = await GetViewVersionContent({
|
CorpID,
|
ID: VersionID.value,
|
});
|
initFaultTreeStructure1(res, graphNodesJSON);
|
} else {
|
const res = await GetVersionContentByVersionID({
|
CorpID,
|
VersionID: VersionID.value,
|
ProductID: ProductID.value,
|
});
|
initFaultTreeStructure1(res, graphNodesJSON);
|
}
|
graph.setMode('originDrag');
|
(window as any).$welabxG6 = graph;
|
};
|
/** 按 antvx6id 查找事件树中的某个结点 */
|
function findNode<T extends { Paras?: Paras; Children?: T[] }>(root: T, antvx6id: string): T {
|
let result = null;
|
const find = (node: T, antvx6id: string) => {
|
if (node.Paras.antvx6id === antvx6id) {
|
result = node;
|
return;
|
}
|
if (node.Children && node.Children.length > 0) {
|
for (const child of node.Children) {
|
find(child, antvx6id);
|
}
|
}
|
};
|
find(root, antvx6id);
|
return result;
|
}
|
|
const getCatalogEvents = async () => {
|
const res = await GetInheritAndSelfByCatalogID({
|
CorpID: CorpID,
|
CatalogID: catalogID.value,
|
});
|
if (res && res.Code === 0) {
|
if (res.Data) {
|
currentCatalogEvents.value = res.Data;
|
}
|
} else {
|
ElMessage.error('获取故障组事件失败');
|
}
|
};
|
/** 整合展示数据以及业务数据,方便 antv g6 组件操作
|
*
|
*/
|
const combineDisplayData = (nodes: NodeConfig[], faultNode: EventNodeResponse) => {
|
for (const node of nodes) {
|
if (shapeWithInfo.includes(node.type)) {
|
const foundNode = findNode(faultNode, node.id);
|
if (foundNode) {
|
(node.data as any).eventInfo = foundNode;
|
const foundIndex = currentCatalogEvents.value.findIndex((item) => item.ID === foundNode.EventID);
|
|
if (foundIndex !== -1) {
|
const mapInfo = currentCatalogEvents.value[foundIndex];
|
Reflect.deleteProperty(mapInfo, 'CorpID');
|
(node.data as any).dataInfo = mapInfo;
|
}
|
}
|
}
|
}
|
};
|
|
/**
|
*
|
* 将 EventList,DiagnosisList,Content 整合进展示数据中
|
* 注意:需要在展示数据生效后整合,否则Children 无法指向原来的对象
|
*/
|
const combineDisplayData1 = (
|
displayNodes: NodeConfig[],
|
eventNode: EventNode,
|
event: PureFaultEvent[] | FaultEventView[],
|
diagnosisList: DiagnosisListOnline[] | DiagnosisListView[]
|
) => {
|
for (const node of displayNodes) {
|
if (shapeWithInfo.includes(node.type)) {
|
const foundNode = findNode(eventNode, node.id);
|
if (foundNode) {
|
(node.data as any).eventInfo = foundNode;
|
const foundIndex = event.findIndex((item) => item.ID === foundNode.EventID);
|
|
if (foundIndex !== -1) {
|
const mapInfo = event[foundIndex];
|
(node.data as any).dataInfo = mapInfo;
|
}
|
// 匹配诊断项
|
if (foundNode.EventType === EventEnum.Bottom) {
|
(node.data as any).diagnosisList = (diagnosisList as any).filter((item) => item.OrgID === foundNode.ID);
|
}
|
}
|
}
|
}
|
};
|
|
const initFaultTreeStructure = async (nodes: NodeConfig[]) => {
|
const res = await GetFaultTreeStructure({
|
CorpID: CorpID,
|
ID: VersionID.value,
|
});
|
if (res && res.Code === 0) {
|
if (res.Data) {
|
const topEventNode = res.Data as EventNodeResponse;
|
combineDisplayData(nodes, topEventNode);
|
}
|
} else {
|
ElMessage.error('获取节点信息失败');
|
}
|
};
|
|
const initFaultTreeStructure1 = async (res: AxiosResponse<any, any>, nodes: NodeConfig[]) => {
|
if (res?.Code === 0) {
|
if (res?.Data) {
|
const { Content, McsList, EventList, DiagnosisList } = res.Data;
|
mcsList.value = McsList;
|
diagnosisList.value = DiagnosisList;
|
combineDisplayData1(nodes, Content, EventList, DiagnosisList);
|
}
|
} else {
|
console.log('失败了');
|
|
ElMessage.error('获取版本内容失败');
|
}
|
};
|
|
// 初始化图事件
|
const initGraphEvent = () => {
|
graph.on('node:drop', (e) => {
|
(e.item as Node).getOutEdges().forEach((edge) => {
|
edge.clearStates('edgeState');
|
});
|
});
|
|
graph.on('on-node-mouseenter', (e) => {
|
if (e && e.item) {
|
(e.item as Node).getOutEdges().forEach((edge) => {
|
edge.clearStates('edgeState');
|
edge.setState('edgeState', 'hover');
|
});
|
}
|
});
|
|
// 鼠标拖拽到画布外时特殊处理
|
graph.on('mousedown', (e) => {
|
isMouseDown.value = true;
|
});
|
graph.on('mouseup', (e) => {
|
isMouseDown.value = false;
|
});
|
graph.on('canvas:mouseleave', (e) => {
|
graph.getNodes().forEach((x) => {
|
const group = x.getContainer();
|
|
(group as any).clearAnchor();
|
x.clearStates('anchorActived');
|
});
|
});
|
|
graph.on('after-node-selected', (e) => {
|
// this.configVisible = !!e;
|
|
if (e && e.item) {
|
const model = e.item.get('model');
|
emits('nodeSelect', model);
|
}
|
});
|
|
graph.on('on-canvas-click', (e) => {
|
emits('canvasClick', e);
|
});
|
|
graph.on('on-node-mousemove', (e) => {
|
if (e && e.item) {
|
// console.log(e.item.get('model').data, 'data');
|
if (e.item.get('model').data?.dataInfo) {
|
const { Name, Code, Flags, TagName, Advice, Description } = e.item.get('model').data?.dataInfo;
|
const { DiagnosisList } = e.item.get('model').data?.eventInfo;
|
let diagnosisStr = '';
|
// if (DiagnosisList) {
|
// diagnosisStr = '诊断项:' + JSON.stringify(DiagnosisList);
|
// }
|
tooltip.value = `
|
名称:${Name}
|
编码: ${Code}
|
标签列表:${Flags}
|
标签:${TagName}
|
建议:${Advice}
|
说明:${Description}
|
${diagnosisStr}
|
`;
|
}
|
left.value = e.clientX + 40;
|
top.value = e.clientY - 20;
|
}
|
});
|
|
graph.on('on-node-mouseleave', (e) => {
|
if (e && e.item) {
|
tooltip.value = '';
|
if (e && e.item) {
|
(e.item as Node).getOutEdges().forEach((edge) => {
|
edge.clearStates('edgeState');
|
});
|
}
|
}
|
});
|
|
graph.on('before-node-removed', ({ target, callback }) => {
|
console.log(target);
|
setTimeout(() => {
|
// 确认提示
|
(callback as any)(true);
|
}, 1000);
|
});
|
|
graph.on('after-node-dblclick', (e) => {
|
if (e && e.item) {
|
console.log(e.item);
|
}
|
});
|
|
graph.on('after-edge-selected', (e) => {
|
if (e && e.item) {
|
graph.updateItem(e.item, {
|
// shape: 'line-edge',
|
style: {
|
radius: 10,
|
lineWidth: 2,
|
},
|
});
|
}
|
});
|
|
graph.on('on-edge-mousemove', (e) => {
|
if (e && e.item) {
|
tooltip.value = e.item.get('model').label;
|
left.value = e.clientX + 40;
|
top.value = e.clientY - 20;
|
}
|
});
|
|
graph.on('on-edge-mouseleave', (e) => {
|
if (e && e.item) {
|
tooltip.value = '';
|
}
|
});
|
|
graph.on('before-edge-add', ({ source, target, sourceAnchor, targetAnchor }) => {
|
setTimeout(() => {
|
graph.addItem('edge', {
|
id: `${+new Date() + (Math.random() * 10000).toFixed(0)}`, // edge id
|
source: (source as any).get('id'),
|
target: target.get('id'),
|
sourceAnchor,
|
targetAnchor,
|
// label: 'edge label',
|
});
|
}, 100);
|
});
|
};
|
const deleteNode = (item: Item) => {
|
graph.removeItem(item);
|
};
|
</script>
|
<style scoped>
|
.g6-tooltip {
|
position: fixed;
|
top: 0;
|
left: 0;
|
font-size: 12px;
|
color: #545454;
|
border-radius: 4px;
|
border: 1px solid #e2e2e2;
|
background-color: rgba(255, 255, 255, 0.9);
|
box-shadow: rgb(174, 174, 174) 0 0 10px;
|
padding: 0px 8px;
|
}
|
</style>
|