| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- <template>
- <div>
- <div class="time-card">
- <i class="el-icon-time"></i>
- <div class="time-content">
- <div class="label">{{ $t('mes.lockCabinet.lastUpdate') }}</div>
- <div class="time">{{ updateTime }}</div>
- </div>
- </div>
- <div ref="container" style="width: 900px; height: 400px;margin: 0 auto"></div>
- <el-dialog :title="$t('mes.lockCabinet.exceptionInfo')" :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">{{ $t('common.close') }}</el-button>
- </div>
- </el-dialog>
- </div>
- </template>
- <script>
- import Konva from 'konva'
- import { getIsSystemAttributeByKey } from '@/api/system/configuration'
- import { getIsLockCabinetSlotsPage } from '@/api/mes/lockCabinet/slots'
- export default {
- data() {
- return {
- stage: null,
- layer: null,
- cachedResults: {},
- cachedImages: {},
- slotData: [],
- dialogVisible: false,
- errorInfo: '',
- updateTime: null
- }
- },
- mounted() {
- this.initKonva()
- this.getData()
- },
- methods: {
- cancel() {
- this.dialogVisible = false
- },
- async getData() {
- const data = {
- current: 1,
- size: -1,
- cabinetId: this.$route.query.cabinetId
- }
- try {
- const res = await getIsLockCabinetSlotsPage(data)
- this.updateTime = res.data.records[0].updateTime
- this.slotData = res.data?.records || []
- const icons = [
- 'icon.locker.normal',
- 'icon.locker.out',
- 'icon.padlock.normal',
- 'icon.padlock.out',
- 'icon.locker.exception'
- ]
- const results = await Promise.all(icons.map(key => getIsSystemAttributeByKey(key)))
- this.cachedResults = icons.reduce((map, key, idx) => {
- map[key] = results[idx].data?.sysAttrValue || ''
- return map
- }, {})
- await this.preloadImages()
- this.renderSlots()
- } catch (err) {
- console.error(this.$t('mes.lockCabinet.getDataFailed'), err)
- }
- },
- showErrorDialog(slot) {
- this.errorInfo = slot.remark || this.$t('mes.lockCabinet.unknownException')
- this.dialogVisible = true
- },
- initKonva() {
- this.stage = new Konva.Stage({
- container: this.$refs.container,
- width: 900,
- height: 800
- })
- 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 grouped = {}
- for (const slot of this.slotData) {
- const key = `${slot.row}`
- if (!grouped[key]) grouped[key] = []
- grouped[key].push(slot)
- }
- const rows = Object.keys(grouped).sort((a, b) => Number(a) - Number(b))
- const startY = 20
- const rowHeight = 120
- const rowGap = 20
- for (let i = 0; i < rows.length; i++) {
- const rowSlots = grouped[rows[i]]
- await this.renderSlotRow(rowSlots, startY + i * (rowHeight + rowGap), rowHeight)
- }
- this.layer.draw()
- },
- async renderSlotRow(slots, boxTopY, boxHeight) {
- const padding = 20
- const boxWidth = 860
- 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
- })
- this.layer.add(box)
- const loadedImages = []
- for (const slot of slots) {
- const { slotType, isOccupied } = slot
- let baseKey = ''
- if (slotType === '0') {
- baseKey = isOccupied === '1' ? 'icon.locker.normal' : 'icon.locker.out'
- } else {
- baseKey = isOccupied === '1' ? 'icon.padlock.normal' : 'icon.padlock.out'
- }
- const baseUrl = this.cachedResults[baseKey]
- 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 })
- loadedImages.push({ imageNode, slot, width, height })
- }
- const totalSlots = loadedImages.length
- 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) {
- imageNode.setAttrs({
- x: currentX,
- y: boxTopY + (boxHeight - height) / 2
- })
- this.layer.add(imageNode)
- if (slot.status === '1') {
- const exUrl = this.cachedResults['icon.locker.exception']
- if (exUrl && this.cachedImages[exUrl]) {
- const exImage = this.cachedImages[exUrl].clone()
- const exWidth = 30
- const exHeight = 30
- exImage.setAttrs({
- x: currentX + (width - exWidth) / 2,
- y: boxTopY + (boxHeight - exHeight) / 2,
- width: exWidth,
- height: exHeight
- })
- 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])
- return new Promise((resolve, reject) => {
- 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)
- }
- img.onerror = reject
- })
- }
- }
- }
- </script>
- <style scoped lang="scss">
- .time-card {
- display: inline-flex;
- align-items: center;
- padding: 12px 20px;
- background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: all 0.3s;
- }
- .time-card.glow {
- box-shadow: 0 0 15px rgba(64, 158, 255, 0.5);
- }
- .time-card i {
- font-size: 24px;
- color: #409EFF;
- margin-right: 12px;
- }
- .time-content .label {
- font-size: 12px;
- color: #909399;
- }
- .time-content .time {
- font-size: 16px;
- font-weight: bold;
- color: #303133;
- }
- </style>
|