This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nezha-nezha-fronted/nezha-fronted/src/components/common/project/topologyL5.vue
2021-03-31 15:26:01 +08:00

2648 lines
82 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style>
@import "./L5/css/iconfont.css";
@import "./L5/css/props.css";
</style>
<template>
<div class="project-box" v-loading="topologyLoading" :style="{'border':fromOverView?'none':'1px solid #eeeeee'}">
<!--project主要信息-->
<div class="project-title" v-if="showTopTools&&!fromOverView" :style="{'background':editTopologyFlag?'#F6F6F6':'#ffffff','border-bottom':editTopologyFlag? '1px solid #F6F6F6':'' }">
<div v-show="editTopologyFlag" class="edit-topologyLine" style="padding-left: 20px">
<!--工具栏-->
<span class="project-topology-tool">
<el-dropdown trigger="click" size="small" placement="bottom-start">
<span class="el-dropdown-title"><i class="iconfont icon-cube"></i> <i
class="nz-icon nz-icon-arrow-down"></i></span>
<el-dropdown-menu slot="dropdown" @click="dropdownClick">
<div style="height: 450px">
<el-card shadow="hover" style="height:420px;width:284px;overflow-y: auto"
class="project-topology-add-node">
<!--<div class="drag-header"></div>-->
<el-collapse v-model="activeNames" v-for="(item, index) in tools" :key="index">
<el-collapse-item :title="item.group" :name="item.group">
<div v-for="(btn, i) in item.children" :key="'info2'+'-'+index+'-'+i" class="buttons">
<a
:key="i"
:title="btn.data.text"
:draggable="btn.data"
@dragstart.stop="onDrag($event, btn)"
@mousedown="dragFlag=false"
@mouseup="dragFlagChange(btn)"
class="btn"
>
<img :src="btn.data.image" v-if="btn.data.image">
<i v-else :class="`iconfont ${btn.icon}`"></i>
</a>
<span v-if="item.group==='自定义图片'" class="delIcon" @click="delImg(btn)">x</span>
</div>
</el-collapse-item>
</el-collapse>
</el-card>
<div class="upload-pic-box" @click="uploadPicChange">
<i class="el-icon-plus"></i>
<span>
Upload custom picture
</span>
</div>
</div>
</el-dropdown-menu>
</el-dropdown>
<div class="flex middle special-select mb10"
style="width: 75px;height: 28px;display: inline-block;margin: 0 40px 0 20px;background: #fff">
<div class="full pr10">
<el-select v-model="lineName" size="small"
:popper-append-to-body="false"
@change="changeTopologyOpt(lineName,'lineName')">
<div slot="prefix">
<div class="icon-item" style="width: 100%;padding: 0">
<svg>
<g fill="none" stroke="black" stroke-width="1">
<path
:d="penLineType.find((item,i)=>item.id==lineName).d"
>
</path>
</g>
</svg>
</div>
</div>
<el-option :disabled="true" :value="false">{{$t('project.topology.defaultLineType')}}</el-option>
<el-option v-for="(item,index) in penLineType" :value="item.id" :key="index">
<div class="icon-item" style="position: relative;width: 100%;padding: 0">
<svg>
<g fill="none" :stroke="(lineName==item.name)?'#ee9d3f':'black'" stroke-width="1">
<path :d="item.d" :stroke-dasharray="item['stroke-dasharray']"></path>
</g>
</svg>
<span style="position: absolute;left:60px;top: 0;">{{item.name}}</span>
</div>
</el-option>
</el-select>
</div>
</div>
<topology-top-tool
v-if="editTopologyFlag&&toolShow.topTool"
:selection.sync="props"
@del="delPen"
:index="topologyIndex"
ref="topTool"
@toolShowChange="toolShowChange"
:cachesIndex="cachesIndex"
:redoIndexChange="redoIndexChange"
:toolShow="toolShow">
</topology-top-tool>
</span>
<span class="float-right">
<button @click="previewTopology" class="nz-btn nz-btn-size-normal-new nz-btn-style-light-new"
style="margin-right: 20px"
>
{{$t('project.topology.preview')}}
</button>
<button @click="saveTopology" class="nz-btn nz-btn-size-normal-new nz-btn-style-normal-new"
v-has="'project_topo_save'" :disabled="prevent_opt.save"
:class="{'nz-btn-disabled':prevent_opt.save}"
style="margin-right: 20px">
{{$t('project.topology.save')}}
</button>
<button @click="cancelTopology" class="nz-btn nz-btn-size-normal-new nz-btn-style-normal-new" style="margin-right: 20px">
{{$t('project.topology.exit')}}
</button>
</span>
</div>
<div style="width: 100%;display: flex;justify-content: space-between" v-if="!editTopologyFlag&&!fromOverView">
<div class="facade-top">
<div class="facade-top-left" v-loading="projectInfo.loading" v-if="projectInfoShow">
<div class="facade-top-title">
Project information
</div>
<div>
<!--<span><span class="label">Id :</span>{{projectInfo.id}}</span>-->
<span><span class="label">Name :</span>{{projectInfo.name}}</span>
</div>
<div>
<span><span class="label">Description :</span>{{projectInfo.remark?projectInfo.remark:'--'}}</span>
</div>
<!--<div>-->
<!--<span>-->
<!--<span class="label">Alert state :</span>-->
<!--<div class="active-icon" style="background: #B7464A 100%;"></div>{{projectInfo.alertStat[0]}}-->
<!--<div class="active-icon" style="background: #E64E4E 100%;"></div>{{projectInfo.alertStat[1]}}-->
<!--<div class="active-icon" style="background: #F7B500 100%;"></div>{{projectInfo.alertStat[2]}}-->
<!--</span>-->
<!--</div>-->
<div>
<span><span class="label">Module Num :</span>{{projectInfo.moduleMum}}</span>
</div>
</div>
<div class="facade-top-right" v-loading="projectInfo.loading" v-if="projectAlertShow" style="padding: 20px 20px 0 20px;height: calc(100% - 20px);">
<div class="facade-top-title">
<span class="label" style="padding-left: 0;">Alert :</span>
{{projectInfo.total}}
</div>
<div class="facade-top-right-content">
<div>
<div class="content-P1-title">
{{returnSeverityLabel('P1')}}
</div>
<div>
{{projectInfo.alertStat[0] || 0}}
</div>
</div>
<div>
<div class="content-P2-title">
{{returnSeverityLabel('P2')}}
</div>
<div>
{{projectInfo.alertStat[1] || 0}}
</div>
</div>
<div style="margin-bottom: 20px;">
<div class="content-P3-title">
{{returnSeverityLabel('P3')}}
</div>
<div>
{{projectInfo.alertStat[2] || 0}}
</div>
</div>
</div>
</div>
</div>
<div :style="{flex:1,'padding-right': '15px','text-align':topologyInfo.align,'font-size':topologyInfo.fontSize,'color':topologyInfo.fontColor,opacity:topologyInfo.opacity}">{{topologyInfo.name}}</div>
<span class="edit-topologyLine" style="padding-top: 5px" v-show="!editTopologyFlag&&!fromPrev&&!fromOverView">
<button @click="editTopology" class="nz-btn nz-btn-size-normal nz-btn-style-light float-right"
style="border-right: 1px solid rgba(162,162,162,0.50);margin-right: 12px;margin-top: -2px" type="button" v-has="'project_topo_save'"
>
<i class="nz-icon nz-icon-edit" :title="$t('project.topology.edit')"></i>
</button>
<pick-time
v-show="!editTopologyFlag"
class="float-right pickTime"
:refresh-data-func="dateChange"
v-model="searchTime"
:use-chart-unit="false"
ref="pickTime">
</pick-time>
</span>
</div>
</div>
<div :class="['page',fromOverView?'overview-page':'']">
<!--画布部分-->
<div :id="'topology-canvas' + topologyIndexF" class="full" :ref="'topology-canvas'+ topologyIndexF"></div>
<!--设置属性-->
<div class="props" v-if="editTopologyFlag&&toolShow.attr">
<CanvasProps :selection.sync="props"
@change="onUpdateProps"
@animate="animateCanvas"
@changeProjectTitle="changeProjectTitle"
@notModuleIDArrChange="notModuleIDArrChange"
:index="topologyIndex"
@del="delPen"
:modules="modules"
ref="CanvasProps">
</CanvasProps>
</div>
<!--所有节点上的小图标-->
<div v-for="(item,index) in nodesArr" :key="index"
:style="{position: 'absolute',top:item.rect.y - (48*(fromOverView?penToolTipScale:1))+'px',left:item.rect.center.x - (24*(fromOverView?penToolTipScale:1)) +'px',transform:'scale('+(fromOverView?penToolTipScale:1)+')'}"
v-if="!editTopologyFlag&&item.data.iconToolState&&!fromPrev"
class="network-pop"
>
<i
:class="{'nz-icon':true, 'nz-icon-shuidi':true,'model-error':item.data.state&&item.data.state.error&&!item.data.show,'model-error-active':item.data.state&&item.data.state.error&&item.data.show}"
:ref="'modelTopId'+index"
@click="showNodeTools(index,item)"
>
<i class="nz-icon nz-icon-model"></i>
</i>
<!--'selpop':selpopIs(item),'no-selPop':!selpopIs(item),'error-model-stat':modelPopError(item) @click="popClick(item.id)" -->
<div v-for="(item1, index) in popData" :key="index">
<transition name="scaleTool">
<i v-if="item.data.show"
:class="{'nz-icon':true,'nz-icon-hexagonBorder':true,'error-model-stat':item.data.state[item1.id],'selpop':selpopIs(item,item1),'no-selPop':!selpopIs(item,item1),}"
:style="{top:item1.top,left:item1.left}"
:title="item1.title"
@click="nodeTools(item,item1)"
>
<i class="nz-icon nz-icon-liubianxing"></i>
<i :class="[item1.className,{'nz-icon':item1.className},'noMove']"></i>
</i>
</transition>
</div>
</div>
<!--提示未添加module id的块-->
<div v-for="(item, index) in notModuleIDArr"
:key="index"
:style="{
position: 'absolute',
top:item.rect.y- 10 +'px',
left:item.rect.x - 10+'px',
transform:'scale('+(fromOverView?penToolTipScale:1)+')',
}"
v-if="editTopologyFlag&&!fromPrev"
>
<div class="module-rect-top" :style="{top:0,left:0,width:item.rect.width+15+'px',height:0}"></div>
<div class="module-rect-right" :style="{top:0,left:item.rect.width+15+'px',width:0,height:item.rect.height+15+'px'}"></div>
<div class="module-rect-bottom" :style="{top:item.rect.height+15 +'px',left:0,width:item.rect.width+15+'px',height:0}"></div>
<div class="module-rect-left" :style="{top:0,left:0,width:0,height:item.rect.height+15+'px'}"></div>
</div>
<!--节点连线相关的 tooltip-->
<div :style="{position:'absolute',top:tooltipPosition.top+'px',left:tooltipPosition.left+'px','z-index':10,height:tooltipPosition.height+'px'}"
v-if="tooltipPosition.show&&!editTopologyFlag"
@mouseover="tooltipOver"
@mouseout="tooltipOut"
ref="topoTooltip"
>
<topoTooltip :chartDataParent="chartData" :filterTime="filterTime"/>
</div>
</div>
<!--<div class="left-bottom" v-if="editTopologyFlag">-->
<!--<div class="title">小提示</div>-->
<!--<ul class="group">-->
<!--<li>编辑时</li>-->
<!--<li>1.Ctrl + 鼠标移动移动整个画布</li>-->
<!--<li>2.Ctrl + 鼠标滚轮缩放</li>-->
<!--<li>3.选中元素 按下Delete键或者Backspace可以删除元素</li>-->
<!--</ul>-->
<!--</div>-->
<!--悬浮network部分-->
<div class="network-info">
<div v-if="popDataShow.main">
<popDataMain :moduleId="moduleId" :projectId="projectInfo.id"></popDataMain>
</div>
<div v-if="popDataShow.info">
<popDataInfo :moduleId="moduleId" :projectId="projectInfo.id"></popDataInfo>
</div>
</div>
<!--endpoint-->
<transition name="right-box">
<endpointTable v-if="popDataShow.endpoint" :moduleId="moduleId" :projectId="projectInfo.id"
@close="popDataShowUpdate('',true)">endpoint
</endpointTable>
</transition>
<!--asset-->
<transition name="right-box">
<assetTable v-if="popDataShow.asset" :moduleId="moduleId" :projectId="projectInfo.id"
@close="popDataShowUpdate('',true)">alert
</assetTable>
</transition>
<!--alert-->
<transition name="right-box">
<alertTable v-if="popDataShow.alert" :moduleId="moduleId" :projectId="projectInfo.id"
@close="popDataShowUpdate('',true)">alert
</alertTable>
</transition>
<!--preview-->
<el-dialog
:visible.sync="previewShow"
:width="'80%'"
:before-close="previewBeforeClose"
>
<div style="width: calc(80vw - 40px);height: 80vh">
<topologyPrev
v-if="previewShow"
:obj="obj"
:topoPrevDataS="topoPrevData"
:fromOverView="false"
:fromPrev="true"
:topologyIndexF="1">
</topologyPrev>
</div>
</el-dialog>
<!--Custom picture-->
<el-dialog
title="Custom picture"
:visible.sync="uploadPicShow"
width="auto"
@close="uploadPicShow = false"
destroy-on-close>
<el-row class="upload-pic-row">
<el-col :span="4" class="upload-pic-label">Name</el-col>
<el-col :span="20">
<el-input v-model="uploadPic.name" size="small" :placeholder="$t('project.topology.placeholderImg')"></el-input>
</el-col>
</el-row>
<el-row class="upload-pic-row">
<el-col :span="4" class="upload-pic-label">Folder</el-col>
<el-col :span="20">
<el-autocomplete
class="inline-input"
v-model="uploadPic.unit"
:fetch-suggestions="querySearch"
size="small"
></el-autocomplete>
</el-col>
</el-row>
<el-row class="upload-pic-row">
<el-col :span="4" class="upload-pic-label"> </el-col>
<el-col :span="20">
<div class="upload-body">
<el-upload
drag
class="upload-demo"
action=" "
:show-file-list="true"
:on-change="beforeAvatarUpload"
:auto-upload="false"
accept=".jpg,.png"
:limit="1"
:id="'upload-pic-show'">
<!--<div slot="tip" class="el-upload__tip" >{{$t('overall.importTipImg')}}</div>-->
<i class="nz-icon nz-icon-upload"></i>
<div class="el-upload__text">{{$t('overall.dragFileTip')}}{{$t('overall.or')}}&nbsp;<em>{{$t('overall.clickUpload')}}</em></div>
</el-upload>
</div>
</el-col>
</el-row>
<div class="upload-pic-row" style="text-align: center">
<span>
<button @click="uploadPicShow=false" class="nz-btn nz-btn-size-normal-new nz-btn-style-light-new" style="margin-right: 20px">
{{$t('project.topology.exit')}}
</button>
<button @click="imgUpload" class="nz-btn nz-btn-size-normal-new nz-btn-style-normal-new"
v-has="'project_topo_save'" :disabled="prevent_opt.save"
:class="{'nz-btn-disabled':prevent_opt.save}"
style="margin-right: 20px">
{{$t('project.topology.save')}}
</button>
</span>
</div>
</el-dialog>
<div class="right-bottom-zoom" v-if="!fromOverView&&!editTopologyFlag&&!fromPrev">
<div class="zoom-option" style="border-bottom: 1px solid #c5c8cb;" @click="zoomMap(0.25)"><span><i class="nz-icon nz-icon-enlarge"></i></span></div>
<div class="zoom-option" @click="zoomMap(-0.25)"><span><i class="nz-icon nz-icon-narrow"></i></span></div>
</div>
</div>
</template>
<script>
import { Topology, registerNode } from '@topology/core'
import {
Tools,
canvasRegister,
imageTemp,
myShape,
myAnchors,
myIconRect,
myTextRect,
onChangeAnimate,
onChangeAnimateLine,
myCubec,
myCubeAnchors
} from './L5/services/canvas.js'
import { getTopology, setTopology } from '../js/common'
import CanvasProps from './L5/CanvasProps'
import topologyTopTool from './L5//topologyTopTool'
import popDataMain from './popData/Main'
import popDataInfo from './popData/Info'
import alertTable from './popData/alertTable'
import assetTable from './popData/assetTable'
import endpointTable from './popData/endpointTable'
import topoTooltip from './L5/topoTooltip'
import { getMetricTypeValue } from '../js/tools'
import bus from '../../../libs/bus'
import topologyPrev from './topologyPrev'
// 注册到画布
registerNode('rectangleImg', myShape, myAnchors, myIconRect, myTextRect)
registerNode('myCube', myCubec, myCubeAnchors, null, null)
const canvasOptions = {
rotateCursor: '/img/rotate.cur',
translateKey: 'None',
disableEmptyLine: true,
autoExpandDistance: 0,
minScale: 0.01
}
export default {
name: 'topologyL5',
data () {
return {
objChange: false, // project 变化 用于判断 init是否执行完成 执行完成 才可以执行下次变化
chartDataInfo: {},
topoPrevData: {}, // 预览数据
imgInit: false, // 判断图片是否加载完成
toolGroup: '基本形状',
editFlag: true,
tools: Tools,
props: {},
topologyLoading: false,
contextmenu: {
left: null,
top: null,
bottom: null
},
filterTime: [
bus.timeFormate(bus.getOffsetTimezoneData(-1), 'yyyy-MM-dd hh:mm:ss'),
bus.timeFormate(bus.getOffsetTimezoneData(), 'yyyy-MM-dd hh:mm:ss')
],
topologyInfo: {
fontSize: 14,
align: 'left',
fontColor: '#000000',
opacity: 1,
name: ''
},
saveData: {},
oldTopologyData: '',
redoIndex: 0,
dataLength: 0,
editTopologyFlag: false,
searchTime: bus.getTimezontDateRange(),
activeNames: [],
topologyIndex: 0,
iconArray: [],
imgageLoading: false,
toolShow: {
node: true,
attr: true,
topTool: true,
nodeCord: [0, 0],
attrCord: [0, 0],
height: 500
},
dragFlag: true,
modules: [],
allModules: [],
projectInfo: {
title: '',
id: '',
remark: '',
alertStat: [1, 2, 3],
moduleMum: 6,
loading: true
},
timer: null, // 处理project短时间呢频繁变更的定时器
timer2: null, // 处理平移画布显示iconState的定时器
timer3: null, // 处理tooltip的显示定时器
nodesArr: [],
notModuleIDArr: [],
popData: [
{
top: '-40px',
left: '-21px',
className: 'nz-icon-endpoint',
id: 'endpoint',
title: this.$t('project.topology.endpoint')
},
{ top: '-40px', left: '19px', className: 'nz-icon-asset', id: 'asset', title: this.$t('project.topology.asset') },
{ top: '-4px', left: '40px', className: '', id: 'other', title: '' },
{ top: '30px', left: '19px', className: '', id: 'other', title: '' },
{ top: '30px', left: '-21px', className: 'nz-icon-info-normal', id: 'info', title: this.$t('project.topology.info') },
{ top: '-4px', left: '-40px', className: 'nz-icon-gaojing', id: 'alert', title: this.$t('project.topology.alert') }
],
popDataShow: {
endpoint: false,
asset: false,
total: false,
other: false,
info: false,
alert: false,
main: false
},
moduleId: '',
tooltipPosition: {
top: 0,
left: 0,
show: false,
height: 400
},
chartData: {},
chartGetData: [],
penLineType: [
{ d: 'M5 19 a50,100 0 0,1 40,0', 'stroke-dasharray': '', name: this.$t('project.topology.curve'), id: 'curve' },
{ d: 'M5 8 l20 0 l0 12 l20 0', 'stroke-dasharray': '', name: this.$t('project.topology.polyline'), id: 'polyline' },
{ d: 'M5 14 l40 0', 'stroke-dasharray': '', name: this.$t('project.topology.line'), id: 'line' }
// {d:'M5 20 C0,8 50,0 85,0',"stroke-dasharray":"",name:'mind'},
],
lineName: 'curve',
cachesIndex: 0,
projectInfoShow: false,
projectAlertShow: false,
previewShow: false,
penId: undefined,
penToolTipScale: 1,
oldScale: 1,
uploadPicShow: false,
uploadPic: {
name: '',
unit: ''
},
unitArr: []
}
},
components: {
CanvasProps,
topologyTopTool,
popDataMain,
popDataInfo,
alertTable,
assetTable,
endpointTable,
topoTooltip,
topologyPrev
},
props: {
topologyIndexF: {
type: Number,
default: 0
},
obj: {},
showTopTools: {
type: Boolean,
default: true
},
fromOverView: {
type: Boolean,
default: false
},
fromPrev: {
type: Boolean,
default: false
},
topoPrevDataS: {
}
},
watch: {
topologyIndexF: {
immediate: true,
handler (n) {
this.topologyIndex = n
}
},
obj: {
deep: true,
immediate: true,
handler (n) {
if (n.id) {
if (getTopology(this.topologyIndex)) {
getTopology(this.topologyIndex).destroy()
setTopology(this.topologyIndex, null)
}
if (!this.objChange) {
this.editTopologyFlag = false
this.topologyLoading = true
this.projectInfoShow = false
this.projectAlertShow = false
if (n.id) {
this.getProjectData(n)
}
this.topologyLoading = true
this.init()
this.timer = null
this.objChange = true
} else {
if (!this.timer) {
this.timer = setInterval(() => {
if (this.objChange) { return }
this.editTopologyFlag = false
this.topologyLoading = true
this.projectInfoShow = false
this.projectAlertShow = false
if (n.id) {
this.getProjectData(n)
}
clearTimeout(this.timer)
this.timer = null
this.objChange = true
this.topologyLoading = true
this.init()
}, 100)
} else {
clearTimeout(this.timer)
this.timer = setInterval(() => {
if (this.objChange) { return }
this.editTopologyFlag = false
this.topologyLoading = true
this.projectInfoShow = false
this.projectAlertShow = false
if (n.id) {
this.getProjectData(n)
}
clearTimeout(this.timer)
this.timer = null
this.objChange = true
this.topologyLoading = true
this.init()
}, 100)
}
}
}
}
}
},
computed: {},
created () {
canvasRegister()
if (process.client) {
document.onclick = event => {
this.contextmenu = {
left: null,
top: null,
bottom: null
}
}
}
},
mounted () {
if (!this.fromOverView) { // 从overview来的 加载相应图片 优化首页加载速度
this.addNodeInit()
}
document.getElementById('topology-canvas' + this.topologyIndexF).addEventListener('mousemove', this.canvasMove)
window.addEventListener('resize', this.winResize)
},
methods: {
init () {
canvasOptions.on = this.onMessage
this.reload()
},
reload () {
this.topologyLoading = true
this.getTopologyData().then((data) => {
this.openTopologyData(data).then(() => {
// 获取对应的值 给节点 连线添加对应动画
this.lineName = data.lineName ? data.lineName : this.lineName
this.chartGetData = []
const axiosArr = []
const promiseArr = []
const self = this
const pensPromise = (pen, arr, index) => {
return new Promise(function (resolve, reject) {
Promise.all(arr).then((res) => {
self.chartGetData[index].res = self.computeData(res, pen.data.aggregation, pen)
self.setAnimation(pen, self.chartGetData[index].res)
resolve()
})
})
}
const endTime = this.filterTime[1]
const startTime = this.filterTime[0]
const step = bus.getStep(startTime, endTime)
data.pens && data.pens.forEach((item, index) => {
this.chartGetData.push({ id: item.id, res: [] })
const arr = item.data.expressArr.map((ele) => {
let query = encodeURIComponent(ele)
if (!query) {
return new Promise(resolve => {
resolve({ data: '', status: 'no query' })
})
}
query += '&nullType=' + 'connected'
return this.$get('/prom/api/v1/query_range?query=' + query + '&start=' + this.$stringTimeParseToUnix(startTime) + '&end=' + this.$stringTimeParseToUnix(endTime) + '&step=' + step)
})
axiosArr.push({ item, arr })
promiseArr.push(pensPromise(item, arr, index))
})
Promise.all(promiseArr).then((res) => {
getTopology(this.topologyIndex).open(data)
getTopology(this.topologyIndex).lock(1)
this.objChange = false
let flag = false
const position = {
x: this.$refs['topology-canvas' + this.topologyIndexF].offsetWidth,
y: this.$refs['topology-canvas' + this.topologyIndexF].offsetHeight
}
this.oldScale = getTopology(this.topologyIndex).data.scale
getTopology(this.topologyIndex).data.pens.forEach(item => {
if (flag) {
return
}
if (item.rect.ex > position.x || item.rect.ey > position.y) {
getTopology(this.topologyIndex).fitView(20)
flag = true
}
})
getTopology(this.topologyIndex).centerView(20)
this.penToolTipScale = getTopology(this.topologyIndex).data.scale
setTimeout(() => {
getTopology(this.topologyIndex).data.pens.forEach(item => {
if (item.animatePlay) {
item.stopAnimate()
setTimeout(() => {
item.startAnimate()
})
}
}, 100)
})
// if(this.fromPrev){
// getTopology(this.topologyIndex).scaleTo(data.scale/2)
// }
// getTopology(this.topologyIndex).fitView();
this.oldTopologyData = JSON.stringify(getTopology(this.topologyIndex).data)
this.getNodesArr()
})
})
})
},
dateChange () {
const nowTimeType = this.$refs.pickTime.$refs.timePicker.nowTimeType
this.setSearchTime(nowTimeType.type, nowTimeType.value)
this.filterTime[0] = bus.timeFormate(this.searchTime[0], 'yyyy-MM-dd hh:mm:ss')
this.filterTime[1] = bus.timeFormate(this.searchTime[1], 'yyyy-MM-dd hh:mm:ss')
this.reload()
},
setSearchTime (type, val) { // 设置searchTime
if (type === 'minute') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setMinutes(new Date(bus.computeTimezone(new Date().getTime())).getMinutes() - val), 'yyyy-MM-dd hh:mm:ss')
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())), 'yyyy-MM-dd hh:mm:ss')
this.$set(this.searchTime, 0, startTime)
this.$set(this.searchTime, 1, endTime)
this.$set(this.searchTime, 2, val + 'm')
} else if (type === 'hour') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setHours(new Date(bus.computeTimezone(new Date().getTime())).getHours() - val), 'yyyy-MM-dd hh:mm:ss')
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())), 'yyyy-MM-dd hh:mm:ss')
this.$set(this.searchTime, 0, startTime)
this.$set(this.searchTime, 1, endTime)
this.$set(this.searchTime, 2, val + 'h')
} else if (type === 'date') {
const startTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())).setDate(new Date(bus.computeTimezone(new Date().getTime())).getDate() - val), 'yyyy-MM-dd hh:mm:ss')
const endTime = bus.timeFormate(new Date(bus.computeTimezone(new Date().getTime())), 'yyyy-MM-dd hh:mm:ss')
this.$set(this.searchTime, 0, startTime)
this.$set(this.searchTime, 1, endTime)
this.$set(this.searchTime, 2, val + 'd')
}
this.$refs.pickTime.$refs.timePicker.searchTime = this.searchTime
},
// 打开topology数据
openTopologyData (data) {
return new Promise(resolve => {
if (!getTopology(this.topologyIndex)) {
const canvas = new Topology('topology-canvas' + this.topologyIndexF, canvasOptions)
canvas.open(data)
setTopology(this.topologyIndex, canvas)
} else {
getTopology(this.topologyIndex).open(data)
}
this.topologyLoading = false
if (!getTopology(this.topologyIndex).data.name) {
getTopology(this.topologyIndex).data.name = this.obj.name
}
getTopology(this.topologyIndex).data.projectId = this.obj.id
getTopology(this.topologyIndex).lock(1)
this.getModule()// 获取module
resolve()
})
},
// 获取topology数据
getTopologyData () {
return new Promise(resolve => {
if (this.fromPrev) {
resolve(this.topoPrevDataS)
}
this.$get('/project/topo', { projectId: this.obj.id }).then(res => {
let data = res.data.topo
if (!res.data.topo || !data.pens) {
data = {
bkColor: '#FFFFFF',
gridSize: 10,
gridColor: '#ededed',
lineWidth: 1,
ruleColor: '#4e4e4e'
}
this.projectInfoShow = true
this.projectAlertShow = true
this.saveData = { ...data }
this.topologyInfo.name = this.obj.name
resolve(data)
} else {
this.topologyInfo = {
fontSize: data.data.fontSize,
align: data.data.align,
fontColor: data.data.fontColor,
opacity: data.data.opacity,
name: data.name
}
if (this.fromOverView) { // 优化从首页来的加载速度
const arr = data.pens.filter(item => !item.type)
this.addNodeInit(arr)
}
const timer = setInterval(() => {
if (!this.imgInit) {
return
}
clearInterval(timer)
const promiseArr = []
const self = this
for (let i = 0; i < data.pens.length; i++) {
const line = data.pens[i]
if (line.type === 1) {
if (!data.pens.find(item => item.id === line.from.id) || !data.pens.find(item => item.id === line.to.id)) {
data.pens.splice(i, 1)
i--
}
}
}
data.pens.forEach(item => {
if (item.type === 0 && item.data.imageId) {
item.image = this.iconArray.find(item1 => item1.id == item.data.imageId).image
}
if (item.type === 0) {
promiseArr.push(this.$get('/module/stat', { id: item.data.moduleId }))
} else {
promiseArr.push({ type: 1 })
}
})
if (!data.data) {
this.projectInfoShow = true
this.projectAlertShow = true
} else if (!JSON.stringify(data.data.projectInfo)) {
this.projectInfoShow = true
this.projectAlertShow = true
} else {
this.projectInfoShow = data.data.projectInfo
this.projectAlertShow = data.data.alertInfo
}
if (!data.bkImage) {
data.bkImage = undefined
}
Promise.all(promiseArr).then(res => {
res.forEach((response, index) => {
const item = data.pens[index]
if (item.type === 0) {
item.data.state = response.data
item.data.state.asset = false
item.data.state.endpoint = false
item.data.state.alert = false
if (item.data.state.assetStat.down > 0) {
item.data.state.asset = true
}
if (item.data.state.endpointStat.down > 0) {
item.data.state.endpoint = true
}
if (item.data.state.alertStat.P1 > 0 || item.data.state.alertStat.P3 > 0 || item.data.state.alertStat.P2 > 0) {
item.data.state.alert = true
}
item.data.state.error = item.data.state.asset || item.data.state.endpoint || item.data.state.alert
}
})
self.saveData = { ...data }
resolve(data)
})
}, 100)
}
})
})
},
// 赋值动画
setAnimation (pen, res) { // 根据所有res的状态 赋值动画
let maxLevel = 0
if (res.length > 0) {
res.forEach((response, innerPos) => {
if (response.status !== 'success') {
return
}
if (response.data.result) {
response.data.result.forEach((queryItem, resIndex) => {
pen.data.valueMapping.forEach((item, index) => {
if (item.value === 'base') { return }
if (queryItem.showValue > item.value) {
queryItem.level = item.level
if (maxLevel < item.level) {
maxLevel = item.level
}
}
})
})
}
})
}
if (maxLevel !== 0) {
if (pen.type === 0) { // 判断valueMapping 给相应的状态
const selLevel = pen.data.valueMapping.find(item => item.level === maxLevel)
if (selLevel) {
pen.fontColor = selLevel.color.text
pen.fillStyle = selLevel.color.fill
pen.strokeStyle = selLevel.color.line
pen.bkType = 0
}
onChangeAnimate(pen, selLevel.animateType, selLevel.color.fill, selLevel.color.line)
} else if (pen.type === 1) { // 判断valueMapping 给相应的状态
const selLevel = pen.data.valueMapping.find(item => item.level === maxLevel)
if (selLevel) {
pen.animateColor = selLevel.color.fill
pen.strokeStyle = selLevel.color.line
pen.animateType = selLevel.animateType
pen.fontColor = selLevel.color.text
}
onChangeAnimateLine(pen, pen.animateType)
}
} else {
if (pen.type === 0 && pen.animatePlay) { // 判断valueMapping 给相应的状态
onChangeAnimate(pen, pen.animateType)
} else if (pen.type === 1 && pen.animatePlay) { // 判断valueMapping 给相应的状态
onChangeAnimateLine(pen, pen.animateType)
}
}
},
computeData (res, type, pen) { // 处理别名 以及 根据type显示对应的值
if (res.length > 0) {
res.forEach((response, innerPos) => {
if (response.status !== 'success') {
return
}
if (response.data.result) {
response.data.result.forEach((queryItem, resIndex) => {
// 图表中每条线的名字,后半部分
let host = ''// up,
if (queryItem.metric.__name__) {
host = `${queryItem.metric.__name__}{`// up,
}
const tagsArr = Object.keys(queryItem.metric)// ["__name__","asset","idc","instance","job","module","project"]
// 设置时间-数据格式对
let dpsArr = []
dpsArr = Object.entries(queryItem.values)// [ ["0",[1577959830.781,"0"]], ["1",[1577959845.781,"0"]] ]
dpsArr = dpsArr.map(item => {
return [item[0], [item[1][0], Number(item[1][1])]]
})
// 判断是否有数据, && tagsArr.length > 0
if (dpsArr.length) {
tagsArr.forEach((tag, i) => {
if (tag !== '__name__') {
host += `${tag}="${queryItem.metric[tag]}",`
}
})
if (host.endsWith(',')) {
host = host.substr(0, host.length - 1)
}
if (queryItem.metric.__name__) {
host += '}'
}
if (!host || host === '') {
host = pen.data.expressArr[innerPos]
}
// 处理legend别名
let alias = this.dealLegendAlias(host, pen.data.legends[innerPos])
if (!alias || alias === '') {
alias = host
}
queryItem.legend = { name: host + '-' + pen.data.legends[innerPos] + '-' + resIndex, alias: alias }
queryItem.showValue = getMetricTypeValue(queryItem.values, type)
// 图表中每条线的名字,去掉最后的逗号与空格:metric名称, 标签1=a,标签2=c
}
})
}
})
}
return res
},
dealLegendAlias: function (legend, expression) {
if (/\{\{.+\}\}/.test(expression)) {
const labelValue = expression.replace(/(\{\{.+?\}\})/g, function (i) {
const label = i.substr(i.indexOf('{{') + 2, i.indexOf('}}') - i.indexOf('{{') - 2)
const reg = new RegExp(label + '=".+?"')
let value = null
if (reg.test(legend)) {
const find = legend.match(reg)[0]
value = find.substr(find.indexOf('"') + 1, find.lastIndexOf('"') - find.indexOf('"') - 1)
}
return value || label
})
return labelValue
} else {
return expression
}
},
// 获取project Info
getProjectData (n) {
// 获取projectInfo
this.projectInfo.loading = true
this.$get('project/info', { id: n.id }).then(response => {
if (response.code === 200) {
this.projectInfo.loading = false
this.projectInfo = { ...this.projectInfo, ...response.data.basic, moduleMum: response.data.module.length }
this.projectInfo.total = this.projectInfo.alertStat[0] + this.projectInfo.alertStat[1] + this.projectInfo.alertStat[2]
if (!this.projectInfo.total) {
this.projectInfo.total = 0
}
}
})
},
// Severity Label
returnSeverityLabel (key) {
return this.$CONSTANTS.alertMessage.severityData.find(s => { return s.value == key }).label
},
// 获取module
getModule () {
this.projectInfo.loading = true
this.$get('project/info', { id: this.obj.id }).then(response => {
if (response.code === 200) {
this.projectInfo.loading = false
this.projectInfo = { ...this.projectInfo, ...response.data.basic, moduleMum: response.data.module.length }
this.allModules = response.data.module
this.modulesDiff()
}
})
},
getNodesArr () {
if (!getTopology(this.topologyIndex)) return
this.nodesArr = getTopology(this.topologyIndex).data.pens.filter(item => {
if (!item.data) {
item.data = {
moduleId: '',
moduleName: '',
show: false,
error: false,
expressArr: []
}
}
return item.type === 0
})
// this.nodesArr=this.nodesArr.map(item=>{
// if(!item.data){
// item.data={
// moduleId:'',
// moduleName:'',
// show:false,
// error:false,
// expressArr:[],
// }
// }
// return {
// rect:item.rect,
// data:item.data
// }
// });
// 打开动画 是否更新顶部图标
this.nodesArr = JSON.parse(JSON.stringify(this.nodesArr))
},
modelTopClick (item, index) {
},
// 摘除已选择的module
modulesDiff (data) {
this.modules = this.allModules
if (getTopology(this.topologyIndex).data.pens) {
getTopology(this.topologyIndex).data.pens.forEach(item => {
if (item.type == 0) {
this.modules = this.modules.filter(item1 => item.data.moduleId !== item1.id)
}
})
}
if (data && data.data && data.data.moduleId) {
this.modules.unshift({ id: data.data.moduleId, name: data.data.moduleName })
}
},
// 显示module的工具
showNodeTools (index, pen) {
this.nodesArr.forEach((item, i) => {
item.data.show = i === index
})
},
// 具体内容点击
nodeTools (node, tool) {
this.moduleId = node.data.moduleId
if (tool.id === 'total') {
this.popDataShowUpdate('', false, node)
return
}
setTimeout(() => {
this.popDataShowUpdate(tool.id, false, node)
}, 100)
},
popDataShowUpdate (key, flag, node) { // key 显示对应的弹窗 flag是否不显示工具栏
this.popDataShow = {
endpoint: false,
asset: false,
total: false,
other: false,
info: false,
alert: false,
main: false
}
if (key === 'total') {
this.chartDataInfo = { ...node.data, ...this.chartGetData.find(item => item.id === node.id) }
}
this.$nextTick(() => {
this.popDataShow[key] = true
})
if (flag) { // 处理关闭后 缩放后显示工具按钮的问题
this.moduleId = ''
this.showNodeTools('')
}
if (key === 'asset' || key === 'alert' || key === 'endpoint') {
this.showNodeTools('')
}
},
/* topology 方法 */
onDrag (event, node) {
this.dragFlag = false
setTimeout(() => {
this.dragFlag = true
}, 100)
event.dataTransfer.setData('Text', JSON.stringify({ ...node.data, data: { imageId: node.data.imageId } }))
},
dragFlagChange (node) {
getTopology(this.topologyIndex).addNode(
{
...node.data,
rect: {
...node.data.rect,
x: this.$refs['topology-canvas' + this.topologyIndexF].offsetWidth / 2 - 50,
y: this.$refs['topology-canvas' + this.topologyIndexF].offsetHeight / 2 - 50
},
data: { imageId: node.data.imageId }
})
setTimeout(() => {
this.dragFlag = true
}, 100)
},
onMessage (event, data, e) {
// console.log('onMessage', event, data)
// console.log(getTopology(this.topologyIndex))
// this.notModuleIDArr=[];
this.toolShow.attr = false
this.toolShow.topTool = false
this.$nextTick(() => {
this.toolShow.attr = true
this.toolShow.topTool = true
})
if (data) {
this.notModuleIDArr.forEach(item => {
if (item.id === data.id) {
item.rect = data.rect
}
})
}
if (!Array.isArray(data) && data) { // 判断不是数组 提前个data配置好节点属性
if (data.type === 0 && !data.data.moduleId) {
data.data = {
...data.data,
moduleId: '',
moduleName: '',
show: false,
error: false,
animatePlay: false,
fillStyle: data.fillStyle,
strokeStyle: data.strokeStyle,
gradientColor: '#bae7ff',
gradientType: 0,
lineWidth: this.nodeDefaultWidth(data.name),
iconToolState: true,
// chart 配置项
valueMapping: [{
color: {
line: '#000000',
fill: '#ffffff',
text: '#000000'
},
value: 'base',
animateType: 'base',
level: 0,
base: true
}],
valueMappingSort: 'asc',
expressArr: [''],
legends: [''],
tooltipShow: true,
panelName: 'topologyName',
unit: 2,
type: 'line',
displayChart: true,
aggregation: 'last',
title: '',
url: ''
}
} else if (data.type == 1 && !data.data) {
// 连线是否自动计算锚点
// data.manualCps=true;
data.animateColor = '#FA901C'
data.data = {
animatePlay: false,
strokeStyle: data.strokeStyle,
animateColor: data.animateColor,
arrowColor: '#000000',
fromArrowColor: '#000000',
toArrowColor: '#000000',
lineWidth: 1,
// chart 配置项
valueMapping: [{
color: {
line: '#000000',
fill: '#ffffff',
text: '#000000'
},
value: 'base',
animateType: 'base',
level: 0,
base: true
}],
valueMappingSort: 'asc', /* desc */
expressArr: [''],
legends: [''],
tooltipShow: true,
panelName: 'topologyName',
unit: 2,
type: 'line',
displayChart: true,
aggregation: 'last',
title: '',
moduleName: '',
url: ''
}
}
if (data.type === 0 || data.type === 1) {
data.lineWidth = data.data.lineWidth
}
}
switch (event) {
case 'moveInNode':
case 'moveInLine':
if (this.timer3) {
clearTimeout(this.timer3)
this.timer3 = null
}
this.chartData = { ...data.data, ...this.chartGetData.find(item => item.id === data.id) }
this.tooltipPosition.show = false
setTimeout(() => {
this.tooltipPosition.show = true
const ePosition = window.ePosition
const boxWidth = document.getElementsByClassName('page')[0].offsetWidth
const boxHeight = document.getElementsByClassName('page')[0].offsetHeight
this.tooltipPosition.left = ePosition.layerX + 20
this.tooltipPosition.top = ePosition.layerY
this.$nextTick(() => {
if (this.$refs.topoTooltip) {
if ((boxWidth / 2) > ePosition.layerX) {
this.tooltipPosition.left = ePosition.layerX + 20
} else {
this.tooltipPosition.left = ePosition.layerX - 20 - this.$refs.topoTooltip.offsetWidth
}
if (boxHeight > (ePosition.layerY + this.$refs.topoTooltip.offsetHeight)) {
this.tooltipPosition.top = ePosition.layerY
} else {
this.tooltipPosition.top = ePosition.layerY - this.$refs.topoTooltip.offsetHeight
}
}
})
}, 100)
break
case 'moveOutNode':
case 'moveOutLine':
// this.tooltipPosition.show=false;
// return
if (!this.timer3) {
this.timer3 = setTimeout(() => {
this.tooltipPosition.show = false
this.timer3 = null
}, 50)
} else {
clearTimeout(this.timer3)
this.timer3 = setTimeout(() => {
this.tooltipPosition.show = false
this.timer3 = null
}, 50)
}
break
}
// 右侧输入框编辑状态时点击编辑区域其他元素onMessage执行后才执行onUpdateProps方法通过setTimeout让onUpdateProps先执行
setTimeout(() => {
switch (event) {
case 'node':
case 'addNode':
this.modulesDiff(data)
if (data.data.expressArr.length === 0 && event !== 'node') {
data.data.expressArr.push('')
data.data.legends.push('')
}
this.props = {
node: data,
line: null,
multi: false,
expand: this.props.expand,
nodes: null,
locked: data.locked,
pen: data,
pens: null
}
this.$nextTick(() => {
if (this.$refs.CanvasProps) {
if (this.penId !== data.id) {
this.$refs.CanvasProps.tab = '1'
}
this.penId = data.id
}
})
break
case 'line':
case 'addLine':
this.props = {
node: null,
line: data,
multi: false,
nodes: null,
locked: data.locked,
pen: data,
pens: null
}
this.$nextTick(() => {
if (this.$refs.CanvasProps) {
if (this.penId !== data.id) {
this.$refs.CanvasProps.tab = '1'
}
this.penId = data.id
}
})
break
case 'multi':
this.props = {
node: null,
line: null,
multi: true,
nodes: data.length > 1 ? data : null,
locked: this.getLocked({ nodes: data }),
pen: null,
pens: data.length > 1 ? data : null
}
break
case 'space':
this.penId = undefined
this.props = {
node: null,
line: null,
multi: false,
nodes: null,
locked: false,
pen: null,
pens: null
}
break
case 'moveOut':
break
case 'moveNodes':
case 'resizeNodes':
if (data.length > 1) {
this.props = {
node: null,
line: null,
multi: true,
nodes: data,
locked: this.getLocked({ nodes: data }),
pen: null,
pens: null
}
} else {
this.props = {
node: data[0],
line: null,
multi: false,
nodes: null,
locked: false,
pen: data[0],
pens: null
}
}
break
case 'resize': {
const domRect = document.getElementById('topology-canvas' + this.topologyIndexF).getBoundingClientRect()
this.toolShow.attrCord = [domRect.width - 350, 0]
this.toolShow.height = domRect.height
if (getTopology(this.topologyIndex)) {
getTopology(this.topologyIndex).canvasPos = domRect
}
break
}
case 'scale': {
if (this.$refs.topTool) {
this.$refs.topTool.scaleNum = parseInt(data * 100)
}
break
}
case 'locked': {
this.props = {
node: null,
line: null,
multi: false,
nodes: null,
locked: false,
pen: null,
pens: null
}
break
}
case 'delete': {
this.props = {
node: null,
line: null,
multi: false,
nodes: null,
locked: false,
pen: null,
pens: null
}
break
}
}
switch (event) {
case 'node':
case 'line':
case 'space':
case 'scale':
case 'translate':
this.moduleId = ''
this.showNodeTools('')
this.popDataShowUpdate('', false)
if (!this.editTopologyFlag) {
getTopology(this.topologyIndex)
}
break
}
switch (event) {
case 'space':
case 'scale':
case 'translate':
this.getNodesArr()
// if(!this.timer2){
// this.timer2=setTimeout(()=>{
// this.getNodesArr();
// this.timer2=null
// },300)
// }else{
// clearTimeout(this.timer2);
// this.timer2=setTimeout(()=>{
// this.getNodesArr();
// this.timer2=null
// },300)
// }
break
}
}, 0)
},
getLocked (data) {
let locked = true
if (data.nodes && data.nodes.length) {
for (const item of data.nodes) {
if (!item.locked) {
locked = false
break
}
}
}
if (locked && data.lines) {
for (const item of data.lines) {
if (!item.locked) {
locked = false
break
}
}
}
return locked
},
onUpdateProps (node) {
// 如果是node属性改变需要传入node重新计算node相关属性值
// 如果是line属性改变无需传参
getTopology(this.topologyIndex).updateProps(node)
},
handleAvatarSuccess () {
},
beforeAvatarUpload (file, fileList) {
const this_ = this
const isJPG = (file.raw.type === 'image/jpeg' || file.raw.type === 'image/png')
if (!isJPG) {
this.$message.error(this_.$t('project.topology.imgFormat'))
return false
}
// if (!isLt2M) {
// this.$message.error( this_.$t('project.topology.imgSize'));
// return false;
// }
const isSize = new Promise(function (resolve, reject) {
const width = 100
const height = 100
const _URL = window.URL || window.webkitURL
const img = new Image()
img.onload = function () {
const valid = img.width > width && img.height > height
valid ? resolve() : reject()
}
img.src = _URL.createObjectURL(file.raw)
}).then(() => {
if (isJPG) {
this.file = file.raw
}
return file.raw
}, () => {
this.$message.error(this_.$t('project.topology.imgMeasure'))
return Promise.reject()
})
return false
},
toolShowChange (attr) {
this.toolShow[attr] = !this.toolShow[attr]
},
/* topology 方法 */
end (v) {
},
/* tools 方法 */
imgUpload () {
const this_ = this
if (!this.uploadPic.unit) {
this.$message({
message: this_.$t('project.topology.unitError'),
type: 'error'
})
return
}
if (!this.file) {
this.$message({
message: this_.$t('project.topology.imgError'),
type: 'error'
})
return
}
this.upload()
},
upload () {
const form = new FormData()
form.append('file', this.file)
if (this.uploadPic.name) {
form.append('name', this.uploadPic.name)
} else {
form.append('name', this.file.name.substring(0, this.file.name.lastIndexOf('.')))
}
form.append('unit', this.uploadPic.unit)
this.$post('/project/topo/icon', form, { 'Content-Type': 'multipart/form-data' }).then(res => {
if (res.code == 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.saveSuccess') })
this.uploadPicShow = false
this.dealImg(`/project/topo/icon/${res.data.id}`).then((data) => {
const group = this.tools.find(tool => tool.group === this.uploadPic.unit)
if (group) {
group.children.push({
...imageTemp,
data: {
...imageTemp.data,
text: res.data.imageName,
image: data,
imageId: res.data.id,
unit: this.uploadPic.unit
}
})
} else {
this.tools.push({
group: this.uploadPic.unit,
children: [{
...imageTemp,
data: {
...imageTemp.data,
text: res.data.imageName,
image: data,
imageId: res.data.id,
unit: this.uploadPic.unit
}
}]
})
}
})
} else {
this.$message.error(res.msg)
}
})
},
delImg (item) {
this.$delete('/project/topo/icon?ids=' + item.data.imageId).then(res => {
if (res.code == 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('tip.deleteSuccess') })
this.addNodeInit()
} else {
this.$message.error(res.msg)
}
})
},
addNodeInit (imgidList) {
if (!this.fromOverView) {
this.$get('/project/topo/icon').then(res => {
this.imgageLoading = true
// this.tools[1].children=[];
const imgArr = []
const promiseArr = []
res.data.list.forEach((item, index) => {
item.imageName = item.name
delete item.name
promiseArr.push(this.dealImg(`/project/topo/icon/${item.id}`))
imgArr.push({ ...item })
})
Promise.all(promiseArr).then((res2) => {
this.iconArray = [...res.data.list]
this.iconArray.forEach((item, index) => {
item.image = res2[index]
const group = this.tools.find(tool => tool.group === item.unit)
if (group) {
group.children.push({
...imageTemp,
data: {
...imageTemp.data,
text: item.imageName,
image: res2[index],
imageId: item.id,
unit: item.unit
}
})
} else {
this.tools.push({
group: item.unit,
children: [{
...imageTemp,
data: {
...imageTemp.data,
text: item.imageName,
image: res2[index],
imageId: item.id,
unit: item.unit
}
}]
})
}
})
this.imgInit = true
})
})
} else {
this.imgageLoading = true
const promiseArr = []
imgidList.forEach((item, index) => {
if (item.data.imageId) {
promiseArr.push(this.dealImg(`/project/topo/icon/${item.data.imageId}`))
} else {
promiseArr.push('')
}
})
Promise.all(promiseArr).then(res2 => {
this.iconArray = imgidList.map(item => {
return {
id: item.data.imageId
}
})
this.iconArray.forEach((item, index) => {
if (item.id) {
item.image = res2[index]
}
})
this.imgInit = true
})
}
},
dealImg (url) {
// 处理后端传过来的图片流乱码问题
if (url) {
return new Promise((resolve, reject) => {
this.$axios
.get(url, {
responseType: 'arraybuffer'
})
.then(res => {
return ('data:image/jpeg;base64,' + btoa(new Uint8Array(res.data).reduce((data, byte) => data + String.fromCharCode(byte), '')))
})
.then(data => {
resolve(data)
// changeImage(data,(img)=>{
// resolve(img)
// })
})
.catch(err => {
})
})
}
},
delPen (obj) { // 删除元素
getTopology(this.topologyIndex).delete(obj)
this.props = {
node: null,
line: null,
multi: false,
nodes: null,
locked: false,
pen: null,
pens: null
}
},
editTopology (val) {
this.editTopologyFlag = true
setTimeout(() => {
getTopology(this.topologyIndex).lock(0)
getTopology(this.topologyIndex).data.pens.forEach((item, index) => { // 停止动画 以及赋值默认data
if (item.animatePlay) {
item.stopAnimate()
}
if (!item.data.expressArr.length) {
item.data.expressArr = ['']
item.data.legends = ['']
}
item.animateType = item.data.animateType
if (item.type === 0) {
item.fillStyle = item.data.fillStyle
item.strokeStyle = item.data.strokeStyle
item.animatePlay = false
item.fontColor = '#000000'
item.gradientType = item.data.gradientType ? item.data.gradientType : 0
if (!item.data.gradientColor) {
item.data.gradientType = 0
item.data.gradientColor = '#bae7ff'
}
if (item.data.gradientType === 0) {
item.bkType = 0
} else {
item.bkType = 1
}
} else if (item.type === 1) {
item.animateColor = item.data.animateColor
item.strokeStyle = item.data.strokeStyle
item.arrowColor = item.data.arrowColor
item.fromArrowColor = item.data.arrowColor
item.toArrowColor = item.data.arrowColor
item.animatePlay = false
item.fontColor = '#000000'
}
})
const domRect = document.getElementById('topology-canvas' + this.topologyIndexF).getBoundingClientRect()
this.toolShow.attrCord = [domRect.width - 350, 0]
this.toolShow.height = domRect.height
getTopology(this.topologyIndex).canvasPos = domRect
getTopology(this.topologyIndex).caches = {
index: 0,
list: [JSON.parse(JSON.stringify(getTopology(this.topologyIndex).data))]
}
})
},
animateCanvas () {
getTopology(this.topologyIndex).render()
getTopology(this.topologyIndex).animate()
},
refreshTopology () {
// canvas.open()
},
dropdownClick () {
},
changeTopologyOpt (val, key) {
// this.topologyData.data[key]=this.colorRGBtoHex(val);
// getTopology(this.index).data[key]=val;
// getTopology(this.index).render();
const dataOption = getTopology(this.topologyIndex).data
dataOption[key] = this.lineName
getTopology(this.topologyIndex).render()
},
notModuleIDArrChange (id) {
this.notModuleIDArr = this.notModuleIDArr.filter(item => item.id !== id)
},
// 保存
saveTopology () {
const topologyData = getTopology(this.topologyIndex).pureData()
let flag = true
const arr = []
this.notModuleIDArr = []
topologyData.pens.forEach(item => {
if (item.type === 0 && ((!item.data) || (item.data && !item.data.moduleId))) {
arr.push(item)
this.props = {
node: null,
line: null,
multi: false,
nodes: null,
locked: false,
pen: null,
pens: null
}
flag = false
}
})
if (!flag) {
this.notModuleIDArr = arr
}
if (flag) {
this.editTopologyFlag = false
topologyData.rule = false
topologyData.grid = false
topologyData.gridSize = 10
topologyData.pens.forEach(item => {
item.animatePlay = item.data.animatePlay
item.data.animateType = item.animateType
if (item.type === 0 && JSON.stringify(item.data.imageId)) {
item.image = ''
item.animateFrames = []
item.animateReady = null
delete item.img
delete item.lastImage
}
item.data.expressArr = item.data.expressArr.filter((expression, i) => {
if (!expression) {
item.data.legends.splice(i, 1)
return false
} else {
return true
}
})
})
if (this.penToolTipScale == getTopology(this.topologyIndex).data.scale) {
getTopology(this.topologyIndex).data.scale = this.oldScale
}
this.$put('/project/topo', { topo: JSON.stringify(topologyData), projectId: this.projectInfo.id }).then(res => {
this.prevent_opt.save = false
if (res.code === 200) {
this.$message({
message: this.$t('tip.saveSuccess'),
type: 'success'
})
this.$nextTick(() => {
getTopology(this.topologyIndex).lock(1)
const domRect = document.getElementById('topology-canvas' + this.topologyIndexF).getBoundingClientRect()
this.toolShow.attrCord = [domRect.width - 350, 0]
this.toolShow.height = domRect.height
getTopology(this.topologyIndex).canvasPos = domRect
this.reload()
})
}
}).catch(res => {
this.prevent_opt.save = false
this.$message({
message: res.msg,
type: 'error'
})
})
} else {
this.$message({
showClose: true,
message: this.$t('project.topology.selMod'),
type: 'warning'
})
}
},
// 取消
cancelTopology () {
this.editTopologyFlag = false
this.$nextTick(() => {
getTopology(this.topologyIndex).lock(1)
const domRect = document.getElementById('topology-canvas' + this.topologyIndexF).getBoundingClientRect()
this.toolShow.attrCord = [domRect.width - 350, 0]
this.toolShow.height = domRect.height
getTopology(this.topologyIndex).canvasPos = domRect
})
this.reload()
},
// 预览
previewTopology () {
const data = JSON.parse(JSON.stringify(getTopology(this.topologyIndex).data))
data.pens.forEach((item) => {
item.animatePlay = item.data.animatePlay
})
this.topoPrevData = JSON.parse(JSON.stringify(data))
this.topoPrevData.rule = false
this.topoPrevData.grid = false
this.previewShow = true
},
// 联动 project
changeProjectTitle () {
const data = getTopology(this.topologyIndex).data
this.topologyInfo = {
fontSize: data.data.fontSize,
align: data.data.align,
fontColor: data.data.fontColor,
opacity: data.data.opacity,
name: data.name
}
},
/* tools 方法 */
winResize () {
setTimeout(() => {
const domRect = document.getElementById('topology-canvas' + this.topologyIndex).getBoundingClientRect()
// this.toolShow.attrCord=[domRect.width-350,0];
// this.toolShow.height=domRect.height;
getTopology(this.topologyIndex).canvasPos = domRect
if (this.fromOverView) {
getTopology(this.topologyIndex).open(this.oldTopologyData)
}
let flag = false
const position = {
x: this.$refs['topology-canvas' + this.topologyIndexF].offsetWidth,
y: this.$refs['topology-canvas' + this.topologyIndexF].offsetHeight
}
getTopology(this.topologyIndex).data.pens.forEach(item => {
if (flag) {
return
}
if (item.rect.ex > position.x || item.rect.ey > position.y) {
getTopology(this.topologyIndex).fitView(20)
flag = true
}
})
getTopology(this.topologyIndex).centerView(20)
this.getNodesArr()
}, 100)
},
canvasMove (e) { // 画布上的移动 确定tooltip的位置
if (this.tooltipPosition.show) {
return
}
window.ePosition = e
},
tooltipOver () {
clearTimeout(this.timer3)
this.timer3 = null
},
tooltipOut () {
this.timer3 = setTimeout(() => {
this.tooltipPosition.show = false
this.timer3 = null
}, 50)
},
nodeDefaultWidth (nodeName) {
switch (nodeName) {
case 'myCube':
case 'rectangleImg':
return 0
default:
return 1
}
},
previewBeforeClose (done) {
this.$emit('changeTopologyIndexF')
done()
},
selpopIs (pen, state) { // 判断是否有图表
let flag = true
if (state.id === 'other') {
flag = false
}
if (state.id === 'total' && pen.data.expressArr.length === 0) {
flag = false
}
return flag
},
modelPopError (pen, state) {
if (item.id === 'asset' && this.activeModelItem.assetError) {
return true
}
if (item.id === 'alert' && this.activeModelItem.alertError) {
return true
}
if (item.id === 'endpoint' && this.activeModelItem.endpointError) {
return true
}
return false
},
redoIndexChange (index) {
this.redoIndex = index
},
querySearch (queryString, cb) {
const restaurants = this.unitArr
const results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants
// 调用 callback 返回建议列表的数据
cb(results)
},
createFilter (queryString) {
return (restaurant) => {
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
}
},
uploadPicChange () {
this.unitArr = []
this.tools.forEach((item, index) => {
if (index > 0) {
this.unitArr.push({
value: item.group
})
}
})
this.uploadPic.name = ''
this.uploadPic.unit = ''
this.file = null
this.uploadPicShow = true
},
zoomMap (num) {
getTopology(this.topologyIndex).scaleTo(getTopology(this.topologyIndex).data.scale + num)
},
penToBottom () {
getTopology(this.topologyIndex).bottom()
}
},
destroyed () {
if (getTopology(this.topologyIndex)) {
getTopology(this.topologyIndex).destroy()
setTopology(this.topologyIndex, null)
}
if (document.getElementById('topology-canvas' + this.topologyIndexF)) {
document.getElementById('topology-canvas' + this.topologyIndexF).removeEventListener('mousemove', this.canvasMove)
}
window.removeEventListener('resize', this.winResize)
}
}
</script>
<style lang="scss">
.el-dropdown-menu {
.project-topology-add-node {
.el-collapse-item__header {
padding: 0 10px;
background-color: #ffffff;
height: 32px;
}
.el-collapse-item__header.is-active {
background: #F6F6F6;
ont-family: Roboto-Bold;
font-size: 14px;
color: #FA901C;
font-weight: 700;
el-collapse-item__arrow {
color: #666;
}
}
.el-collapse-item__wrap {
padding: 0 10px;
background-color: #ffffff;
}
.el-collapse-item__content {
padding: 12px 0px;
display: flex;
flex-wrap: wrap;
/*justify-content: space-around;*/
}
.el-card__body {
padding: 0;
height: 100%;
}
.handle {
position: absolute;
z-index: 2;
}
.buttons {
padding: 12px;
display: inline-block;
position: relative;
vertical-align: middle;
width: 26px;
.delIcon {
position: absolute;
width: 16px;
height: 16px;
border-radius: 10px;
font-size: 12px;
line-height: 16px;
text-align: center;
background: red;
right: -8px;
top: -8px;
color: #fff;
display: none;
cursor: pointer;
}
a {
display: inline-block;
color: #314659;
width: 26px;
height: 26px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
line-height: 26px;
.iconfont {
font-size: 24px;
}
img {
max-width: 26px;
max-height: 26px;
}
&:hover {
color: #1890ff;
}
}
.upload-icon-box {
.el-icon-plus {
font-size: 14px;
margin-bottom: 10px;
}
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
}
}
.buttons:hover {
.delIcon {
/*display: inline-block;*/
}
}
}
.avatar-uploader {
line-height: 30px;
.el-icon-plus {
font-size: 12px;
color: #FA901C;
margin: 0 8px 0 15px;
}
.el-upload--picture-card {
width: 100%;
height: 100%;
border: none;
color: #666;
font-size: 14px;
line-height: 30px;
text-align: left;
}
.el-upload--picture-card:hover, .el-upload:focus {
color: #666;
}
}
.avatar-uploader:active el-upload--picture-card {
color: #DB8B8B;
}
.avatar-uploader:active .el-upload--picture-card:hover, .avatar-uploader:active .el-upload:focus {
color: #DB8B8B;
}
}
</style>
<style scoped>
@keyframes model-error-animation {
0% {
transform: scale(0.7);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0.7);
}
}
@keyframes model-icon-animation {
0% {
transform: scale(1.2) translateX(1px);
}
50% {
transform: scale(0.9) translateX(0px);
}
100% {
transform: scale(1.2) translateX(1px);
}
}
.nz-icon-shuidi {
position: absolute;
font-size: 48px;
color: rgba(190, 233, 222, 0.45);
border-radius: 50%;
height: 48px;
width: 48px;
line-height: 48px;
}
.model-error.nz-icon-shuidi {
color: #FADED7;
animation: model-error-animation .6s infinite ease-in-out;
animation-direction: normal;
}
.model-error-active.nz-icon-shuidi {
color: #FADED7;
}
.nz-icon-model {
color: #23BF9A;
position: absolute;
top: -4px;
left: 15px;
font-size: 18px;
line-height: 48px;
}
.model-error .nz-icon-model {
color: #EC7F66;
animation: model-icon-animation .6s infinite ease-in-out;
animation-direction: normal;
}
.model-error-active .nz-icon-model {
color: #EC7F66;
}
.scaleTool-enter-active {
animation: scaleTool-in .15s;
}
.scaleTool-leave-active {
animation: scaleTool-in .15s reverse;
}
@keyframes scaleTool-in {
from {
top: 0px;
left: 0px;
transform: scale(0.5);
}
}
.moduleIdRect{
/*border: 4px dashed #FA901C;*/
}
.module-rect-top{
border-top: 4px dashed #ff8c0a;
position: absolute;
}
.module-rect-right{
border-right: 4px dashed #ff8c0a;
position: absolute;
}
.module-rect-bottom{
border-bottom: 4px dashed #ff8c0a;
position: absolute;
}
.module-rect-left{
border-left: 4px dashed #ff8c0a;
position: absolute;
}
.network-pop .nz-icon-hexagonBorder{
position: absolute;
font-size: 48px;
color: #84d5c2;
height: 48px;
width: 48px;
line-height: 48px;
}
.network-pop .nz-icon-hexagonBorder:hover {
transform: scale(1.1);
color: #4BB49B;
}
.network-pop .nz-icon-hexagonBorder.error-model-stat {
color: #F5BAAC;
}
.network-pop .nz-icon-hexagonBorder.error-model-stat:hover {
color: #EC7F66;
}
.network-pop .nz-icon-liubianxing {
color: #e2f3ef;
font-size: 44px;
position: absolute;
top: 1px;
left: 2px;
transform: scale(0.95);
z-index: 0;
}
.network-pop .error-model-stat .nz-icon-liubianxing {
color: #FADED7;
}
.network-pop .nz-icon.noMove {
position: absolute;
left: 14px;
font-size: 20px;
color: #27c09c;
}
.network-pop .error-model-stat .nz-icon.noMove {
color: #EC7F66;
}
.network-pop .no-selPop {
color: #999 !important;
}
.network-pop .no-selPop .nz-icon-liubianxing {
color: rgb(218, 218, 218);
}
.network-pop .no-selPop .nz-icon-chart {
color: #999;
}
.network-info {
position: absolute;
right: 0;
top: 50px;
}
.facade-top{
min-height: 138px;
display: flex;
margin: 4px 0;
height: calc(16% - 40px);
font-size: 12px;
z-index: 10;
padding-left: 15px;
}
.facade-top > div{
width: 18%;
min-width: 315px;
background: #FFFFFF;
margin-right: 9px;
padding: 20px;
border: 1px solid #FFFFFF;
box-shadow: 1px 2px 4px 0 rgba(0,0,0,0.12), -1px 1px 9px -1px rgba(205,205,205,0.77);
}
.facade-top-title{
font-size: 16px;
color: #333333;
font-weight: bold;
padding: 5px 0;
}
.facade-top-left{
display: flex;
flex-direction: column;
justify-content: space-around;
}
.special.label{
margin-left: 30px;
}
.facade-top .facade-top-right{
width: auto;
min-width: 100px;
}
.facade-top-right-content{
display: flex;
justify-content: space-around;
justify-items: center;
flex-direction: column;
height: calc(100% - 30px);
align-items:flex-start;
}
.facade-top-right-content > div{
min-width: 84px;
height: 22px;
display: flex;
justify-content: space-between;
color: #fff;
text-align: center;
margin-bottom: 5px;
line-height: 22px;
}
.facade-top-right-content > div > div:last-child{
text-align: center;
border-radius: 0 4px 4px 0;
flex: 1;
height: calc(100% - 2px);
padding: 0 8px;
min-width: 40px;
}
.content-P1-title{
background: #F2866E;
border-radius: 4px 0 0 4px;
width: 40px;
height: 100%;
}
.content-P1-title + div{
border: 1px solid #F4907A;
font-size: 12px;
color: #F4907A;
}
.content-P2-title{
background: #F89984;
border-radius: 4px 0 0 4px;
width: 40px;
height: 100%;
}
.content-P2-title + div{
border: 1px solid #F9A28F;
font-size: 12px;
color: #F9A28F;
}
.content-P3-title{
background: #F7BA78;
border-radius: 4px 0 0 4px;
width: 40px;
height: 100%;
}
.content-P3-title + div{
border: 1px solid #F7BA78;
font-size: 12px;
color: #F7BA78;
}
.right-content-P1{
border: 1px solid ;
}
.align--center{
text-align: center;
}
</style>
<style lang="scss" scoped>
.project-topology-tool {
display: inline-flex;
height: 30px;
}
.el-dropdown-title {
background: #FFFFFF;
border: 1px solid #DEDEDE;
border-radius: 2px;
width: 66px;
height: 28px;
display: inline-block;
line-height: 28px;
.icon-cube {
margin-left: 15px;
}
}
.project-box {
width: 100%;
height: calc(100% - 20px);
margin-top: 10px;
position: relative;
border: 1px solid #eeeeee;
border-radius: 2px;
overflow: hidden;
.pickTime{
margin-top: -13px;
}
.project-title {
height: 34px;
padding-top: 8px;
padding-bottom: 8px;
}
.drag-header {
cursor: move;
background: #1a1a1a;
color: #fff;
}
.left-bottom {
position: absolute;
left: 10px;
bottom: 10px;
}
}
.page {
display: flex;
height: calc(100% - 60px);
width: 100%;
position: relative;
.tools {
width: 300px;
height: 100%;
border: none;
position: absolute;
z-index: 1 !important;
left: 20px;
background-color: #f9f9f9;
.title {
float: left;
}
}
.full {
flex: 1;
overflow: unset !important;
}
.props {
width: 500px;
height: 100%;
border: none;
position: absolute;
z-index: 1 !important;
right: 0;
top: 0;
background: #FFFFFF;
box-shadow: inset 1px 0 0 0 rgba(0, 0, 0, 0.09);
border-radius: 0px;
}
}
.overview-page{
height: 100%;
}
.special-select svg {
width: 75px;
height: 30px;
}
.special-select .el-select.el-select--small {
width: 100%;
}
.special-select /deep/ .el-select-dropdown {
width: 130px !important;
.el-select-dropdown__item {
padding: 0 0 0 10px;
}
}
.special-select /deep/ .el-input.el-input--prefix.el-input--suffix, .line-width /deep/ .el-input.el-input--prefix.el-input--suffix {
border: 1px solid #DCDFE6;
height: 28px;
}
.special-select /deep/ .el-input__inner, .line-width /deep/ .el-input__inner {
display: none;
}
.special-select /deep/ .el-input__prefix, .line-width /deep/ .el-input__prefix {
height: 28px;
line-height: 28px;
color: #899FB7;
width: 100%;
}
.special-select /deep/ .el-input__prefix > div {
width: 100%;
height: 100%;
}
.upload-pic-row{
width: 420px;
margin-bottom: 10px;
.upload-pic-label{
text-align: right;
font-size: 14px;
color: #666666;
letter-spacing: 0;
font-weight: 400;
height: 30px;
line-height: 30px;
padding-right: 8px;
}
/deep/ .el-upload--text{
width: 100%;
.el-upload-dragger{
width: 100%;
}
}
}
.upload-pic-box{
width: 284px;
height: 30px;
text-align: center;
font-size: 14px;
color: #666666;
font-weight: 400;
line-height: 30px;
cursor: pointer;
.el-icon-plus{
color: #FA901C;
}
}
</style>