512 lines
15 KiB
Vue
512 lines
15 KiB
Vue
<template>
|
||
<el-dialog
|
||
v-model="dialogFormVisible"
|
||
:show-close="false"
|
||
width="1200"
|
||
class="location-selection-dialog"
|
||
top="4vh"
|
||
>
|
||
<template #header="{ close, titleId }">
|
||
<div class="my-header">
|
||
<div class="left-header">
|
||
<div :id="titleId" class="ml-4">位置选择</div>
|
||
<div class="ml-4">
|
||
<el-cascader
|
||
v-model="cascaderValue"
|
||
:options="cascaderList"
|
||
@change="handleChange"
|
||
class="!w-140px"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<el-icon @click="close" class="mr-4"><Close /></el-icon>
|
||
</div>
|
||
</template>
|
||
<div class="location-selection-dialog-map" @wheel="handleWheel">
|
||
<div
|
||
class="location-selection-map"
|
||
:style="{
|
||
transform: `scale(${imageChangeMultiple})`,
|
||
transformOrigin: '0 0',
|
||
width: imgBgObj.showWidth + 'px',
|
||
height: imgBgObj.showHeight + 'px',
|
||
backgroundImage: `url(${imgBgObj.imgUrl})`
|
||
}"
|
||
>
|
||
<div
|
||
v-for="(item, index) in allMapPointInfo"
|
||
:key="index"
|
||
class="location-selection-map-item"
|
||
>
|
||
<template v-if="locationTypeNumber == 1">
|
||
<el-popover
|
||
placement="top"
|
||
trigger="click"
|
||
:popper-style="{ padding: '0px' }"
|
||
@before-enter="getByMapItemId(item, index)"
|
||
width="300"
|
||
>
|
||
<template #reference>
|
||
<img
|
||
src="@/assets/imgs/indexPage/bin-location.png"
|
||
alt=""
|
||
:style="{
|
||
position: 'absolute',
|
||
left: Number(item.locationX) - Number(item.locationWidePx) / 2 + 'px',
|
||
top: Number(item.locationY) - Number(item.locationDeepPx) / 2 + 'px',
|
||
width: item.locationWidePx + 'px',
|
||
height: item.locationDeepPx + 'px',
|
||
zIndex: 999
|
||
}"
|
||
/>
|
||
</template>
|
||
<div class="current-item-list">
|
||
<!-- 楼层展开 -->
|
||
<div
|
||
v-for="(floor, floorIndex) in currentMapItemList"
|
||
:key="floorIndex"
|
||
class="current-item"
|
||
:class="currentItem && currentItem.id == floor.id ? 'tool-active' : ''"
|
||
@click="chooseLocationPoint(floor)"
|
||
:style="{
|
||
background:
|
||
floor.locationEnable === 0 ||
|
||
floor.locationLock === 0 ||
|
||
(floor.locationUseStatus === 1 && locationTypeStr === 'release') ||
|
||
(floor.locationUseStatus === 0 && locationTypeStr === 'take')
|
||
? '#FFE2E2'
|
||
: '#F6FFEF'
|
||
}"
|
||
>
|
||
<div>层数: 第{{ floor.locationStorey }}层</div>
|
||
<div class="mt-4px">库位号: {{ floor.locationNo }}</div>
|
||
<div class="mt-4px"
|
||
>库位状态: {{ floor.locationEnable === 1 ? '启用' : '禁用' }} /
|
||
{{ floor.locationLock === 1 ? '正常' : '锁定' }} /
|
||
{{ floor.locationUseStatus === 1 ? '占用' : '空闲' }}</div
|
||
>
|
||
</div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
<template v-else>
|
||
<el-popover placement="top" trigger="click" :popper-style="{ padding: '0px' }">
|
||
<template #reference>
|
||
<img
|
||
@click="choosePoint(item)"
|
||
src="@/assets/imgs/indexPage/bin-location.png"
|
||
alt=""
|
||
:style="{
|
||
position: 'absolute',
|
||
left: Number(item.locationX) - Number(item.locationWidePx) / 2 + 'px',
|
||
top: Number(item.locationY) - Number(item.locationDeepPx) / 2 + 'px',
|
||
width: item.locationWidePx + 'px',
|
||
height: item.locationDeepPx + 'px',
|
||
zIndex: 999,
|
||
border:
|
||
(currentItem &&
|
||
locationTypeNumber == 2 &&
|
||
currentItem.laneId == item.laneId) ||
|
||
(locationTypeNumber == 3 && currentItem?.areaId == item?.areaId)
|
||
? '1px dashed #000'
|
||
: 'none'
|
||
}"
|
||
/>
|
||
</template>
|
||
<div class="drop-down-menu">
|
||
<div class="drop-down-menu" v-if="locationTypeNumber == 2">
|
||
<div class="drop-down-menu-item"> {{ item.laneName }} </div>
|
||
</div>
|
||
<div class="drop-down-menu" v-if="locationTypeNumber == 3">
|
||
<div class="drop-down-menu-item">{{ item.areaName }}</div>
|
||
</div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="location-selection-dialog-footer">
|
||
<el-button style="width: 90px" @click="dialogFormVisible = false">取消</el-button>
|
||
<el-button style="width: 90px" type="primary" @click="submitAddForm"> 确定 </el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup>
|
||
import JSONBigInt from 'json-bigint'
|
||
import { reactive, ref } from 'vue'
|
||
import * as MapApi from '@/api/map/map'
|
||
|
||
const dialogFormVisible = ref(false) //列表的
|
||
const message = useMessage() // 消息弹窗
|
||
|
||
const props = defineProps({
|
||
positionMapId: {
|
||
type: String,
|
||
default: () => ''
|
||
}
|
||
})
|
||
|
||
const locationTypeNumber = ref(1)
|
||
const locationTypeStr = ref() //release放货 take取货
|
||
const open = (locationTypeNum, locationTyp) => {
|
||
dialogFormVisible.value = true
|
||
currentItem.value = null
|
||
locationTypeNumber.value = locationTypeNum
|
||
locationTypeStr.value = locationTyp
|
||
imgBgObj.positionMapId = props.positionMapId
|
||
getMapList()
|
||
}
|
||
|
||
const allMapPointInfo = ref([]) //点位信息
|
||
const imgBgObj = reactive({
|
||
imgUrl: '',
|
||
positionMapId: '',
|
||
width: '',
|
||
height: '',
|
||
floor: '',
|
||
area: '',
|
||
resolution: 0,
|
||
origin: null,
|
||
showWidth: 1182,
|
||
showHeight: 0
|
||
})
|
||
const cascaderList = ref()
|
||
const cascaderValue = ref([])
|
||
const getMapList = async () => {
|
||
// 先获取所有的地图
|
||
let res = await MapApi.getPositionMapGetMap()
|
||
cascaderList.value = Object.keys(res).map((floor) => {
|
||
return {
|
||
value: parseInt(floor),
|
||
label: `${floor}楼`,
|
||
children: res[floor].map((item) => {
|
||
return {
|
||
value: item.id, //mapId
|
||
label: item.area
|
||
}
|
||
})
|
||
}
|
||
})
|
||
|
||
//如果传了地图id
|
||
if (props.positionMapId) {
|
||
let item = findDataById(res, props.positionMapId)
|
||
if (item) {
|
||
cascaderValue.value = [item.floor, item.id]
|
||
}
|
||
} else {
|
||
cascaderValue.value = [cascaderList.value[0].value, cascaderList.value[0].children[0].value]
|
||
}
|
||
|
||
getPositionMap()
|
||
}
|
||
|
||
const getPositionMap = async () => {
|
||
let res = await MapApi.getPositionMap(cascaderValue.value[1])
|
||
let yamlJson = JSON.parse(res.yamlJson)
|
||
imgBgObj.positionMapId = res.id
|
||
imgBgObj.floor = res.floor
|
||
imgBgObj.area = res.area
|
||
imgBgObj.width = yamlJson.width
|
||
imgBgObj.height = yamlJson.height
|
||
imgBgObj.origin = yamlJson.origin
|
||
imgBgObj.resolution = yamlJson.resolution
|
||
|
||
if (imgBgObj.width > 1477) {
|
||
imgBgObj.showWidth = imgBgObj.width * 0.8
|
||
} else {
|
||
imgBgObj.showWidth = 1182
|
||
}
|
||
|
||
imgBgObj.showHeight = (imgBgObj.showWidth * imgBgObj.height) / imgBgObj.width
|
||
await getMapData()
|
||
await getAllNodeList()
|
||
}
|
||
|
||
const findDataById = (obj, id) => {
|
||
// 遍历 obj 的每一层
|
||
for (const floor in obj) {
|
||
// 遍历当前楼层的每个区域
|
||
for (const item of obj[floor]) {
|
||
// 如果找到匹配的 id,返回该数据
|
||
if (String(item.id) === String(id)) {
|
||
return item
|
||
}
|
||
}
|
||
}
|
||
// 如果没有找到,返回 null
|
||
return null
|
||
}
|
||
|
||
const handleChange = (list) => {
|
||
getPositionMap()
|
||
}
|
||
|
||
//调转换成png的接口
|
||
const getMapData = async () => {
|
||
let data = await MapApi.getPositionMapdDwnloadPngBase64({
|
||
floor: imgBgObj.floor,
|
||
area: imgBgObj.area
|
||
})
|
||
imgBgObj.imgUrl = data
|
||
}
|
||
|
||
const currentItem = ref(null)
|
||
const getAllNodeList = async () => {
|
||
let list = await MapApi.getPositionMapItemList({
|
||
positionMapId: imgBgObj.positionMapId
|
||
})
|
||
allMapPointInfo.value = []
|
||
|
||
list.forEach((item) => {
|
||
//只要库位
|
||
if (locationTypeNumber.value == 1 && item.type === 2) {
|
||
item.locationX = Number(item.locationX) * (imgBgObj.showWidth / imgBgObj.width)
|
||
item.locationY = Number(item.locationY) * (imgBgObj.showWidth / imgBgObj.width)
|
||
item.dataList = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
|
||
item.locationDeep = item.dataList[0].locationDeep
|
||
item.locationWide = item.dataList[0].locationWide
|
||
|
||
//要将实际的cm改成px
|
||
if (item.locationWide && item.locationDeep) {
|
||
let pxObj = cmConversionPx(item.locationWide, item.locationDeep)
|
||
item.locationWidePx = pxObj.pWidth
|
||
item.locationDeepPx = pxObj.pHeight
|
||
}
|
||
allMapPointInfo.value.push(item)
|
||
}
|
||
|
||
//线库 laneId
|
||
if (item.type === 2 && locationTypeNumber.value == 2 && item.laneId !== null) {
|
||
item.locationX = Number(item.locationX) * (imgBgObj.showWidth / imgBgObj.width)
|
||
item.locationY = Number(item.locationY) * (imgBgObj.showWidth / imgBgObj.width)
|
||
item.dataList = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
|
||
item.locationDeep = item.dataList[0].locationDeep
|
||
item.locationWide = item.dataList[0].locationWide
|
||
item.laneName = item.dataList[0].laneName
|
||
|
||
//要将实际的cm改成px
|
||
if (item.locationWide && item.locationDeep) {
|
||
let pxObj = cmConversionPx(item.locationWide, item.locationDeep)
|
||
item.locationWidePx = pxObj.pWidth
|
||
item.locationDeepPx = pxObj.pHeight
|
||
}
|
||
allMapPointInfo.value.push(item)
|
||
}
|
||
|
||
//区域 areaId
|
||
if (item.type === 2 && locationTypeNumber.value == 3 && item?.areaId !== null) {
|
||
item.locationX = Number(item.locationX) * (imgBgObj.showWidth / imgBgObj.width)
|
||
item.locationY = Number(item.locationY) * (imgBgObj.showWidth / imgBgObj.width)
|
||
item.dataList = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
|
||
item.locationDeep = item.dataList[0].locationDeep
|
||
item.locationWide = item.dataList[0].locationWide
|
||
item.areaName = item.dataList[0].areaName
|
||
|
||
//要将实际的cm改成px
|
||
if (item.locationWide && item.locationDeep) {
|
||
let pxObj = cmConversionPx(item.locationWide, item.locationDeep)
|
||
item.locationWidePx = pxObj.pWidth
|
||
item.locationDeepPx = pxObj.pHeight
|
||
}
|
||
allMapPointInfo.value.push(item)
|
||
}
|
||
|
||
if (item.dataList && item.dataList.length > 0) {
|
||
item.dataList = item.dataList.reverse()
|
||
}
|
||
})
|
||
}
|
||
//库位
|
||
const currentMapItemList = ref([])
|
||
const getByMapItemId = async (item, index) => {
|
||
let list = await MapApi.getByMapItemId(imgBgObj.positionMapId, item.id)
|
||
currentMapItemList.value = list.reverse()
|
||
}
|
||
|
||
//将节点实际宽高cm转换成px
|
||
const cmConversionPx = (cWidth, cHeight) => {
|
||
let pWidth = (Number(cWidth) / imgBgObj.resolution / 100) * (imgBgObj.showWidth / imgBgObj.width)
|
||
let pHeight =
|
||
(Number(cHeight) / imgBgObj.resolution / 100) * (imgBgObj.showWidth / imgBgObj.width)
|
||
|
||
return {
|
||
pWidth,
|
||
pHeight
|
||
}
|
||
}
|
||
|
||
const choosePoint = (item) => {
|
||
currentItem.value = item
|
||
}
|
||
|
||
const chooseLocationPoint = (item) => {
|
||
if (item.locationEnable === 0) {
|
||
message.warning('该库位已被禁用')
|
||
return
|
||
}
|
||
|
||
if (item.locationLock === 0) {
|
||
message.warning('该库位已被锁定')
|
||
return
|
||
}
|
||
|
||
// locationUseStatus 1占用 0空闲
|
||
if (item.locationUseStatus === 1 && locationTypeStr.value === 'release') {
|
||
message.warning('该库位已被占用')
|
||
return
|
||
}
|
||
|
||
if (item.locationUseStatus === 0 && locationTypeStr.value === 'take') {
|
||
message.warning('该库位没有货物')
|
||
return
|
||
}
|
||
currentItem.value = item
|
||
}
|
||
|
||
const submitAddForm = () => {
|
||
if (currentItem.value) {
|
||
emit('locationSelectionDialogSuccess', currentItem.value)
|
||
dialogFormVisible.value = false
|
||
} else {
|
||
message.warning('请选择一个位置')
|
||
}
|
||
}
|
||
|
||
const imageChangeMultiple = ref(1)
|
||
//鼠标滚轮
|
||
const handleWheel = (event) => {
|
||
// 判断 Ctrl 键是否被按下
|
||
if (event.ctrlKey) {
|
||
// 阻止默认的滚动行为
|
||
event.preventDefault()
|
||
|
||
// 根据滚轮滚动方向调整缩放比例
|
||
if (event.deltaY < 0) {
|
||
// 向上滚动,放大
|
||
//放大
|
||
if (imageChangeMultiple.value < 4) {
|
||
imageChangeMultiple.value += 0.2
|
||
} else {
|
||
imageChangeMultiple.value = 3.8
|
||
message.warning('不能在放大了')
|
||
}
|
||
} else {
|
||
//缩小
|
||
if (imageChangeMultiple.value > 0.2) {
|
||
imageChangeMultiple.value -= 0.1
|
||
} else {
|
||
imageChangeMultiple.value = 0.1
|
||
message.warning('不能在缩小了')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const emit = defineEmits(['locationSelectionDialogSuccess'])
|
||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.location-selection-dialog {
|
||
padding: 0px;
|
||
position: relative;
|
||
|
||
.el-dialog__header {
|
||
padding-bottom: 0 !important;
|
||
}
|
||
|
||
.my-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 58px;
|
||
padding: 0;
|
||
margin-right: 0 !important;
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
|
||
.left-header {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.location-selection-dialog-map {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 80vh;
|
||
overflow: auto;
|
||
|
||
.location-selection-map {
|
||
position: relative;
|
||
width: 100%;
|
||
background-repeat: no-repeat;
|
||
background-size: contain;
|
||
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
|
||
.location-selection-map-item {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.location-selection-dialog-footer {
|
||
position: absolute;
|
||
bottom: 30px;
|
||
right: 60px;
|
||
}
|
||
}
|
||
.drop-down-menu {
|
||
.drop-down-menu-item {
|
||
cursor: pointer;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 11px 10px;
|
||
font-family:
|
||
PingFangSC,
|
||
PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
color: #0d162a;
|
||
line-height: 20px;
|
||
text-align: left;
|
||
font-style: normal;
|
||
border-bottom: 1px solid #e9e9e9;
|
||
}
|
||
|
||
:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
|
||
.current-item-list {
|
||
.current-item {
|
||
width: 300px;
|
||
font-family:
|
||
PingFangSC,
|
||
PingFang SC;
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
cursor: pointer;
|
||
border: 1px solid rgba(0, 0, 0, 0);
|
||
position: relative;
|
||
border-bottom: 1px solid #e9e9e9;
|
||
padding: 12px 15px;
|
||
}
|
||
}
|
||
|
||
.tool-active {
|
||
background: #ebf1ff !important;
|
||
}
|
||
</style>
|