在线文档
<div id="flow-container"></div>
<div
v-show="popoverStatus"
class="popover-container"
:style="{ top: popoverTop + 'px', left: popoverLeft + 'px' }"
>
<div class="items" v-if="!popoverShowInverter">
<div class="item">
<div class="item-label">编号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.channelNum) }}
</div>
</div>
<div class="item">
<div class="item-label">SN号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.optimizerSncode) }}
</div>
</div>
<div class="item">
<div class="item-label">型号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.optimizerModel) }}
</div>
</div>
<div class="item">
<div class="item-label">优化器名称</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.optimizerName) }}
</div>
</div>
<div class="item">
<div class="item-label">通道编号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.channelNo) }}
</div>
</div>
<div class="item">
<div class="item-label">发电量 (kWh)</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.cap) }}
</div>
</div>
<div class="item">
<div class="item-label">输出电压 (V)</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.outv) }}
</div>
</div>
<div class="item">
<div class="item-label">输出电流 (A)</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.outa) }}
</div>
</div>
<div class="item">
<div class="item-label">输出功率 (W)</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.outPower) }}
</div>
</div>
<div class="item">
<div class="item-label">输入电压 (V)</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.inv) }}
</div>
</div>
<div class="item">
<div class="item-label">输入电流 (A)</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.ina) }}
</div>
</div>
<div class="item">
<div class="item-label">通道状态</div>
<div class="item-value">
{{ $t(`DEV.COM_STATE.${popoverData.status}`) }}
</div>
</div>
</div>
<div class="items" v-else>
<div class="item">
<div class="item-label">编号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.inventerNum) }}
</div>
</div>
<div class="item">
<div class="item-label">SN号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.inventerSnCode) }}
</div>
</div>
<div class="item">
<div class="item-label">型号</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.inventerModel) }}
</div>
</div>
<div class="item">
<div class="item-label">逆变器名称</div>
<div class="item-value">
{{ $utils.handlerItemData(popoverData.name) }}
</div>
</div>
</div>
</div>
<style lang='scss'>
#flow-container {
width: 100%;
height: 100%;
}
.popover-container {
position: fixed;
left: 10px;
top: 20px;
display: inline-block;
z-index: 101;
background: rgba(255, 255, 255, 0.8);
.items {
padding: 16px;
.item {
display: flex;
align-items: center;
+ .item {
margin-top: 10px;
}
&-label {
min-width: 74px;
height: 16px;
line-height: 16px;
font-size: 12px;
color: #757575;
}
&-value {
min-width: 74px;
height: 16px;
line-height: 16px;
margin-left: 12px;
font-size: 12px;
color: #757575;
}
}
}
}
</style>
import Konva from 'konva'
import head_img from '../dataScreen/img/view-head.png'
import online_img from '../dataScreen/img/view-online.png'
import offline_img from '../dataScreen/img/view-offline.png'
export default {
name: 'StationView',
components: {},
data() {
return {
layer: null,
stage: null,
defaultConfig: {
rectWith: 74,
rectHeight: 100,
lineStrokeWidth: 1,
grey: 'rgb(155,162,160)',
lineColor: '#D9D9D9',
scaleX: 1,
scaleY: 1
},
startX: 10,
startY: 10,
initStartX: 10,
initStartY: 10,
longLine: 62,
shortLine: 24,
wrapBlock: 10,
boxSpace: 40,
bigBlockSpace: 66,
pointsArr: [],
groupArr: [],
onlineImg: null,
offlineImg: null,
headImg: null,
formData: [
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
cap: 1234,
name: '逆变器名称',
optimizerRealList: [
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
optimizerNum: '1',
optimizerSncode: '111111',
cap: 1111,
channelNo: 1111,
name: '通道1名称',
channelNum: '1.1.1',
timestamp: 1111
},
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
optimizerNum: '1',
optimizerSncode: '111111',
cap: 1122,
channelNo: 1122,
name: '通道2名称',
channelNum: '1.1.2',
timestamp: 1122
},
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
optimizerNum: '1',
optimizerSncode: '111111',
cap: 1133,
channelNo: 1133,
name: '通道3名称',
channelNum: '1.1.3',
timestamp: 1133
},
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
optimizerNum: '2',
optimizerSncode: '223333',
cap: 2222,
channelNo: 2222,
name: '通道21名称',
channelNum: '1.2.1',
timestamp: 2222
},
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
optimizerNum: '2',
optimizerSncode: '223333',
cap: 2222,
channelNo: 2222,
name: '通道22名称',
channelNum: '1.2.2',
timestamp: 2222
},
{
inventerId: 11111,
inventerNum: '1',
inventerSnCode: '111111',
optimizerNum: '2',
optimizerSncode: '223333',
cap: 2222,
channelNo: 111,
name: '通道22名称',
channelNum: '1.2.3',
timestamp: 2345
}
]
},
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
cap: 5678,
name: '逆变器2名称',
optimizerRealList: [
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
optimizerNum: '1',
optimizerSncode: '5554545',
cap: 343334,
channelNo: 43434,
name: '通道22名称',
channelNum: '2.1.1',
timestamp: 34343434
},
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
optimizerNum: '1',
optimizerSncode: '5554545',
cap: 343344,
channelNo: 34343,
name: '通道22名称',
channelNum: '2.1.2',
timestamp: 45455
},
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
optimizerNum: '1',
optimizerSncode: '5554545',
cap: 545454,
channelNo: 454545,
name: '通道22名称',
channelNum: '2.1.3',
timestamp: 45454545
},
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
optimizerNum: '2',
optimizerSncode: '',
cap: 4545,
channelNo: 0,
name: '通道22名称',
channelNum: '2.2.1',
timestamp: 0
},
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
optimizerNum: '2',
optimizerSncode: '',
cap: 0,
channelNo: 0,
name: '通道22名称',
channelNum: '2.2.2',
timestamp: 0
},
{
inventerId: 34344,
inventerNum: '2',
inventerSnCode: '',
optimizerNum: '2',
optimizerSncode: '',
cap: 0,
channelNo: 0,
name: '通道22名称',
channelNum: '2.2.3',
timestamp: 0
}
]
}
],
popoverShowInverter: false,
popoverStatus: false,
popoverData: {},
popoverTop: 0,
popoverLeft: 0
}
},
computed: {
flowHeight() {
let height = 0
this.formData.forEach((item, index) => {
height +=
this.defaultConfig.rectHeight + (index > 0 ? this.bigBlockSpace : 0)
if (item.optimizerRealList && item.optimizerRealList.length) {
height +=
Math.floor(item.optimizerRealList.length / this.wrapBlock) *
(this.boxSpace + this.defaultConfig.rectHeight)
}
})
return height
},
flowWidth() {
let num = 0
this.formData.forEach((item, index) => {
if (
item.optimizerRealList &&
item.optimizerRealList.length &&
item.optimizerRealList.length > num
) {
num = item.optimizerRealList.length
}
})
num = num > this.wrapBlock ? this.wrapBlock : num
return (
this.defaultConfig.rectWith +
this.longLine +
this.defaultConfig.rectWith * num +
this.shortLine * (num - 1)
)
}
},
watch: {
formData: {
handler(nv, ov) {
if (nv) {
this.$nextTick((_) => {
this.initGraph()
this.initPoints()
this.render()
this.eventListen()
})
}
},
immediate: true
}
},
async mounted() {
this.initImage()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
console.log('页面销毁,取消监听resize')
window.removeEventListener('resize', this.handleResize)
},
methods: {
initGraph() {
this.stage && this.stage.clearCache()
this.defaultConfig.scaleX = 1
this.defaultConfig.scaleY = 1
const el = document.querySelector('#flow-container')
if (!el) return
this.startX =
(el.offsetWidth - this.flowWidth) / 2 > this.initStartX
? (el.offsetWidth - this.flowWidth) / 2
: this.initStartX
this.startY =
(el.offsetHeight - this.flowHeight) / 2 > this.initStartY
? (el.offsetHeight - this.flowHeight) / 2
: this.initStartY
this.initStartX = this.startX
this.initStartY = this.startX
this.stage = new Konva.Stage({
container: el,
width: el.offsetWidth,
height: el.offsetHeight,
draggable: true
})
this.layer = new Konva.Layer()
this.stage.add(this.layer)
this.layer.draw()
},
initPoints() {
this.pointsArr = []
for (let i = 0, len = this.formData.length; i < len; i++) {
if (i > 0) {
const item = this.formData[i - 1]
this.startY += this.defaultConfig.rectHeight + this.bigBlockSpace
if (item.optimizerRealList && item.optimizerRealList.length) {
this.startY +=
Math.floor(item.optimizerRealList.length / this.wrapBlock) *
(this.boxSpace + this.defaultConfig.rectHeight)
}
}
this.getPoints(this.formData[i], i)
}
},
async initImage() {
if (!this.onlineImg) {
this.onlineImg = await this.loadImage(online_img)
}
if (!this.offlineImg) {
this.offlineImg = await this.loadImage(offline_img)
}
if (!this.headImg) {
this.headImg = await this.loadImage(head_img, '1')
}
},
loadImage(imgUrl, type) {
return new Promise((resolve) => {
Konva.Image.fromURL(imgUrl, (darthNode) => {
const rHeight = type
? this.defaultConfig.rectHeight
: this.defaultConfig.rectHeight - 3
darthNode.setSize({
width: this.defaultConfig.rectWith,
height: rHeight
})
resolve(darthNode.clone())
})
})
},
getPoints(content = {}, index) {
let startX = this.startX
this.pointsArr.push({
type: 'Inverter',
points: [startX, this.startY],
name: content.inventerName,
label: content.label,
value: content.cap,
version: content.inventerNum,
id: index + '',
status: content.status
})
const x1 = startX + this.defaultConfig.rectWith
const y1 = this.startY + this.defaultConfig.rectHeight / 2
const x2 = x1 + this.longLine
this.pointsArr.push({
type: 'Line',
points: [x1, y1, x2, y1]
})
startX = x2
let oldInt = 0
for (let i = 0, len = content.optimizerRealList.length; i < len; i++) {
const item = content.optimizerRealList[i]
const int = Math.floor(i / this.wrapBlock)
const mod = i % this.wrapBlock
if (oldInt !== int) {
this.pointsArr.push({
type: 'Line',
points: [
x1 + this.longLine / 2,
y1,
x1 + this.longLine / 2,
y1 + (this.defaultConfig.rectHeight + this.boxSpace) * int,
x1 + this.longLine,
y1 + (this.defaultConfig.rectHeight + this.boxSpace) * int
]
})
}
oldInt = int
const x = startX + (this.defaultConfig.rectWith + this.shortLine) * mod
const y =
this.startY + (this.defaultConfig.rectHeight + this.boxSpace) * int
this.pointsArr.push({
type: 'Optimizer',
points: [x, y],
name: item.optimizerName,
label: item.label,
value: item.cap,
version: item.channelNum,
status: item.status,
id: index + ':' + i
})
if (
mod !== this.wrapBlock - 1 &&
int * this.wrapBlock + mod + 1 !== len
) {
this.pointsArr.push({
type: 'Line',
points: [
x + this.defaultConfig.rectWith,
y + this.defaultConfig.rectHeight / 2,
x + this.defaultConfig.rectWith + this.shortLine,
y + this.defaultConfig.rectHeight / 2
]
})
}
}
},
createInverter(data = {}) {
const group = new Konva.Group({
x: data.points[0],
y: data.points[1],
id: data.id
})
const textConfig = {
width: this.defaultConfig.rectWith,
height: 17,
lineHeight: 17,
align: 'center',
verticalAlign: 'middle',
fontSize: 12
}
this.layer.add(group)
this.groupArr.push(group)
const darthNode = this.headImg.clone()
group.add(darthNode)
const rectText = new Konva.Text({
y: 9,
...textConfig,
text: '= / ~',
fill: '#fff'
})
group.add(rectText)
const rectText2 = new Konva.Text({
y: 38,
...textConfig,
text: data.value,
fill: '#000'
})
group.add(rectText2)
const rectText3 = new Konva.Text({
y: 55,
...textConfig,
text: 'kWh',
fill: '#79889C'
})
group.add(rectText3)
const rectText4 = new Konva.Text({
y: 79,
...textConfig,
text: data.version,
fill: '#79889C'
})
group.add(rectText4)
},
createOptimizer(data = {}, lienStatus = true) {
const group = new Konva.Group({
x: data.points[0],
y: data.points[1] + 3,
id: data.id
})
const textConfig = {
width: this.defaultConfig.rectWith,
height: 17,
lineHeight: 17,
align: 'center',
verticalAlign: 'middle',
fontSize: 12
}
this.layer.add(group)
this.groupArr.push(group)
if (lienStatus) {
const darthNode = this.onlineImg.clone()
group.add(darthNode)
const valueText = new Konva.Text({
y: 25,
...textConfig,
fontSize: 14,
text: data.value,
fill: '#fff'
})
group.add(valueText)
const unitText = new Konva.Text({
y: 46,
...textConfig,
text: 'kWh',
fill: '#79889C'
})
group.add(unitText)
const versionText = new Konva.Text({
y: 77,
...textConfig,
text: data.version,
fill: '#79889C'
})
group.add(versionText)
} else {
const darthNode = this.offlineImg.clone()
group.add(darthNode)
const valueText = new Konva.Text({
y: 27,
...textConfig,
fontSize: 14,
text: '--',
fill: '#fff'
})
group.add(valueText)
const versionText = new Konva.Text({
y: 77,
...textConfig,
text: data.version,
fill: '#fff'
})
group.add(versionText)
}
},
render() {
this.pointsArr.forEach((item, index) => {
if (item.type === 'Inverter') {
this.createInverter(item)
} else if (item.type === 'Optimizer') {
this.createOptimizer(item, item.status)
} else if (item.type === 'Line') {
const line = new Konva.Line({
points: item.points,
stroke: this.defaultConfig.lineColor,
strokeWidth: this.defaultConfig.lineStrokeWidth
})
this.layer.add(line)
}
})
},
eventListen() {
if (!this.stage) return
this.stage.on('wheel', (e) => {
this.popoverStatus = false
this.popoverData = {}
this.handleZoom(e.evt, e.evt.wheelDelta)
})
const setCursor = (e, val = 'pointer') => {
const stage = e.target.getStage()
if (stage) {
stage.container().style.cursor = val
}
}
this.groupArr.forEach((group) => {
group.on('click', (e) => {
e.cancelBubble = true
setCursor(e)
this.popoverStatus = true
const idArr = group.id().split(':')
this.popoverData =
idArr.length > 1
? this.formData[idArr[0]].optimizerRealList[idArr[1]]
: this.formData[idArr[0]]
console.log(this.popoverData)
if (this.popoverData.optimizerRealList) {
this.popoverShowInverter = true
} else {
this.popoverShowInverter = false
}
this.popoverTop = e.evt.clientY
this.popoverLeft = e.evt.clientX
})
group.on('mouseenter', (e) => {
setCursor(e)
})
group.on('mouseover', (e) => {
if (!this.popoverStatus) {
setCursor(e)
}
})
group.on('mouseleave', (e) => {
setCursor(e, 'default')
})
})
this.stage.on('click', (e) => {
setCursor(e, 'default')
this.popoverStatus = false
this.popoverData = {}
})
this.stage.on('dragmove', (e) => {
this.popoverStatus = false
this.popoverData = {}
})
},
handleZoom(evt) {
const max = 4
const min = 0.5
const step = 0.07
let x = this.defaultConfig.scaleX
let y = this.defaultConfig.scaleY
if (evt.wheelDelta > 0) {
x += step
y += step
if (x >= max) x = max
if (y >= max) y = max
} else {
x -= step
y -= step
if (x <= min) x = min
if (y <= min) y = min
}
this.defaultConfig.scaleX = x
this.defaultConfig.scaleY = y
this.stage.scale({
x: x,
y: y
})
},
handleResize() {
if (this.stage) {
this.stage.destroy()
this.initGraph()
this.initPoints()
this.render()
this.eventListen()
}
},
transformData(data1) {
const data2 = []
data1.forEach((item1) => {
const newItem2 = {
inventerId: item1.inventerId,
inventerNum: item1.inventerNum,
inventerSnCode: item1.inventerSnCode,
inventerModel: item1.inventerModel,
cap: item1.inventerCap,
name: item1.inventerName,
optimizerRealList: []
}
item1.optimizerRealList.forEach((optimizer1) => {
const optimizerNum = optimizer1.optimizerNum
optimizer1.optimizerChanelList.forEach((channel1) => {
const newOptimizer = {
...channel1,
inventerId: item1.inventerId,
inventerNum: item1.inventerNum,
inventerSnCode: item1.inventerSnCode,
optimizerName: optimizer1.optimizerName,
optimizerNum: optimizerNum,
optimizerSncode: optimizer1.optimizerSncode,
optimizerModel: optimizer1.optimizerModel,
cap: channel1.cap,
channelNo: channel1.channelNo,
channelNum:
item1.inventerNum +
'.' +
optimizerNum +
'.' +
channel1.channelNo,
timestamp: channel1.timestamp,
status: channel1.status
}
newItem2.optimizerRealList.push(newOptimizer)
})
})
data2.push(newItem2)
})
return data2
}
}
}
实际效果