<script setup>
|
import { ref, onMounted, watch } from 'vue';
|
import { get, post, postBlob } from '@/utils/request.js'
|
import { message, Modal } from 'ant-design-vue';
|
import { logout } from '@/config.js';
|
import { formatMessage } from '@/utils/localeUtils.js';
|
import {
|
CompressOutlined,
|
} from "@ant-design/icons-vue";
|
import * as PIXI from 'pixi.js'
|
|
let width = 25;
|
let height = 25;
|
let pixiApp;
|
let pixiStageMap = new Map();
|
let objectsContainer;
|
let map = []
|
|
const pixiView = ref(null)
|
const mapFps = ref(0)
|
const currentLev = ref(1)
|
const levList = ref([])
|
const pointContainerWidth = ref(0)
|
const drawer = ref(false)
|
const drawerLocData = ref(null)
|
const drawerLocDetls = ref([])
|
const drawerLocDetlField = ref([])
|
|
const drawerOper = ref(false)
|
const drawerOperLocNo = ref(null)
|
const drawerOperMatnr = ref(null)
|
const drawerOperMaktx = ref(null)
|
|
onMounted(() => {
|
createMap();
|
init(currentLev.value);
|
})
|
|
watch(drawer, (newVal, oldVal) => {
|
if (!drawer.value) {
|
var rectangle = pixiStageMap.get(drawerLocData.value.locNo)
|
updateColor(rectangle, rectangle.originColor);//恢复颜色
|
}
|
})
|
|
function switchLev(lev) {
|
currentLev.value = lev;
|
init(lev);
|
}
|
|
function init(lev) {
|
get('/api/locMap/getData/' + lev + '/auth', {}).then(resp => {
|
let result = resp.data;
|
if (result.code == 200) {
|
let tmp = JSON.parse(result.data);
|
let tmpMap = []
|
tmp.forEach((item, index) => {
|
let data2 = []
|
item.forEach((val, idx) => {
|
val.searchStatus = false//搜索标记
|
val.rectangle = null;
|
data2.push(val)
|
})
|
pointContainerWidth.value = item.length * (40 + 1)
|
tmpMap.push(data2)
|
})
|
|
createMapData(tmpMap);
|
}
|
})
|
|
get('/api/locMap/getLev', {}).then(resp => {
|
let result = resp.data;
|
if (result.code == 200) {
|
let tmp = result.data;
|
levList.value = tmp;
|
}
|
})
|
}
|
|
function createMap() {
|
//Create a Pixi Application
|
pixiApp = new PIXI.Application({
|
width: pixiView.value.offsetWidth,
|
height: window.innerHeight * 0.75,
|
backgroundColor: 0xF5F7F9FF,
|
// resizeTo: window
|
});
|
|
//Add the canvas that Pixi automatically created for you to the HTML document
|
pixiView.value.appendChild(pixiApp.view)
|
|
// 创建一个容器来管理大批量的显示对象
|
objectsContainer = new PIXI.Container();
|
|
pixiApp.stage.addChild(objectsContainer);
|
|
//*******************拖动画布*******************
|
let stageOriginalPos;
|
let mouseDownPoint;
|
let touchBlank = false;
|
pixiApp.renderer.plugins.interaction.on(
|
'pointerdown',
|
(event) => {
|
const globalPos = event.data.global;
|
// 记录下stage原来的位置
|
stageOriginalPos = [pixiApp.stage.position._x, pixiApp.stage.position._y];
|
// 记录下mouse down的位置
|
mouseDownPoint = [globalPos.x, globalPos.y];
|
if (!event.target) {
|
// 点到了画布的空白位置
|
touchBlank = true;
|
}
|
}
|
);
|
|
pixiApp.renderer.plugins.interaction.on(
|
'pointermove',
|
(event) => {
|
const globalPos = event.data.global;
|
|
if (touchBlank) {
|
// 拖拽画布
|
const dx = globalPos.x - mouseDownPoint[0];
|
const dy = globalPos.y - mouseDownPoint[1];
|
pixiApp.stage.position.set(
|
stageOriginalPos[0] + dx,
|
stageOriginalPos[1] + dy
|
);
|
}
|
}
|
);
|
|
pixiApp.renderer.plugins.interaction.on(
|
'pointerup',
|
(event) => {
|
touchBlank = false;
|
}
|
);
|
//*******************拖动画布*******************
|
|
//*******************缩放画布*******************
|
pixiApp.view.addEventListener('wheel', (event) => {
|
event.stopPropagation();
|
event.preventDefault();
|
// 因为画布是充满视窗的,所以clientX等于mouse point在renderer上的x坐标
|
const globalPos = [event.clientX, event.clientY];
|
const delta = event.deltaY;
|
const oldZoom = pixiApp.stage.scale.x;
|
let newZoom = oldZoom * 0.999 ** delta;
|
|
// const oldStageMatrix = app.stage.localTransform.clone();
|
// const oldStagePos = oldStageMatrix.applyInverse(pointerGlobalPos);
|
const oldStagePos = globalPos;
|
const dx = oldStagePos[0] * oldZoom - oldStagePos[0] * newZoom;
|
const dy = oldStagePos[1] * oldZoom - oldStagePos[1] * newZoom;
|
|
pixiApp.stage.setTransform(
|
pixiApp.stage.position.x + dx,
|
pixiApp.stage.position.y + dy,
|
newZoom,
|
newZoom,
|
0,
|
0,
|
0,
|
0,
|
0
|
);
|
|
});
|
//*******************缩放画布*******************
|
|
//*******************FPS*******************
|
var g_Time = 0;
|
pixiApp.ticker.add((delta) => {
|
var timeNow = (new Date()).getTime();
|
var timeDiff = timeNow - g_Time;
|
g_Time = timeNow;
|
var fps = 1000 / timeDiff;
|
mapFps.value = parseInt(fps)
|
});
|
//*******************FPS*******************
|
}
|
|
function createMapData(map) {
|
objectsContainer.removeChildren();
|
map.forEach((item, index) => {
|
for (let idx = 0; idx < item.length; idx++) {
|
let val = item[idx]
|
if (val.value < 0) {
|
continue;
|
}
|
let rectangle = getContainer(val.value, idx * width, index * height, map[index][idx].locSts);
|
rectangle.on('click', (e) => {
|
openLocDrawer(index, idx, map[index][idx], { x: e.data.originalEvent.offsetX, y: e.data.originalEvent.offsetY })
|
updateColor(rectangle, 0x9900ff);
|
});
|
rectangle.locX = index;
|
rectangle.locY = idx;
|
|
pixiStageMap.set(map[index][idx].locNo, rectangle);
|
objectsContainer.addChild(rectangle);
|
}
|
})
|
|
//视角居中
|
let containerWidth = (pixiApp.view.width - objectsContainer.width) / 2;
|
let containerHeight = (pixiApp.view.height - objectsContainer.height) / 2;
|
pixiApp.stage.position.set(containerWidth, containerHeight);
|
}
|
|
function containerAppViewCenter() {
|
//视角居中
|
let containerWidth = (pixiApp.view.width - objectsContainer.width) / 2;
|
let containerHeight = (pixiApp.view.height - objectsContainer.height) / 2;
|
pixiApp.stage.position.set(containerWidth, containerHeight);
|
}
|
|
function openLocDrawer(x, y, loc, e) {
|
drawer.value = true;
|
drawerLocData.value = loc;
|
|
get('/api/locDetl/locNo/' + loc.locNo, {}).then(resp => {
|
let result = resp.data;
|
if (result.code == 200) {
|
drawerLocDetls.value = result.data;
|
}
|
})
|
}
|
|
function getContainer(value, x, y, locSts) {
|
let rectangle = new PIXI.Graphics();
|
if (value === 0) {
|
if (locSts === "F") {
|
rectangle.beginFill(0xff0000);
|
rectangle.originColor = 0xff0000;
|
} else if (locSts === "O") {
|
rectangle.beginFill(0x55aaff);
|
rectangle.originColor = 0x55aaff;
|
} else if (locSts === "D") {
|
rectangle.beginFill(0xc2c934);
|
rectangle.originColor = 0xc2c934;
|
} else if (locSts === "P") {
|
rectangle.beginFill(0xf1aa19);
|
rectangle.originColor = 0xf1aa19;
|
} else if (locSts === "R") {
|
rectangle.beginFill(0x618593);
|
rectangle.originColor = 0x618593;
|
} else if (locSts === "S") {
|
rectangle.beginFill(0xfa736f);
|
rectangle.originColor = 0xfa736f;
|
} else {
|
rectangle.beginFill(0x86779d);
|
rectangle.originColor = 0x86779d;
|
}
|
} else if (value === 3) {//母轨道
|
rectangle.beginFill(0x00ff7f);
|
rectangle.originColor = 0x00ff7f;
|
rectangle.visible = false;
|
} else if (value === 4) {//站点
|
rectangle.beginFill(0xffff00);
|
rectangle.originColor = 0xffff00;
|
rectangle.visible = false;
|
} else if (value === 5) {//充电桩
|
rectangle.beginFill(0xffaa7f);
|
rectangle.originColor = 0xffaa7f;
|
rectangle.visible = false;
|
} else if (value === 9) {//轨迹
|
rectangle.beginFill(0xff0000);
|
rectangle.originColor = 0xff0000;
|
}
|
// rectangle.lineStyle(1, 0xffffff, 1);
|
rectangle.drawRect(0, 0, width, height);
|
rectangle.x = x;
|
rectangle.y = y;
|
// 设置是否可以交互
|
rectangle.interactive = true;
|
rectangle.endFill();
|
|
// 创建文本对象
|
const style = new PIXI.TextStyle({
|
fontSize: 14 * window.devicePixelRatio,
|
fill: 'white',
|
align: 'center', // 设置文本水平居中对齐
|
verticalAlign: 'middle' // 设置文本垂直居中对齐
|
});
|
const text = new PIXI.Text(locSts, style);
|
text.anchor.set(0.5); // 设置文本锚点为中心点
|
text.position.set(rectangle.width / 2, rectangle.height / 2); // 将文本位置设置为Graphics对象的中心点
|
// 将文本对象添加到Graphics对象中
|
rectangle.addChild(text);
|
|
return rectangle;
|
}
|
|
/**
|
* 更新颜色
|
*/
|
function updateColor(rectangle, color) {
|
rectangle.clear()
|
rectangle.beginFill(color);
|
rectangle.drawRect(0, 0, width, height);
|
}
|
|
</script>
|
|
<script>
|
export default {
|
name: '库位地图'
|
}
|
</script>
|
|
<template>
|
<div style="position: relative;overflow: hidden;">
|
<div ref="pixiView">
|
|
</div>
|
|
<!--输出操作和FPS-->
|
<div style="position: absolute;top: 0px;right: 10px;user-select: none;">
|
<div>FPS:{{ mapFps }}</div>
|
<a-button @click="drawerOper = true">操作</a-button>
|
</div>
|
|
<!--输出操作和FPS-->
|
<div style="position: absolute;bottom: 20px;left: 20px;user-select: none;">
|
<a-button type="dashed" @click="containerAppViewCenter">
|
<CompressOutlined />
|
</a-button>
|
</div>
|
|
<div>
|
<a-drawer v-model:open="drawer" placement="right" style="background: #f3f3f3;">
|
<div style="margin-top: 10px;">
|
<a-tag>{{ formatMessage('locMap.locNo', '库位号') }}</a-tag>{{ drawerLocData.locNo }}
|
</div>
|
<div style="margin-top: 20px;">
|
<a-tag>{{ formatMessage('locMap.locSts', '库位状态') }}</a-tag>{{ drawerLocData.locSts }}.{{
|
drawerLocData.locSts$ }}
|
</div>
|
<div style="margin-top: 20px;">
|
<div v-for="(item, index) in drawerLocDetls" :key="index" style="margin-top: 20px;">
|
<a-card :title="item.matnr" :bordered="false" style="width: 300px">
|
<div>{{ formatMessage('locMap.batch', '批号') }}:{{ item.batch }}</div>
|
<div>{{ formatMessage('locMap.orderNo', '单据编号') }}:{{ item.orderNo }}</div>
|
<div>{{ formatMessage('locMap.anfme', '数量') }}:{{ item.anfme }}</div>
|
<div v-for="(field, index) in item.dynamicFieldsList" :key="index">{{
|
formatMessage('locMap.' + field.key, field.desc) }}:{{ field.value }}</div>
|
</a-card>
|
</div>
|
</div>
|
</a-drawer>
|
</div>
|
|
<div>
|
<a-drawer v-model:open="drawerOper" placement="right">
|
<div style="margin-top: 10px;">
|
<div>
|
{{ formatMessage('locMap.locNo', '库位号') }}
|
</div>
|
<div style="margin-top: 10px;">
|
<a-input v-model:value="drawerOperLocNo" :placeholder="formatMessage('locMap.locNo', '库位号')" />
|
</div>
|
</div>
|
<div style="margin-top: 20px;">
|
<div>
|
{{ formatMessage('locMap.matnr', '商品编号') }}
|
</div>
|
<div style="margin-top: 10px;">
|
<a-input v-model:value="drawerOperMatnr" :placeholder="formatMessage('locMap.matnr', '商品编号')" />
|
</div>
|
</div>
|
<div style="margin-top: 20px;">
|
<div>
|
{{ formatMessage('locMap.maktx', '商品名称') }}
|
</div>
|
<div style="margin-top: 10px;">
|
<a-input v-model:value="drawerOperMaktx" :placeholder="formatMessage('locMap.maktx', '商品名称')" />
|
</div>
|
</div>
|
<div style="margin-top: 20px;">
|
<a-button type="primary" @click="drawerOper = true">搜索</a-button>
|
</div>
|
|
<div style="margin-top: 50px;">
|
<div v-for="(lev, index) in levList" :key="index" style="margin-top: 10px;">
|
<a-button :type="currentLev == lev ? 'primary' : 'dashed'" @click="switchLev(lev)"
|
style="width: 100%;">{{ lev }}F</a-button>
|
</div>
|
</div>
|
</a-drawer>
|
</div>
|
</div>
|
</template>
|
|
<style>
|
* {
|
margin: 0;
|
padding: 0;
|
}
|
|
.pointContainer {
|
display: flex;
|
justify-content: center;
|
/*margin-top: 1px;*/
|
}
|
|
.pointBox {
|
background: #bababa;
|
width: 40px;
|
height: 40px;
|
/*margin-right: 1px;*/
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
font-size: 14px;
|
user-select: none;
|
color: #fff;
|
}
|
|
.pointBox:hover {
|
background: #00ff7f;
|
}
|
|
.pointBoxEmpty {
|
background: #c2c934;
|
}
|
|
.pointBoxOut {
|
background: #f1aa19;
|
}
|
|
.pointBoxOutYy {
|
background: #618593;
|
}
|
|
.pointBoxInYy {
|
background: #fa736f;
|
}
|
|
.pointBoxGreen {
|
background: #00ff7f;
|
}
|
|
.pointBoxBlue {
|
background: #55aaff;
|
}
|
|
.pointBoxRed {
|
background: #ff0000;
|
}
|
|
.pointBoxStart {
|
background: #ffaa00;
|
}
|
|
.pointBoxEnd {
|
background: #ff55ff;
|
}
|
|
.pointBoxStation {
|
background: #ffff00;
|
}
|
|
.chargeStation {
|
background: #ffaa7f;
|
}
|
|
.pointBoxDefault {
|
background: #86779d;
|
}
|
|
.pointBoxSelected {
|
background: #00ff7f !important;
|
}
|
|
.pointBoxSearch {
|
background: #9900ff;
|
}
|
|
.crnLine {
|
width: auto;
|
height: 2px;
|
margin: 10px 0;
|
background: #000;
|
position: relative;
|
}
|
|
.popBox {
|
position: absolute;
|
}
|
|
/*卡片样式start*/
|
.apple-card {
|
width: 190px;
|
height: 254px;
|
margin: 0 auto;
|
background-color: #011522;
|
border-radius: 8px;
|
z-index: 1;
|
animation: fadeInOut 0.5s 1;
|
}
|
|
.apple-card .tools {
|
display: flex;
|
align-items: center;
|
padding: 9px;
|
}
|
|
.apple-card .circle {
|
padding: 0 4px;
|
}
|
|
.apple-card .box {
|
display: inline-block;
|
align-items: center;
|
width: 10px;
|
height: 10px;
|
padding: 1px;
|
border-radius: 50%;
|
}
|
|
.apple-card .red {
|
background-color: #ff605c;
|
position: relative;
|
}
|
|
.apple-card .red:hover {
|
background-color: #ff0300;
|
}
|
|
.apple-card .red:hover::before {
|
content: "x";
|
font-size: 11px;
|
color: #fff;
|
width: 10px;
|
height: 10px;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
position: absolute;
|
animation: fadeInOut 0.5s 1;
|
}
|
|
.apple-card .yellow {
|
background-color: #ffbd44;
|
}
|
|
.apple-card .green {
|
background-color: #00ca4e;
|
}
|
|
.apple-card .card-content {
|
color: #fff;
|
padding: 10px;
|
}
|
|
/*卡片样式end*/
|
|
/*滑动卡片start*/
|
.hoverCard {
|
width: 150px;
|
height: 224px;
|
border-radius: 20px;
|
background: #f5f5f5;
|
position: relative;
|
padding: 1.8rem;
|
border: 2px solid #c3c6ce;
|
transition: 0.5s ease-out;
|
overflow: visible;
|
margin-top: 30px;
|
}
|
|
.hoverCard .card-details {
|
color: black;
|
height: 100%;
|
gap: .5em;
|
display: grid;
|
place-content: center;
|
}
|
|
.hoverCard .card-button {
|
transform: translate(-50%, 125%);
|
width: 60%;
|
border-radius: 1rem;
|
border: none;
|
background-color: #008bf8;
|
color: #fff;
|
font-size: 1rem;
|
padding: .5rem 1rem;
|
position: absolute;
|
left: 50%;
|
bottom: 0;
|
opacity: 0;
|
transition: 0.3s ease-out;
|
}
|
|
.hoverCard .text-body {
|
color: rgb(134, 134, 134);
|
}
|
|
/*Text*/
|
.hoverCard .text-title {
|
font-size: 1.5em;
|
font-weight: bold;
|
}
|
|
/*Hover*/
|
.hoverCard:hover {
|
border-color: #008bf8;
|
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.25);
|
}
|
|
.hoverCard:hover .card-button {
|
transform: translate(-50%, 50%);
|
opacity: 1;
|
}
|
|
/*滑动卡片end*/
|
|
/*楼层控制start*/
|
.floorSelect {
|
--text: #414856;
|
--radio: #7C96B2;
|
--radio-checked: #4F29F0;
|
--radio-size: 20px;
|
--width: 150px;
|
--height: 200px;
|
--border-radius: 10px;
|
width: var(--width);
|
height: var(--height);
|
border-radius: var(--border-radius);
|
color: var(--text);
|
position: relative;
|
box-shadow: 0 10px 30px rgba(65, 72, 86, 0.05);
|
display: grid;
|
grid-template-columns: auto var(--radio-size);
|
align-items: center;
|
}
|
|
.floorSelect label {
|
cursor: pointer;
|
}
|
|
.floorSelect input[type="radio"] {
|
-webkit-appearance: none;
|
-moz-appearance: none;
|
position: relative;
|
height: var(--radio-size);
|
width: var(--radio-size);
|
outline: none;
|
margin: 0;
|
cursor: pointer;
|
border: 2px solid var(--radio);
|
background: transparent;
|
border-radius: 50%;
|
display: grid;
|
justify-self: end;
|
justify-items: center;
|
align-items: center;
|
overflow: hidden;
|
transition: border .5s ease;
|
}
|
|
.floorSelect input[type="radio"]::before,
|
.floorSelect input[type="radio"]::after {
|
content: "";
|
display: flex;
|
justify-self: center;
|
border-radius: 50%;
|
}
|
|
.floorSelect input[type="radio"]::before {
|
position: absolute;
|
width: 100%;
|
height: 100%;
|
z-index: 1;
|
opacity: var(--opacity, 1);
|
}
|
|
.floorSelect input[type="radio"]::after {
|
position: relative;
|
width: calc(100% /2);
|
height: calc(100% /2);
|
background: var(--radio-checked);
|
top: var(--y, 100%);
|
transition: top 0.5s cubic-bezier(0.48, 1.97, 0.5, 0.63);
|
}
|
|
.floorSelect input[type="radio"]:checked {
|
--radio: var(--radio-checked);
|
}
|
|
.floorSelect input[type="radio"]:checked::after {
|
--y: 0%;
|
animation: stretch-animate .3s ease-out .17s;
|
}
|
|
.floorSelect input[type="radio"]:checked::before {
|
--opacity: 0;
|
}
|
|
.floorSelect input[type="radio"]:checked~input[type="radio"]::after {
|
--y: -100%;
|
}
|
|
.floorSelect input[type="radio"]:not(:checked)::before {
|
--opacity: 1;
|
transition: opacity 0s linear .5s;
|
}
|
|
@keyframes stretch-animate {
|
0% {
|
transform: scale(1, 1);
|
}
|
|
28% {
|
transform: scale(1.15, 0.85);
|
}
|
|
50% {
|
transform: scale(0.9, 1.1);
|
}
|
|
100% {
|
transform: scale(1, 1);
|
}
|
}
|
|
/*楼层控制end*/
|
|
/*搜索start*/
|
.search-input {
|
line-height: 28px;
|
border: 2px solid transparent;
|
border-bottom-color: #777;
|
padding: .2rem 0;
|
outline: none;
|
background-color: transparent;
|
color: #0d0c22;
|
transition: .3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
}
|
|
.search-input:focus,
|
.search-input:hover {
|
outline: none;
|
padding: .2rem 1rem;
|
border-radius: 1rem;
|
border-color: #7a9cc6;
|
}
|
|
.search-input::placeholder {
|
color: #777;
|
}
|
|
.search-input:focus::placeholder {
|
opacity: 0;
|
transition: opacity .3s;
|
}
|
|
/*搜索end*/
|
|
@keyframes fadeInOut {
|
0% {
|
opacity: 0;
|
}
|
|
100% {
|
opacity: 1;
|
}
|
}
|
</style>
|