|
|
@@ -15,7 +15,7 @@
|
|
|
@click="handleCellClick(cell)"
|
|
|
@contextmenu.prevent="handleCellContextMenu(cell)"
|
|
|
>
|
|
|
- <div v-if="cell && isCellVisible(cell)" class="cell-content">
|
|
|
+ <div v-if="isLocationCell(cell) && isCellVisible(cell)" class="cell-content">
|
|
|
<div class="category-badge" :style="getCategoryStyle(cell)">
|
|
|
{{ getHeatLabel(cell) }}
|
|
|
</div>
|
|
|
@@ -39,6 +39,12 @@
|
|
|
{{ cell.containerCode }}
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div
|
|
|
+ v-else-if="isSpecialCell(cell) && cell.label"
|
|
|
+ :class="['special-cell-label', `special-cell-label-${cell.type}`]"
|
|
|
+ >
|
|
|
+ {{ cell.label }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div
|
|
|
@@ -57,7 +63,11 @@
|
|
|
<script setup lang="ts">
|
|
|
import { computed, onBeforeUnmount, onMounted, ref, watch, type CSSProperties } from 'vue'
|
|
|
import type { LocationResourceDataVO } from '../types'
|
|
|
-import { applyWarehouseLayoutEnhancement } from './warehouse-layout-enhancers'
|
|
|
+import {
|
|
|
+ applyWarehouseLayoutEnhancement,
|
|
|
+ getWarehouseLayoutSpecialCells,
|
|
|
+ type WarehouseLayoutSpecialCell
|
|
|
+} from './warehouse-layout-enhancers'
|
|
|
|
|
|
interface Props {
|
|
|
locations: LocationResourceDataVO[]
|
|
|
@@ -87,6 +97,8 @@ interface GridCell extends LocationResourceDataVO {
|
|
|
categoryMismatch: boolean
|
|
|
}
|
|
|
|
|
|
+type MapCell = GridCell | WarehouseLayoutSpecialCell | null
|
|
|
+
|
|
|
const props = defineProps<Props>()
|
|
|
const emit = defineEmits<{
|
|
|
(event: 'select-loc-group', locGroup1: string): void
|
|
|
@@ -203,6 +215,15 @@ const parsedLocations = computed<GridCell[]>(() => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+const specialCellMap = computed(() => {
|
|
|
+ return new Map(
|
|
|
+ getWarehouseLayoutSpecialCells(props.currentLevel).map((cell) => [
|
|
|
+ `${cell.gridRow}-${cell.gridCol}`,
|
|
|
+ cell
|
|
|
+ ])
|
|
|
+ )
|
|
|
+})
|
|
|
+
|
|
|
const locationMap = computed(() => {
|
|
|
const map = new Map<string, GridCell>()
|
|
|
parsedLocations.value.forEach((loc) => {
|
|
|
@@ -235,12 +256,24 @@ const gridMetrics = computed(() => {
|
|
|
const gridData = computed(() => {
|
|
|
if (!gridBounds.value) return []
|
|
|
|
|
|
- // 构建网格:按行(X)和列(Y)排列
|
|
|
- const grid: (GridCell | null)[] = []
|
|
|
+ // 构建网格:按行(X)和列(Y)排列,空位根据规则显示为过道或特殊区域。
|
|
|
+ const grid: MapCell[] = []
|
|
|
for (let x = gridBounds.value.minX; x <= gridBounds.value.maxX; x++) {
|
|
|
for (let y = gridBounds.value.minY; y <= gridBounds.value.maxY; y++) {
|
|
|
const key = `${x}-${y}`
|
|
|
- grid.push(locationMap.value.get(key) || null)
|
|
|
+ const locationCell = locationMap.value.get(key)
|
|
|
+ if (locationCell) {
|
|
|
+ grid.push(locationCell)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ const specialCell = specialCellMap.value.get(key)
|
|
|
+ if (specialCell) {
|
|
|
+ grid.push(specialCell)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ grid.push(null)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -348,7 +381,16 @@ const isCellVisible = (cell: GridCell) => {
|
|
|
return isCellMatched(cell) && isCellInSelectedRange(cell)
|
|
|
}
|
|
|
|
|
|
-const getCellClass = (cell: GridCell | null) => {
|
|
|
+const isSpecialCell = (cell: MapCell): cell is WarehouseLayoutSpecialCell => {
|
|
|
+ return Boolean(cell && 'type' in cell)
|
|
|
+}
|
|
|
+
|
|
|
+const isLocationCell = (cell: MapCell): cell is GridCell => {
|
|
|
+ return Boolean(cell && !('type' in cell))
|
|
|
+}
|
|
|
+
|
|
|
+const getCellClass = (cell: MapCell) => {
|
|
|
+ if (isSpecialCell(cell)) return [cell.type]
|
|
|
if (!cell) return ['aisle']
|
|
|
if (!isCellVisible(cell)) return ['aisle']
|
|
|
|
|
|
@@ -455,8 +497,10 @@ const hasSameBorderNeighbor = (cell: GridCell) => {
|
|
|
|| isSameBorderNeighbor(cell, 0, 1)
|
|
|
}
|
|
|
|
|
|
-const getCellStyle = (cell: GridCell | null): CSSProperties => {
|
|
|
- if (!cell || !isCellVisible(cell) || !hasActiveBorder() || !hasSameBorderNeighbor(cell)) return {}
|
|
|
+const getCellStyle = (cell: MapCell): CSSProperties => {
|
|
|
+ if (!isLocationCell(cell) || !isCellVisible(cell) || !hasActiveBorder() || !hasSameBorderNeighbor(cell)) {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
|
|
|
const borderWidth = 'var(--group-outline-width, 2px)'
|
|
|
|
|
|
@@ -473,12 +517,12 @@ const clearSelectedRange = () => {
|
|
|
selectedRange.value = null
|
|
|
}
|
|
|
|
|
|
-const handleCellClick = (cell: GridCell | null) => {
|
|
|
+const handleCellClick = (cell: MapCell) => {
|
|
|
if (suppressNextClick.value) {
|
|
|
suppressNextClick.value = false
|
|
|
return
|
|
|
}
|
|
|
- if (!cell || !isCellVisible(cell)) {
|
|
|
+ if (!isLocationCell(cell) || !isCellVisible(cell)) {
|
|
|
if (selectedRange.value) {
|
|
|
clearSelectedRange()
|
|
|
}
|
|
|
@@ -487,8 +531,8 @@ const handleCellClick = (cell: GridCell | null) => {
|
|
|
emit('select-loc-group', cell.locGroup1)
|
|
|
}
|
|
|
|
|
|
-const handleCellContextMenu = (cell: GridCell | null) => {
|
|
|
- if (!cell || !isCellVisible(cell)) return
|
|
|
+const handleCellContextMenu = (cell: MapCell) => {
|
|
|
+ if (!isLocationCell(cell) || !isCellVisible(cell)) return
|
|
|
emit('select-location-id', cell.locationId)
|
|
|
}
|
|
|
|
|
|
@@ -545,7 +589,7 @@ const updateTooltipPosition = (event: MouseEvent) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const handleCellMouseEnter = (cell: GridCell | null, index: number, event: MouseEvent) => {
|
|
|
+const handleCellMouseEnter = (cell: MapCell, index: number, event: MouseEvent) => {
|
|
|
if (selectionStart.value && (event.buttons & 1) === 1) {
|
|
|
const point = getGridPointByIndex(index)
|
|
|
if (point) {
|
|
|
@@ -558,7 +602,11 @@ const handleCellMouseEnter = (cell: GridCell | null, index: number, event: Mouse
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if (!props.showTooltip) return
|
|
|
+ if (!props.showTooltip || !isLocationCell(cell)) {
|
|
|
+ tooltipVisible.value = false
|
|
|
+ hoveredCell.value = null
|
|
|
+ return
|
|
|
+ }
|
|
|
hoveredCell.value = cell
|
|
|
tooltipVisible.value = true
|
|
|
updateTooltipPosition(event)
|
|
|
@@ -585,7 +633,7 @@ const handleWindowMouseUp = () => {
|
|
|
if (didSelectionMove.value) {
|
|
|
selectedRange.value = buildSelectionRange(selectionStart.value, selectionEnd.value)
|
|
|
const selectedLocationIds = gridData.value
|
|
|
- .filter((cell): cell is GridCell => Boolean(cell))
|
|
|
+ .filter(isLocationCell)
|
|
|
.filter((cell) => isCellMatched(cell) && isCellInSelectedRange(cell))
|
|
|
.map((cell) => cell.locationId)
|
|
|
|
|
|
@@ -698,6 +746,16 @@ onBeforeUnmount(() => {
|
|
|
background: #18202a;
|
|
|
}
|
|
|
|
|
|
+.grid-cell.wall {
|
|
|
+ background: #323841;
|
|
|
+ cursor: default;
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell.elevator {
|
|
|
+ background: #20303b;
|
|
|
+ cursor: default;
|
|
|
+}
|
|
|
+
|
|
|
.grid-cell.category-a {
|
|
|
background: rgba(0, 128, 0, 0.16);
|
|
|
}
|
|
|
@@ -710,7 +768,7 @@ onBeforeUnmount(() => {
|
|
|
background: rgba(255, 255, 0, 0.16);
|
|
|
}
|
|
|
|
|
|
-.grid-cell:not(.aisle):hover {
|
|
|
+.grid-cell:not(.aisle):not(.wall):not(.elevator):hover {
|
|
|
transform: scale(1.03);
|
|
|
box-shadow: 0 0 12px rgba(223, 231, 239, 0.12);
|
|
|
z-index: 10;
|
|
|
@@ -794,6 +852,18 @@ onBeforeUnmount(() => {
|
|
|
text-overflow: ellipsis;
|
|
|
}
|
|
|
|
|
|
+.special-cell-label {
|
|
|
+ font-size: calc(var(--cell-id-font-size, 11px) - 1px);
|
|
|
+ font-weight: 700;
|
|
|
+ color: rgba(237, 243, 247, 0.82);
|
|
|
+ letter-spacing: 1px;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.special-cell-label-elevator {
|
|
|
+ color: rgba(221, 234, 244, 0.92);
|
|
|
+}
|
|
|
+
|
|
|
.cell-tooltip {
|
|
|
position: fixed;
|
|
|
z-index: 1000;
|