From f90f717f0d2e258b6310458c9ca0026cc258d24a Mon Sep 17 00:00:00 2001 From: luxiaotao1123 <t1341870251@163.com> Date: 星期一, 08 四月 2024 13:06:17 +0800 Subject: [PATCH] # --- zy-asrs-flow/src/pages/map/utils.js | 652 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 648 insertions(+), 4 deletions(-) diff --git a/zy-asrs-flow/src/pages/map/utils.js b/zy-asrs-flow/src/pages/map/utils.js index dd542bd..cd651d7 100644 --- a/zy-asrs-flow/src/pages/map/utils.js +++ b/zy-asrs-flow/src/pages/map/utils.js @@ -1,8 +1,18 @@ +import * as PIXI from 'pixi.js'; +import * as TWEEDLE from 'tweedle.js'; +import Http from '@/utils/http'; +import { message } from 'antd'; +import { API_TIMEOUT } from '@/config/setting' - +import agv from '/public/img/map/agv.svg' +import shelf from '/public/img/map/shelf.png' +import point from '/public/img/map/point.svg' +import shuttle from '/public/img/map/shuttle.svg' let app = null; let mapContainer = null; +let notify = null; +let selectedSprite, effectTick, effectHalfCircle, effectRectangle; export function syncApp(param) { app = param; @@ -12,10 +22,644 @@ mapContainer = param; } -export const getRealPosition = (x, y) => { +export function syncNotify(param) { + notify = param; +} + +export function getMapContainer() { + return mapContainer; +} + +export const MapEvent = Object.freeze({ + SELECTION_BOX: Symbol.for(0), +}) + +export const SENSOR_TYPE = Object.freeze({ + SHELF: "SHELF", + SHUTTLE: "SHUTTLE", + POINT: "POINT", + AGV: "AGV", +}) + +export const SENSOR_ZINDEX = Object.freeze({ + SHELF: 1, + POINT: 1, + SHUTTLE: 100, + AGV: 100, +}) + +export const SHELF_TYPE = Object.freeze({ + STORE: 0, + TRACK: 3, + DISABLE: 1, +}) + +export const NOTIFY_TYPE = Object.freeze({ + OPEN: 'open', + SUCCESS: 'success', + INFO: 'info', + WARNING: 'warning', + ERROR: 'error', +}) + +export const getRealPosition = (x, y, mapContainer) => { const rect = app.view.getBoundingClientRect(); return { - mapX: x - rect.left, - mapY: y - rect.top + mapX: (x - rect.left) / mapContainer.scale.x - mapContainer.x / mapContainer.scale.x, + mapY: (y - rect.top) / mapContainer.scale.y - mapContainer.y / mapContainer.scale.y } +} + +export const generateSprite = (sensorType) => { + let sprite; + switch (sensorType) { + case SENSOR_TYPE.SHUTTLE: + sprite = new PIXI.Sprite(PIXI.Texture.from(shuttle, { resourceOptions: { scale: 5 } })); + sprite.width = 50; + sprite.height = 50; + sprite.zIndex = SENSOR_ZINDEX.SHUTTLE; + break; + case SENSOR_TYPE.AGV: + sprite = new PIXI.Sprite(PIXI.Texture.from(agv, { resourceOptions: { scale: 5 } })); + sprite.width = 50; + sprite.height = 50; + sprite.zIndex = SENSOR_ZINDEX.AGV; + break; + case SENSOR_TYPE.SHELF: + sprite = new PIXI.Sprite(PIXI.Texture.from(shelf, { resourceOptions: { scale: 1 } })); + sprite.width = 60; + sprite.height = 60; + sprite.zIndex = SENSOR_ZINDEX.SHELF; + break; + case SENSOR_TYPE.POINT: + sprite = new PIXI.Sprite(PIXI.Texture.from(point, { resourceOptions: { scale: 5 } })); + sprite.width = 10; + sprite.height = 10; + sprite.zIndex = SENSOR_ZINDEX.POINT; + break; + default: + break; + } + return sprite; +} + +export const initSprite = (sprite, type) => { + sprite.anchor.set(0.5); + sprite.cursor = 'pointer'; + sprite.eventMode = 'static'; + sprite.data = { + type: type, + uuid: generateID() + }; +} + +export const querySprite = (type, no) => { + if (!mapContainer) { + return; + } + for (const sprite of mapContainer.children) { + if (sprite.data?.type === type && sprite.data?.no === no) { + return sprite; + } + } +} + +// show sprite feature from sprite click event +export const viewFeature = (sprite, setCurSPrite) => { + sprite.off('pointerup'); + sprite.off('pointermove'); + sprite.off('pointerdown'); + sprite.off('click'); + + sprite.on("click", onClick); + + function onClick(event) { + setCurSPrite(sprite); + } + +} + +// sprite be movable from sprite click event +export const beMovable = (sprite) => { + sprite.off('pointerup'); + sprite.off('pointermove'); + sprite.off('pointerdown'); + sprite.off('click'); + + sprite.on("pointerdown", onDragStart); + + let dragTarget; + function onDragStart(event) { + if (event.button === 0) { + dragTarget = event.currentTarget; + mapContainer.parent.off('pointermove'); + mapContainer.parent.on('pointermove', onDragMove, dragTarget); + + mapContainer.parent.off('pointerup'); + mapContainer.parent.on('pointerup', onDragEnd.bind(mapContainer)); + } + } + + function onDragMove(event) { + if (this) { + this.parent.toLocal(event.global, null, this.position); + } + } + + function onDragEnd() { + if (dragTarget) { + this.parent.off('pointermove'); + this.parent.off('pointerup'); + dragTarget.alpha = 1; + dragTarget = null; + } + } + +} + +// sprite be beSettings from sprite click event +export const beSettings = (sprite, setSpriteBySettings) => { + sprite.off('pointerup'); + sprite.off('pointermove'); + sprite.off('pointerdown'); + sprite.off('click'); + + sprite.on("click", onClick); + + function onClick(event) { + setSpriteBySettings(sprite); + } +} + +// sprites be movable from select box +// the scale was dynamic +export const spriteListBeMovable = (selectedSprites, resetFn) => { + if (selectedSprites && selectedSprites.length > 0) { + let batchMove = false; + let batchMoveStartPos = null; + + const batchMoving = (event) => { + const scale = mapContainer.scale.x; + if (batchMove && batchMoveStartPos) { + // offset move val + var mouseMovement = { + x: (event.global.x - batchMoveStartPos.x) / scale, + y: (event.global.y - batchMoveStartPos.y) / scale + }; + for (let sprite of selectedSprites) { + sprite.position.x = sprite.batchMoveStartPos.x + mouseMovement.x; + sprite.position.y = sprite.batchMoveStartPos.y + mouseMovement.y; + } + } + } + + const batchMoveEnd = (event) => { + batchMove = false; + batchMoveStartPos = null; + selectedSprites.forEach(child => { + unMarkSprite(child); + }) + selectedSprites = []; + mapContainer.parent.off('mousedown'); + mapContainer.parent.off('mousemove'); + mapContainer.parent.off('mouseup'); + + if (resetFn) { + resetFn(); + } + } + + const batchMoveStart = (event) => { + batchMoveStartPos = { x: event.data.global.clone().x, y: event.data.global.clone().y }; + selectedSprites.forEach(child => { + child.batchMoveStartPos = { x: child.position.x, y: child.position.y }; + }) + + batchMove = true; + mapContainer.parent.off('mousemove'); + mapContainer.parent.on('mousemove', batchMoving); + + mapContainer.parent.off('mouseup'); + mapContainer.parent.on('mouseup', batchMoveEnd); + } + + mapContainer.parent.off('mousedown') + mapContainer.parent.on('mousedown', batchMoveStart); + } +} + +export const isSpriteInSelectionBox = (sprite, selectionBox) => { + const spriteBounds = sprite.getBounds(); + const boxBounds = selectionBox.getBounds(); + + return spriteBounds.x + spriteBounds.width > boxBounds.x + && spriteBounds.x < boxBounds.x + boxBounds.width + && spriteBounds.y + spriteBounds.height > boxBounds.y + && spriteBounds.y < boxBounds.y + boxBounds.height; +} + +export const showSelectedEffect = (sprite) => { + const { width, height } = sprite; + const scale = sprite.scale.x; + const sideLen = (Math.max(width, height) + 10) * scale; + const color = app.renderer.backgroundColor === '#f1f2f6' ? 0x273c75 : 0xffffff; + + effectHalfCircle = new PIXI.Graphics(); + effectHalfCircle.beginFill(color); + effectHalfCircle.lineStyle(2 * scale, color); + effectHalfCircle.arc(0, 0, sideLen, 0, Math.PI); + effectHalfCircle.endFill(); + effectHalfCircle.position.set(sprite.x, sprite.y); + effectHalfCircle.scale.set(1 / scale); + effectHalfCircle.zIndex = 9999; + + effectRectangle = new PIXI.Graphics(); + effectRectangle.lineStyle(5 * scale, color, 1); + effectRectangle.drawRoundedRect(0, 0, sideLen, sideLen, 16 * scale); + effectRectangle.endFill(); + effectRectangle.mask = effectHalfCircle; + effectRectangle.zIndex = 9999; + + const scaledWidth = sideLen * (1 / scale); + const scaledHeight = sideLen * (1 / scale); + + effectRectangle.scale.set(1 / scale); + effectRectangle.position.set(sprite.x - scaledWidth / 2, sprite.y - scaledHeight / 2); + + mapContainer.addChild(effectRectangle); + mapContainer.addChild(effectHalfCircle); + + selectedSprite = sprite; + + let phase = 0; + effectTick = (delta) => { + phase += delta / 10; + phase %= (Math.PI * 2); + if (effectHalfCircle) { + effectHalfCircle.rotation = phase; + } + }; + + app.ticker.add(effectTick); +} + +export const updateEffect = (sprite) => { + if (!sprite || sprite !== selectedSprite || !effectRectangle || !effectHalfCircle) { + return + } + const { width, height } = sprite; + const scale = sprite.scale.x; + const sideLen = (Math.max(width, height) + 10) * scale; + const scaledWidth = sideLen * (1 / scale); + const scaledHeight = sideLen * (1 / scale); + + effectRectangle.position.set(sprite.x - scaledWidth / 2, sprite.y - scaledHeight / 2); + + effectHalfCircle.position.set(sprite.x, sprite.y); +} + +export const removeSelectedEffect = () => { + if (effectTick) { + app.ticker.remove(effectTick); + } + if (effectHalfCircle) { + mapContainer.removeChild(effectHalfCircle); + effectHalfCircle = null; + } + if (effectRectangle) { + mapContainer.removeChild(effectRectangle); + effectRectangle = null; + } + selectedSprite = null; +} + +export const copySprite = (sprite) => { + // const copiedSprite = new PIXI.Sprite(sprite.texture); + const copiedSprite = generateSprite(sprite.data.type) + initSprite(copiedSprite); + copiedSprite.position.set(sprite.position.x, sprite.position.y); + copiedSprite.scale.set(sprite.scale.x, sprite.scale.y); + copiedSprite.rotation = sprite.rotation; + copiedSprite.data = deepCopy(sprite.data); + copiedSprite.data.uuid = generateID(); + if (copiedSprite.data.type === SENSOR_TYPE.SHELF) { + showSheflType(copiedSprite); + } + return copiedSprite; +} + +export const markSprite = (sprite) => { + sprite.alpha = 0.5; +} + +export const unMarkSprite = (sprite) => { + sprite.alpha = 1; +} + +export const generateID = () => { + return Date.now().toString(36) + Math.random().toString(36).substring(2); +} + +export const deepCopy = (data) => { + return JSON.parse(JSON.stringify(data)); +} + +export const pureNumStr = (param) => { + if (param) { + return Number(param); + } else { + return ''; + } +} + +export const rotationToNum = (rotation) => { + let res = rotation * 180 / Math.PI; + if (res < 0) { + res += 360; + } else if (res > 360) { + res -= 360; + } + return res; +} + +export const rotationParseNum = (num) => { + return num * Math.PI / 180; +} + +export const findSpriteByUuid = (uuid) => { + return mapContainer?.children?.find(child => child?.data?.uuid === uuid); +} + +export const sensorTypeSelectOptions = (intl) => { + let options = []; + Object.entries(SENSOR_TYPE).forEach(([key, value]) => { + switch (key) { + case SENSOR_TYPE.SHELF: + options.push({ + value: value, + label: intl.formatMessage({ id: 'map.sensor.type.shelf', defaultMessage: '璐ф灦' }) + }) + break; + case SENSOR_TYPE.SHUTTLE: + options.push({ + value: value, + label: intl.formatMessage({ id: 'map.sensor.type.shuttle', defaultMessage: '绌挎杞�' }) + }) + break; + case SENSOR_TYPE.AGV: + options.push({ + value: value, + label: intl.formatMessage({ id: 'map.sensor.type.agv', defaultMessage: '鏃犱汉灏忚溅' }) + }) + break; + case SENSOR_TYPE.POINT: + options.push({ + value: value, + label: intl.formatMessage({ id: 'map.sensor.type.point', defaultMessage: '瀹氫綅鐐�' }) + }) + break; + default: + break; + } + + }) + return options; +} + +export const fetchMapFloor = async () => { + const res = await Http.doPost('api/map/floor/list'); + if (res.code === 200) { + return eval(res.data); + } else { + mapNotify(res.msg, NOTIFY_TYPE.ERROR); + } +} + +export const fetchMapData = async (curFloor) => { + clearMapData(); + await Http.doPostPromise('api/map/list', { floor: curFloor }, (res) => { + const mapItemList = eval(res.data); + mapItemList.forEach(item => { + const sprite = generateSprite(item.type); + if (sprite) { + initSprite(sprite, item.type); + // data + sprite.data.uuid = item.uuid; + sprite.data.no = item.no; + + // dynamical data + Object.assign(sprite.data, item.property); + + showSheflType(sprite); + // graph + sprite.position.set(item.positionX, item.positionY); + sprite.scale.set(item.scaleX, item.scaleY); + sprite.rotation = rotationParseNum(item.rotation); + + mapContainer.addChild(sprite); + } + }) + + }).catch((error) => { + console.error(error); + }) +} + +export const saveMapData = async (intl, floor) => { + if (!mapContainer) { + return; + } + + let mapItemList = []; + mapContainer?.children.forEach(child => { + if (child.data?.uuid) { + const { type, uuid, no, ...property } = child.data; + mapItemList.push({ + // data + type: type, + uuid: uuid, + no: no, + property: property, + + // graph + positionX: child.position.x, + positionY: child.position.y, + scaleX: child.scale.x, + scaleY: child.scale.y, + rotation: rotationToNum(child.rotation) + }) + } + }) + + const closeLoading = message.loading({ content: intl.formatMessage({ id: 'common.loading.api.message', defaultMessage: '绛夊緟鏈嶅姟鍣�......' }), duration: API_TIMEOUT }); + await Http.doPostPromise('api/map/save', { + itemList: mapItemList, + floor: floor + }, (res) => { + closeLoading(); + }).catch((error) => { + closeLoading(); + console.error(error); + }) +} + +export const clearMapData = (intl) => { + if (!mapContainer) { + return; + } + let childList = []; + mapContainer.children.forEach(child => { + if (child.data?.uuid) { + childList.push(child); + } + }) + if (childList.length > 0) { + childList.forEach(child => { + mapContainer.removeChild(child); + child.destroy({ children: true, texture: false, baseTexture: false }); + }) + childList.forEach((child, index) => { + childList[index] = null; + }); + childList = []; + } +} + +export const beCenter = (sprite) => { + if (!sprite || !app || !mapContainer) { + return; + } + + mapContainer.scale.set(1); + mapContainer.position.set(0, 0); + + let bounds = sprite.getBounds(); + let centerPoint = { + x: bounds.x + bounds.width / 2, + y: bounds.y + bounds.height / 2 + }; + + let targetPos = { + x: app.renderer.width / 3 - centerPoint.x * mapContainer.scale.x, + y: app.renderer.height / 3 - centerPoint.y * mapContainer.scale.y + }; + + new TWEEDLE.Tween(mapContainer.position).easing(TWEEDLE.Easing.Quadratic.Out) + .to(targetPos, 500).start(); +} + +export const mapNotify = (msg, type = NOTIFY_TYPE.OPEN) => { + notify[type]({ + description: msg, + duration: 1.5, + style: { width: 300 }, + placement: 'bottom', + closeIcon: false, + onClick: () => { } + }); +} + +export const showSheflType = (sprite) => { + let showColor; + switch (sprite.data.shelfType) { + case SHELF_TYPE.STORE: + break; + case SHELF_TYPE.TRACK: + showColor = '#faf6e9'; + break; + case SHELF_TYPE.DISABLE: + showColor = '#ffc8c8'; + break; + default: + break; + } + if (showColor) { + sprite.tint = showColor; + } else { + sprite.tint = 0xFFFFFF; // recovery + } + return showColor; +} + +export const waitTime = (time = 1000) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, time); + }); +}; + +export const isNullOfUndefined = (param) => { + if (null === param || undefined === param) { + return true; + } else { + return false; + } +} + +export const parseLocNo = (locNo) => { + if (!locNo || typeof locNo !== 'string') { + return null; + } + const locParseArr = locNo.split('-'); + return { + row: locParseArr?.[0], + bay: locParseArr?.[1], + lev: locParseArr?.[2], + } +} + +export const generateLocNo = (row, bay, lev) => { + return row + '-' + bay + '-' + lev; +} + +export const updateMapStatusInRealTime = (data) => { + const mapVo = JSON.parse(data); + // shuttle + for (const shuttleVo of mapVo.shuttleVos) { + const shuttle = querySprite(SENSOR_TYPE.SHUTTLE, shuttleVo.shuttleNo); + if (!shuttle && !shuttleVo.curLocNo) { continue; } + const { row, bay, lev } = parseLocNo(shuttleVo.curLocNo); + const shelf = querySprite(SENSOR_TYPE.SHELF, row + '-' + bay); + if (!shelf) { continue; } + + drawPreTravelPath(shuttleVo.preTravelPath) + + new TWEEDLE.Tween(shuttle?.position).easing(TWEEDLE.Easing.Linear.None).to({ + x: shelf.position.x, + y: shelf.position.y + }, 1000).onUpdate(() => { + updateEffect(shuttle); + }).start(); + } +} + + +export const drawPreTravelPath = (path) => { + if (!mapContainer) { + return; + } + + let pathLine = mapContainer.getChildByName('preTravelPath'); + if (pathLine) mapContainer.removeChild(pathLine); + + pathLine = new PIXI.Graphics(); + pathLine.name = 'preTravelPath'; + pathLine.lineStyle(3 * (1 / mapContainer.scale.x), 0x3498db, 1); + pathLine.zIndex = 9999; + for (let i = 0; i < path.length; i++) { + const { row, bay, lev } = parseLocNo(path[i]); + const shelf = querySprite(SENSOR_TYPE.SHELF, row + '-' + bay); + + if (!shelf) continue; + let position = shelf.position; + let x = position.x; + let y = position.y; + if (i === 0) { + pathLine.moveTo(x, y); + } else { + pathLine.lineTo(x, y); + } + } + mapContainer.addChild(pathLine); } \ No newline at end of file -- Gitblit v1.9.1