|
|
@@ -1,6 +1,17 @@
|
|
|
<template>
|
|
|
<div>
|
|
|
<div ref="container" style="width: 900px; height: 400px;"></div>
|
|
|
+ <el-dialog
|
|
|
+ title="异常信息"
|
|
|
+ :visible.sync="dialogVisible"
|
|
|
+ width="400px"
|
|
|
+ >
|
|
|
+ <h4 style="text-align: center;font-weight: bolder">{{ errorInfo }}</h4>
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
+ <el-button v-no-more-click @click="cancel">关 闭</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -15,7 +26,10 @@ export default {
|
|
|
stage: null,
|
|
|
layer: null,
|
|
|
cachedResults: {},
|
|
|
- slotData: []
|
|
|
+ cachedImages: {}, // 缓存已加载的图片节点
|
|
|
+ slotData: [],
|
|
|
+ dialogVisible: false,
|
|
|
+ errorInfo: ''
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -23,6 +37,9 @@ export default {
|
|
|
this.getData()
|
|
|
},
|
|
|
methods: {
|
|
|
+ cancel() {
|
|
|
+ this.dialogVisible = false
|
|
|
+ },
|
|
|
async getData() {
|
|
|
const data = {
|
|
|
current: 1,
|
|
|
@@ -48,12 +65,16 @@ export default {
|
|
|
return map
|
|
|
}, {})
|
|
|
|
|
|
+ await this.preloadImages()
|
|
|
this.renderSlots()
|
|
|
} catch (err) {
|
|
|
console.error('获取数据失败:', err)
|
|
|
}
|
|
|
},
|
|
|
-
|
|
|
+ showErrorDialog(slot) {
|
|
|
+ this.errorInfo = slot.remark || '未知异常'
|
|
|
+ this.dialogVisible = true
|
|
|
+ },
|
|
|
initKonva() {
|
|
|
this.stage = new Konva.Stage({
|
|
|
container: this.$refs.container,
|
|
|
@@ -63,45 +84,41 @@ export default {
|
|
|
this.layer = new Konva.Layer()
|
|
|
this.stage.add(this.layer)
|
|
|
},
|
|
|
-
|
|
|
+ async preloadImages() {
|
|
|
+ const urls = Object.values(this.cachedResults)
|
|
|
+ const promises = urls.map(url => this.loadImageOnce(url))
|
|
|
+ await Promise.all(promises)
|
|
|
+ },
|
|
|
async renderSlots() {
|
|
|
this.layer.destroyChildren()
|
|
|
|
|
|
- // 分组
|
|
|
const keySlots = this.slotData.filter(item => item.slotType === '0')
|
|
|
const lockSlots = this.slotData.filter(item => item.slotType === '1')
|
|
|
|
|
|
- // 先渲染钥匙类
|
|
|
await this.renderSlotRow(keySlots, 50)
|
|
|
-
|
|
|
- // 再渲染挂锁类
|
|
|
await this.renderSlotRow(lockSlots, 170)
|
|
|
|
|
|
this.layer.draw()
|
|
|
},
|
|
|
async renderSlotRow(slots, boxTopY) {
|
|
|
- const padding = 20 // 图片与边框的内边距
|
|
|
+ const padding = 20
|
|
|
const boxWidth = 700
|
|
|
- const boxHeight = 120 // 黑框高度
|
|
|
-
|
|
|
+ const boxHeight = 120
|
|
|
const centerX = this.stage.width() / 2
|
|
|
const boxStartX = centerX - boxWidth / 2
|
|
|
|
|
|
- // 绘制黑色边框容器
|
|
|
const box = new Konva.Rect({
|
|
|
x: boxStartX,
|
|
|
y: boxTopY,
|
|
|
width: boxWidth,
|
|
|
height: boxHeight,
|
|
|
stroke: 'black',
|
|
|
- strokeWidth: 2,
|
|
|
- cornerRadius: 0
|
|
|
+ strokeWidth: 2
|
|
|
})
|
|
|
this.layer.add(box)
|
|
|
|
|
|
const loadedImages = []
|
|
|
|
|
|
- // 预加载图像(不渲染)
|
|
|
for (const slot of slots) {
|
|
|
const { slotType, isOccupied } = slot
|
|
|
let baseKey = ''
|
|
|
@@ -112,10 +129,9 @@ export default {
|
|
|
}
|
|
|
|
|
|
const baseUrl = this.cachedResults[baseKey]
|
|
|
- if (!baseUrl) continue
|
|
|
-
|
|
|
- const imageNode = await this.loadImage(baseUrl)
|
|
|
+ if (!baseUrl || !this.cachedImages[baseUrl]) continue
|
|
|
|
|
|
+ const imageNode = this.cachedImages[baseUrl].clone()
|
|
|
const width = slotType === '0' ? 110 : 40
|
|
|
const height = 90
|
|
|
imageNode.setAttrs({ width, height })
|
|
|
@@ -123,10 +139,10 @@ export default {
|
|
|
loadedImages.push({ imageNode, slot, width, height })
|
|
|
}
|
|
|
|
|
|
- // 平均分布计算(在 box 内部)
|
|
|
const totalSlots = loadedImages.length
|
|
|
- const spacing = (boxWidth - 2 * padding - totalSlots * loadedImages[0].width) / (totalSlots - 1)
|
|
|
-
|
|
|
+ const spacing = totalSlots > 1
|
|
|
+ ? (boxWidth - 2 * padding - totalSlots * loadedImages[0].width) / (totalSlots - 1)
|
|
|
+ : 0
|
|
|
let currentX = boxStartX + padding
|
|
|
|
|
|
for (const { imageNode, slot, width, height } of loadedImages) {
|
|
|
@@ -136,44 +152,47 @@ export default {
|
|
|
})
|
|
|
this.layer.add(imageNode)
|
|
|
|
|
|
- // 如果异常,添加异常图标
|
|
|
if (slot.status === '1') {
|
|
|
- const exceptionUrl = this.cachedResults['icon.locker.exception']
|
|
|
- if (exceptionUrl) {
|
|
|
- const exceptionImage = await this.loadImage(exceptionUrl)
|
|
|
+ const exUrl = this.cachedResults['icon.locker.exception']
|
|
|
+ if (exUrl && this.cachedImages[exUrl]) {
|
|
|
+ const exImage = this.cachedImages[exUrl].clone()
|
|
|
const exWidth = 30
|
|
|
const exHeight = 30
|
|
|
- exceptionImage.setAttrs({
|
|
|
+ exImage.setAttrs({
|
|
|
x: currentX + (width - exWidth) / 2,
|
|
|
y: boxTopY + (boxHeight - exHeight) / 2,
|
|
|
width: exWidth,
|
|
|
height: exHeight
|
|
|
})
|
|
|
- this.layer.add(exceptionImage)
|
|
|
+ exImage.on('click', () => {
|
|
|
+ this.showErrorDialog(slot)
|
|
|
+ })
|
|
|
+ this.layer.add(exImage)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
currentX += width + spacing
|
|
|
}
|
|
|
},
|
|
|
+ loadImageOnce(url) {
|
|
|
+ if (this.cachedImages[url]) return Promise.resolve(this.cachedImages[url])
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- loadImage(url) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
- const image = new window.Image()
|
|
|
- image.crossOrigin = 'Anonymous'
|
|
|
- image.src = url
|
|
|
- image.onload = () => {
|
|
|
- const konvaImage = new Konva.Image({ image })
|
|
|
+ const img = new window.Image()
|
|
|
+ img.crossOrigin = 'Anonymous'
|
|
|
+ img.src = url
|
|
|
+ img.onload = () => {
|
|
|
+ const konvaImage = new Konva.Image({ image: img })
|
|
|
+ this.cachedImages[url] = konvaImage
|
|
|
resolve(konvaImage)
|
|
|
}
|
|
|
- image.onerror = reject
|
|
|
+ img.onerror = reject
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
+
|
|
|
<style scoped lang="scss">
|
|
|
</style>
|