MapData.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <div>
  3. <div ref="container" style="width: 900px; height: 400px;"></div>
  4. <el-dialog
  5. title="异常信息"
  6. :visible.sync="dialogVisible"
  7. width="400px"
  8. >
  9. <h4 style="text-align: center;font-weight: bolder">{{ errorInfo }}</h4>
  10. <div slot="footer" class="dialog-footer">
  11. <el-button v-no-more-click @click="cancel">关 闭</el-button>
  12. </div>
  13. </el-dialog>
  14. </div>
  15. </template>
  16. <script>
  17. import Konva from 'konva'
  18. import { getIsSystemAttributeByKey } from '@/api/system/configuration'
  19. import { getIsLockCabinetSlotsPage } from '@/api/mes/lockCabinet/slots'
  20. export default {
  21. data() {
  22. return {
  23. stage: null,
  24. layer: null,
  25. cachedResults: {},
  26. cachedImages: {}, // 缓存已加载的图片节点
  27. slotData: [],
  28. dialogVisible: false,
  29. errorInfo: ''
  30. }
  31. },
  32. mounted() {
  33. this.initKonva()
  34. this.getData()
  35. },
  36. methods: {
  37. cancel() {
  38. this.dialogVisible = false
  39. },
  40. async getData() {
  41. const data = {
  42. current: 1,
  43. size: -1,
  44. cabinetId: this.$route.query.cabinetId
  45. }
  46. try {
  47. const res = await getIsLockCabinetSlotsPage(data)
  48. this.slotData = res.data?.records || []
  49. const icons = [
  50. 'icon.locker.normal',
  51. 'icon.locker.out',
  52. 'icon.padlock.normal',
  53. 'icon.padlock.out',
  54. 'icon.locker.exception'
  55. ]
  56. const results = await Promise.all(icons.map(key => getIsSystemAttributeByKey(key)))
  57. this.cachedResults = icons.reduce((map, key, idx) => {
  58. map[key] = results[idx].data?.sysAttrValue || ''
  59. return map
  60. }, {})
  61. await this.preloadImages()
  62. this.renderSlots()
  63. } catch (err) {
  64. console.error('获取数据失败:', err)
  65. }
  66. },
  67. showErrorDialog(slot) {
  68. this.errorInfo = slot.remark || '未知异常'
  69. this.dialogVisible = true
  70. },
  71. initKonva() {
  72. this.stage = new Konva.Stage({
  73. container: this.$refs.container,
  74. width: 1800,
  75. height: 400
  76. })
  77. this.layer = new Konva.Layer()
  78. this.stage.add(this.layer)
  79. },
  80. async preloadImages() {
  81. const urls = Object.values(this.cachedResults)
  82. const promises = urls.map(url => this.loadImageOnce(url))
  83. await Promise.all(promises)
  84. },
  85. async renderSlots() {
  86. this.layer.destroyChildren()
  87. const keySlots = this.slotData.filter(item => item.slotType === '0')
  88. const lockSlots = this.slotData.filter(item => item.slotType === '1')
  89. await this.renderSlotRow(keySlots, 50)
  90. await this.renderSlotRow(lockSlots, 170)
  91. this.layer.draw()
  92. },
  93. async renderSlotRow(slots, boxTopY) {
  94. const padding = 20
  95. const boxWidth = 700
  96. const boxHeight = 120
  97. const centerX = this.stage.width() / 2
  98. const boxStartX = centerX - boxWidth / 2
  99. const box = new Konva.Rect({
  100. x: boxStartX,
  101. y: boxTopY,
  102. width: boxWidth,
  103. height: boxHeight,
  104. stroke: 'black',
  105. strokeWidth: 2
  106. })
  107. this.layer.add(box)
  108. const loadedImages = []
  109. for (const slot of slots) {
  110. const { slotType, isOccupied } = slot
  111. let baseKey = ''
  112. if (slotType === '0') {
  113. baseKey = isOccupied === '1' ? 'icon.locker.normal' : 'icon.locker.out'
  114. } else {
  115. baseKey = isOccupied === '1' ? 'icon.padlock.normal' : 'icon.padlock.out'
  116. }
  117. const baseUrl = this.cachedResults[baseKey]
  118. if (!baseUrl || !this.cachedImages[baseUrl]) continue
  119. const imageNode = this.cachedImages[baseUrl].clone()
  120. const width = slotType === '0' ? 110 : 40
  121. const height = 90
  122. imageNode.setAttrs({ width, height })
  123. loadedImages.push({ imageNode, slot, width, height })
  124. }
  125. const totalSlots = loadedImages.length
  126. const spacing = totalSlots > 1
  127. ? (boxWidth - 2 * padding - totalSlots * loadedImages[0].width) / (totalSlots - 1)
  128. : 0
  129. let currentX = boxStartX + padding
  130. for (const { imageNode, slot, width, height } of loadedImages) {
  131. imageNode.setAttrs({
  132. x: currentX,
  133. y: boxTopY + (boxHeight - height) / 2
  134. })
  135. this.layer.add(imageNode)
  136. if (slot.status === '1') {
  137. const exUrl = this.cachedResults['icon.locker.exception']
  138. if (exUrl && this.cachedImages[exUrl]) {
  139. const exImage = this.cachedImages[exUrl].clone()
  140. const exWidth = 30
  141. const exHeight = 30
  142. exImage.setAttrs({
  143. x: currentX + (width - exWidth) / 2,
  144. y: boxTopY + (boxHeight - exHeight) / 2,
  145. width: exWidth,
  146. height: exHeight
  147. })
  148. exImage.on('click', () => {
  149. this.showErrorDialog(slot)
  150. })
  151. this.layer.add(exImage)
  152. }
  153. }
  154. currentX += width + spacing
  155. }
  156. },
  157. loadImageOnce(url) {
  158. if (this.cachedImages[url]) return Promise.resolve(this.cachedImages[url])
  159. return new Promise((resolve, reject) => {
  160. const img = new window.Image()
  161. img.crossOrigin = 'Anonymous'
  162. img.src = url
  163. img.onload = () => {
  164. const konvaImage = new Konva.Image({ image: img })
  165. this.cachedImages[url] = konvaImage
  166. resolve(konvaImage)
  167. }
  168. img.onerror = reject
  169. })
  170. }
  171. }
  172. }
  173. </script>
  174. <style scoped lang="scss">
  175. </style>