|
|
@@ -177,38 +177,63 @@
|
|
|
<option value="N">无</option>
|
|
|
</select>
|
|
|
</label>
|
|
|
- <label class="filter-item">
|
|
|
- <select
|
|
|
- ref="zoneSelectRef"
|
|
|
- v-model="selectedZoneId"
|
|
|
- :class="['level-select', { 'level-select-placeholder': !selectedZoneId }]"
|
|
|
+ <div
|
|
|
+ ref="zoneDropdownRef"
|
|
|
+ class="multi-select"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ class="level-select multi-select-trigger"
|
|
|
+ :class="{ 'level-select-placeholder': selectedZoneIds.length === 0 }"
|
|
|
:style="{ width: selectWidths.zone ? `${selectWidths.zone}px` : 'auto' }"
|
|
|
+ type="button"
|
|
|
+ @click.stop="toggleZoneDropdown"
|
|
|
>
|
|
|
- <option value="">库区</option>
|
|
|
- <option
|
|
|
+ {{ zoneLabel }}
|
|
|
+ </button>
|
|
|
+ <div
|
|
|
+ v-if="zoneDropdownVisible"
|
|
|
+ class="multi-select-menu"
|
|
|
+ >
|
|
|
+ <button
|
|
|
v-for="zoneId in zoneOptions"
|
|
|
:key="zoneId"
|
|
|
- :value="zoneId"
|
|
|
+ class="multi-select-option"
|
|
|
+ type="button"
|
|
|
+ @click.stop="toggleZone(zoneId)"
|
|
|
>
|
|
|
- {{ zoneId }}
|
|
|
- </option>
|
|
|
- </select>
|
|
|
- </label>
|
|
|
- <select
|
|
|
- ref="levelSelectRef"
|
|
|
- v-model.number="currentLevel"
|
|
|
- class="level-select level-select-floor"
|
|
|
- :style="{ width: selectWidths.level ? `${selectWidths.level}px` : 'auto' }"
|
|
|
- @change="handleLevelChange"
|
|
|
+ <span class="multi-select-check">{{ selectedZoneIds.includes(zoneId) ? '✓' : '' }}</span>
|
|
|
+ <span>{{ zoneId }}</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ ref="levelDropdownRef"
|
|
|
+ class="multi-select"
|
|
|
>
|
|
|
- <option
|
|
|
- v-for="level in levelRange"
|
|
|
- :key="level"
|
|
|
- :value="level"
|
|
|
+ <button
|
|
|
+ class="level-select multi-select-trigger"
|
|
|
+ :style="{ width: selectWidths.level ? `${selectWidths.level}px` : 'auto' }"
|
|
|
+ type="button"
|
|
|
+ @click.stop="toggleLevelDropdown"
|
|
|
+ >
|
|
|
+ {{ levelLabel }}
|
|
|
+ </button>
|
|
|
+ <div
|
|
|
+ v-if="levelDropdownVisible"
|
|
|
+ class="multi-select-menu"
|
|
|
>
|
|
|
- {{ level }}层
|
|
|
- </option>
|
|
|
- </select>
|
|
|
+ <button
|
|
|
+ v-for="level in levelRange"
|
|
|
+ :key="level"
|
|
|
+ class="multi-select-option"
|
|
|
+ type="button"
|
|
|
+ @click.stop="toggleLevel(level)"
|
|
|
+ >
|
|
|
+ <span class="multi-select-check">{{ selectedLevels.includes(level) ? '✓' : '' }}</span>
|
|
|
+ <span>{{ level }}层</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<button
|
|
|
class="selection-btn"
|
|
|
:class="{ 'selection-btn-active': selectionMode }"
|
|
|
@@ -379,26 +404,77 @@
|
|
|
class="map-container"
|
|
|
>
|
|
|
<WorkingHighlight>
|
|
|
- <WarehouseMap
|
|
|
- :locations="locations"
|
|
|
- :current-level="currentLevel"
|
|
|
- :selected-category="selectedCategory"
|
|
|
- :selected-location-attribute="selectedLocationAttribute"
|
|
|
- :selected-has-container="selectedHasContainer"
|
|
|
- :selected-zone-id="selectedZoneId"
|
|
|
- :loc-group-keyword="appliedLocGroupKeyword"
|
|
|
- :location-id-keyword="appliedLocationIdKeyword"
|
|
|
- :wcs-location-id-keyword="appliedWcsLocationIdKeyword"
|
|
|
- :show-group-border="showGroupBorder"
|
|
|
- :show-tooltip="showTooltip"
|
|
|
- :category-color-visibility="categoryColorVisibility"
|
|
|
- :theme-mode="isLightTheme ? 'light' : 'dark'"
|
|
|
- :selection-mode="selectionMode"
|
|
|
- @select-loc-group="handleSelectLocGroup"
|
|
|
- @select-location-id="handleSelectLocationId"
|
|
|
- @selection-complete="handleSelectionComplete"
|
|
|
- @create-location-sql="openCreateLocationSqlModal"
|
|
|
- />
|
|
|
+ <div
|
|
|
+ class="floor-map-grid"
|
|
|
+ :class="floorMapGridClass"
|
|
|
+ >
|
|
|
+ <section
|
|
|
+ v-for="level in selectedLevels"
|
|
|
+ :key="level"
|
|
|
+ :class="[
|
|
|
+ 'floor-map-panel',
|
|
|
+ {
|
|
|
+ 'floor-map-panel-hidden': expandedLevel !== null && expandedLevel !== level,
|
|
|
+ 'floor-map-panel-expanded': expandedLevel === level
|
|
|
+ }
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <div class="floor-map-title">
|
|
|
+ <span>{{ level }}层</span>
|
|
|
+ <button
|
|
|
+ v-if="selectedLevels.length > 1"
|
|
|
+ class="floor-map-expand-btn"
|
|
|
+ type="button"
|
|
|
+ :title="expandedLevel === level ? '收起' : '展开'"
|
|
|
+ @click="toggleExpandedLevel(level)"
|
|
|
+ >
|
|
|
+ <svg
|
|
|
+ v-if="expandedLevel === level"
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ aria-hidden="true"
|
|
|
+ class="floor-map-expand-icon"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M6.5 3.25v3.25H3.25V5h1.7L2.72 2.78l1.06-1.06L6 3.95v-1.7h1.5zm2.98 9.5V9.5h3.27V11h-1.72l2.25 2.22-1.06 1.06L10 12.08v1.67H8.48zM3.25 9.5H6.5v3.25H5v-1.7l-2.22 2.23-1.06-1.06L3.95 10H2.25V9.5zm9.5-3H9.5V3.25H11v1.7l2.22-2.23 1.06 1.06L12.05 6h1.7v.5z"
|
|
|
+ fill="currentColor"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ <svg
|
|
|
+ v-else
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ aria-hidden="true"
|
|
|
+ class="floor-map-expand-icon"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M2.5 6.5V2.5h4V4H5.05l2.22 2.22-1.05 1.06L4 5.05V6.5H2.5zm7-4h4v4H12V5.05L9.78 7.28 8.72 6.22 10.95 4H9.5V2.5zm-7 7H4v1.45l2.22-2.23 1.06 1.06L5.05 12H6.5v1.5h-4v-4zm9.5 0h1.5v4h-4V12h1.45L8.72 9.78l1.06-1.06L12 10.95V9.5z"
|
|
|
+ fill="currentColor"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <WarehouseMap
|
|
|
+ :locations="locationsByLevel[level] || []"
|
|
|
+ :current-level="level"
|
|
|
+ :selected-category="selectedCategory"
|
|
|
+ :selected-location-attribute="selectedLocationAttribute"
|
|
|
+ :selected-has-container="selectedHasContainer"
|
|
|
+ :selected-zone-ids="selectedZoneIds"
|
|
|
+ :loc-group-keyword="appliedLocGroupKeyword"
|
|
|
+ :location-id-keyword="appliedLocationIdKeyword"
|
|
|
+ :wcs-location-id-keyword="appliedWcsLocationIdKeyword"
|
|
|
+ :container-code-keyword="appliedContainerCodeKeyword"
|
|
|
+ :show-group-border="showGroupBorder"
|
|
|
+ :show-tooltip="showTooltip"
|
|
|
+ :category-color-visibility="categoryColorVisibility"
|
|
|
+ :theme-mode="isLightTheme ? 'light' : 'dark'"
|
|
|
+ :selection-mode="selectionMode"
|
|
|
+ @select-loc-group="handleSelectLocGroup"
|
|
|
+ @select-location-id="handleSelectLocationId"
|
|
|
+ @selection-complete="handleSelectionComplete"
|
|
|
+ @create-location-sql="openCreateLocationSqlModal"
|
|
|
+ />
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
</WorkingHighlight>
|
|
|
</div>
|
|
|
</main>
|
|
|
@@ -432,19 +508,33 @@ import {
|
|
|
removeToken
|
|
|
} from './utils/auth'
|
|
|
|
|
|
-const LEVEL_STORAGE_KEY = 'warehouse-map.current-level'
|
|
|
+const LEVEL_STORAGE_KEY = 'warehouse-map.selected-levels'
|
|
|
const REFRESH_INTERVAL_STORAGE_KEY = 'warehouse-map.refresh-interval-ms'
|
|
|
-const getInitialLevel = () => {
|
|
|
- const savedLevel = window.localStorage.getItem(LEVEL_STORAGE_KEY)
|
|
|
- const parsedLevel = savedLevel ? Number(savedLevel) : NaN
|
|
|
+const getInitialLevels = () => {
|
|
|
+ const savedLevels = window.localStorage.getItem(LEVEL_STORAGE_KEY)
|
|
|
+ const parsedLevels = savedLevels
|
|
|
+ ? savedLevels
|
|
|
+ .split(',')
|
|
|
+ .map((level) => Number(level))
|
|
|
+ .filter(
|
|
|
+ (level) =>
|
|
|
+ Number.isInteger(level) && level >= config.minLevel && level <= config.maxLevel
|
|
|
+ )
|
|
|
+ : []
|
|
|
+
|
|
|
+ if (parsedLevels.length > 0) {
|
|
|
+ return [...new Set(parsedLevels)].sort((a, b) => a - b)
|
|
|
+ }
|
|
|
+
|
|
|
+ const legacyLevel = Number(window.localStorage.getItem('warehouse-map.current-level'))
|
|
|
if (
|
|
|
- Number.isInteger(parsedLevel) &&
|
|
|
- parsedLevel >= config.minLevel &&
|
|
|
- parsedLevel <= config.maxLevel
|
|
|
+ Number.isInteger(legacyLevel) &&
|
|
|
+ legacyLevel >= config.minLevel &&
|
|
|
+ legacyLevel <= config.maxLevel
|
|
|
) {
|
|
|
- return parsedLevel
|
|
|
+ return [legacyLevel]
|
|
|
}
|
|
|
- return config.minLevel
|
|
|
+ return [config.minLevel]
|
|
|
}
|
|
|
|
|
|
const getInitialRefreshInterval = () => {
|
|
|
@@ -456,7 +546,7 @@ const getInitialRefreshInterval = () => {
|
|
|
return config.refreshInterval
|
|
|
}
|
|
|
|
|
|
-const currentLevel = ref(getInitialLevel())
|
|
|
+const selectedLevels = ref<number[]>(getInitialLevels())
|
|
|
const locations = ref<LocationResourceDataVO[]>([])
|
|
|
const loading = ref(false)
|
|
|
const refreshing = ref(false)
|
|
|
@@ -477,12 +567,12 @@ const isLightTheme = ref(getInitialTheme() === 'light')
|
|
|
const selectedCategory = ref('')
|
|
|
const selectedLocationAttribute = ref<LocationAttributeCode | ''>('')
|
|
|
const selectedHasContainer = ref<'Y' | 'N' | ''>('')
|
|
|
-const selectedZoneId = ref('')
|
|
|
+const selectedZoneIds = ref<string[]>([])
|
|
|
const categorySelectRef = ref<HTMLSelectElement | null>(null)
|
|
|
const locationAttributeSelectRef = ref<HTMLSelectElement | null>(null)
|
|
|
const hasContainerSelectRef = ref<HTMLSelectElement | null>(null)
|
|
|
-const zoneSelectRef = ref<HTMLSelectElement | null>(null)
|
|
|
-const levelSelectRef = ref<HTMLSelectElement | null>(null)
|
|
|
+const zoneDropdownRef = ref<HTMLElement | null>(null)
|
|
|
+const levelDropdownRef = ref<HTMLElement | null>(null)
|
|
|
const selectWidths = ref({
|
|
|
category: 0,
|
|
|
attribute: 0,
|
|
|
@@ -499,10 +589,14 @@ const locationKeywordInput = ref('')
|
|
|
const appliedLocGroupKeyword = ref('')
|
|
|
const appliedLocationIdKeyword = ref('')
|
|
|
const appliedWcsLocationIdKeyword = ref('')
|
|
|
+const appliedContainerCodeKeyword = ref('')
|
|
|
const showGroupBorder = ref(false)
|
|
|
const showTooltip = ref(true)
|
|
|
const selectionMode = ref(false)
|
|
|
const capacityMonitorVisible = ref(false)
|
|
|
+const zoneDropdownVisible = ref(false)
|
|
|
+const levelDropdownVisible = ref(false)
|
|
|
+const expandedLevel = ref<number | null>(null)
|
|
|
const createLocationSqlModalVisible = ref(false)
|
|
|
const createLocationSqlPayload = ref({
|
|
|
floor: 1,
|
|
|
@@ -557,6 +651,13 @@ const categoryOptions = computed(() => {
|
|
|
return [...new Set(locations.value.map((loc) => loc.category).filter(Boolean))].sort()
|
|
|
})
|
|
|
|
|
|
+const locationsByLevel = computed<Record<number, LocationResourceDataVO[]>>(() => {
|
|
|
+ return selectedLevels.value.reduce<Record<number, LocationResourceDataVO[]>>((result, level) => {
|
|
|
+ result[level] = locations.value.filter((loc) => loc.locLevel === level)
|
|
|
+ return result
|
|
|
+ }, {})
|
|
|
+})
|
|
|
+
|
|
|
const hasContainer = (containerCode: string | null) => {
|
|
|
return Boolean(containerCode && containerCode.trim())
|
|
|
}
|
|
|
@@ -585,7 +686,8 @@ const isLocationMatchedByFilters = (loc: LocationResourceDataVO) => {
|
|
|
!selectedHasContainer.value ||
|
|
|
(selectedHasContainer.value === 'Y' && hasContainerFlag) ||
|
|
|
(selectedHasContainer.value === 'N' && !hasContainerFlag)
|
|
|
- const matchedZoneId = !selectedZoneId.value || String(loc.zoneId || '') === selectedZoneId.value
|
|
|
+ const matchedZoneId =
|
|
|
+ selectedZoneIds.value.length === 0 || selectedZoneIds.value.includes(String(loc.zoneId || ''))
|
|
|
const normalizedLocGroupKeyword = appliedLocGroupKeyword.value.trim().toUpperCase()
|
|
|
const matchedLocGroup =
|
|
|
!normalizedLocGroupKeyword ||
|
|
|
@@ -604,6 +706,12 @@ const isLocationMatchedByFilters = (loc: LocationResourceDataVO) => {
|
|
|
String(loc.wcsLocationId || '')
|
|
|
.toUpperCase()
|
|
|
.includes(normalizedWcsLocationIdKeyword)
|
|
|
+ const normalizedContainerCodeKeyword = appliedContainerCodeKeyword.value.trim().toUpperCase()
|
|
|
+ const matchedContainerCode =
|
|
|
+ !normalizedContainerCodeKeyword ||
|
|
|
+ String(loc.containerCode || '')
|
|
|
+ .toUpperCase()
|
|
|
+ .includes(normalizedContainerCodeKeyword)
|
|
|
|
|
|
return (
|
|
|
matchedCategory &&
|
|
|
@@ -612,7 +720,8 @@ const isLocationMatchedByFilters = (loc: LocationResourceDataVO) => {
|
|
|
matchedZoneId &&
|
|
|
matchedLocGroup &&
|
|
|
matchedLocationId &&
|
|
|
- matchedWcsLocationId
|
|
|
+ matchedWcsLocationId &&
|
|
|
+ matchedContainerCode
|
|
|
)
|
|
|
}
|
|
|
|
|
|
@@ -712,8 +821,17 @@ const hasContainerLabel = computed(() => {
|
|
|
if (selectedHasContainer.value === 'N') return '无'
|
|
|
return '容器'
|
|
|
})
|
|
|
-const zoneLabel = computed(() => selectedZoneId.value || '库区')
|
|
|
-const levelLabel = computed(() => `${currentLevel.value}层`)
|
|
|
+const zoneLabel = computed(() =>
|
|
|
+ selectedZoneIds.value.length > 1 ? `${selectedZoneIds.value.length}个库区` : selectedZoneIds.value[0] || '库区'
|
|
|
+)
|
|
|
+const levelLabel = computed(() =>
|
|
|
+ selectedLevels.value.length === levelRange.value.length
|
|
|
+ ? '全部楼层'
|
|
|
+ : selectedLevels.value.map((level) => `${level}层`).join(',')
|
|
|
+)
|
|
|
+const floorMapGridClass = computed(() =>
|
|
|
+ expandedLevel.value === null ? `floor-map-grid-${selectedLevels.value.length}` : 'floor-map-grid-expanded'
|
|
|
+)
|
|
|
|
|
|
const toggleCategoryColorVisibility = (category: 'A' | 'B' | 'C') => {
|
|
|
categoryColorVisibility.value = {
|
|
|
@@ -752,8 +870,8 @@ const updateSelectWidths = () => {
|
|
|
updateSelectWidth('category', categorySelectRef.value, categoryLabel.value)
|
|
|
updateSelectWidth('attribute', locationAttributeSelectRef.value, locationAttributeLabel.value)
|
|
|
updateSelectWidth('container', hasContainerSelectRef.value, hasContainerLabel.value)
|
|
|
- updateSelectWidth('zone', zoneSelectRef.value, zoneLabel.value)
|
|
|
- updateSelectWidth('level', levelSelectRef.value, levelLabel.value)
|
|
|
+ selectWidths.value.zone = Math.max(Math.ceil(measureTextWidth(zoneLabel.value, '12px sans-serif') + 40), 58)
|
|
|
+ selectWidths.value.level = Math.max(Math.ceil(measureTextWidth(levelLabel.value, '12px sans-serif') + 40), 58)
|
|
|
}
|
|
|
|
|
|
const updateFillRateTooltipPosition = (event: MouseEvent) => {
|
|
|
@@ -840,22 +958,31 @@ const applyLocationKeywordFilter = () => {
|
|
|
const keyword = locationKeywordInput.value.trim()
|
|
|
const hyphenCount = countHyphen(keyword)
|
|
|
|
|
|
- if (hyphenCount === 3) {
|
|
|
+ if (/^(TP|HJ)/i.test(keyword)) {
|
|
|
+ appliedContainerCodeKeyword.value = keyword
|
|
|
+ appliedLocGroupKeyword.value = ''
|
|
|
+ appliedLocationIdKeyword.value = ''
|
|
|
+ appliedWcsLocationIdKeyword.value = ''
|
|
|
+ } else if (hyphenCount === 3) {
|
|
|
appliedLocationIdKeyword.value = keyword
|
|
|
appliedLocGroupKeyword.value = ''
|
|
|
appliedWcsLocationIdKeyword.value = ''
|
|
|
+ appliedContainerCodeKeyword.value = ''
|
|
|
} else if (hyphenCount === 2) {
|
|
|
appliedLocGroupKeyword.value = keyword
|
|
|
appliedLocationIdKeyword.value = ''
|
|
|
appliedWcsLocationIdKeyword.value = ''
|
|
|
+ appliedContainerCodeKeyword.value = ''
|
|
|
} else if (keyword.length >= 13 && hyphenCount === 0) {
|
|
|
appliedWcsLocationIdKeyword.value = keyword
|
|
|
appliedLocationIdKeyword.value = ''
|
|
|
appliedLocGroupKeyword.value = ''
|
|
|
+ appliedContainerCodeKeyword.value = ''
|
|
|
} else {
|
|
|
appliedLocGroupKeyword.value = ''
|
|
|
appliedLocationIdKeyword.value = ''
|
|
|
appliedWcsLocationIdKeyword.value = ''
|
|
|
+ appliedContainerCodeKeyword.value = ''
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -864,6 +991,7 @@ const clearLocationKeywordFilter = () => {
|
|
|
appliedLocGroupKeyword.value = ''
|
|
|
appliedLocationIdKeyword.value = ''
|
|
|
appliedWcsLocationIdKeyword.value = ''
|
|
|
+ appliedContainerCodeKeyword.value = ''
|
|
|
}
|
|
|
|
|
|
const handleGroupBorderToggle = (event: Event) => {
|
|
|
@@ -878,6 +1006,28 @@ const toggleCapacityMonitorVisible = () => {
|
|
|
capacityMonitorVisible.value = !capacityMonitorVisible.value
|
|
|
}
|
|
|
|
|
|
+const toggleExpandedLevel = (level: number) => {
|
|
|
+ expandedLevel.value = expandedLevel.value === level ? null : level
|
|
|
+}
|
|
|
+
|
|
|
+const toggleZoneDropdown = () => {
|
|
|
+ zoneDropdownVisible.value = !zoneDropdownVisible.value
|
|
|
+ levelDropdownVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const toggleLevelDropdown = () => {
|
|
|
+ levelDropdownVisible.value = !levelDropdownVisible.value
|
|
|
+ zoneDropdownVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const toggleZone = (zoneId: string) => {
|
|
|
+ if (selectedZoneIds.value.includes(zoneId)) {
|
|
|
+ selectedZoneIds.value = selectedZoneIds.value.filter((selectedZoneId) => selectedZoneId !== zoneId)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ selectedZoneIds.value = [...selectedZoneIds.value, zoneId].sort()
|
|
|
+}
|
|
|
+
|
|
|
const handleSelectionComplete = () => {
|
|
|
selectionMode.value = false
|
|
|
}
|
|
|
@@ -903,6 +1053,10 @@ const refreshCountdownText = computed(() => {
|
|
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
|
|
})
|
|
|
|
|
|
+const saveSelectedLevels = () => {
|
|
|
+ window.localStorage.setItem(LEVEL_STORAGE_KEY, selectedLevels.value.join(','))
|
|
|
+}
|
|
|
+
|
|
|
const loadLocationData = async (options: { silent?: boolean } = {}) => {
|
|
|
const { silent = false } = options
|
|
|
if (!isAuthenticated()) {
|
|
|
@@ -920,11 +1074,15 @@ const loadLocationData = async (options: { silent?: boolean } = {}) => {
|
|
|
|
|
|
let loaded = false
|
|
|
try {
|
|
|
- const data = await fetchLocationData({
|
|
|
- warehouse: config.warehouse,
|
|
|
- locLevel: currentLevel.value
|
|
|
- })
|
|
|
- locations.value = data
|
|
|
+ const levelDataList = await Promise.all(
|
|
|
+ selectedLevels.value.map((level) =>
|
|
|
+ fetchLocationData({
|
|
|
+ warehouse: config.warehouse,
|
|
|
+ locLevel: level
|
|
|
+ })
|
|
|
+ )
|
|
|
+ )
|
|
|
+ locations.value = levelDataList.flat()
|
|
|
hasLoadedOnce.value = true
|
|
|
loaded = true
|
|
|
} catch (err: unknown) {
|
|
|
@@ -960,8 +1118,16 @@ const scheduleNextRefresh = () => {
|
|
|
}, refreshIntervalMs.value)
|
|
|
}
|
|
|
|
|
|
-const handleLevelChange = () => {
|
|
|
- window.localStorage.setItem(LEVEL_STORAGE_KEY, String(currentLevel.value))
|
|
|
+const toggleLevel = (level: number) => {
|
|
|
+ if (selectedLevels.value.includes(level)) {
|
|
|
+ if (selectedLevels.value.length === 1) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ selectedLevels.value = selectedLevels.value.filter((selectedLevel) => selectedLevel !== level)
|
|
|
+ } else {
|
|
|
+ selectedLevels.value = [...selectedLevels.value, level].sort((a, b) => a - b)
|
|
|
+ }
|
|
|
+ saveSelectedLevels()
|
|
|
loadLocationData()
|
|
|
scheduleNextRefresh()
|
|
|
}
|
|
|
@@ -993,11 +1159,20 @@ const handleRefreshContextMenu = async () => {
|
|
|
}
|
|
|
|
|
|
const handleDocumentClick = (event: MouseEvent) => {
|
|
|
+ const target = event.target as Node | null
|
|
|
+ if (target && zoneDropdownRef.value?.contains(target)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (target && levelDropdownRef.value?.contains(target)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ zoneDropdownVisible.value = false
|
|
|
+ levelDropdownVisible.value = false
|
|
|
+
|
|
|
if (!showRefreshPopover.value) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- const target = event.target as Node | null
|
|
|
if (target && refreshControlRef.value?.contains(target)) {
|
|
|
return
|
|
|
}
|
|
|
@@ -1108,6 +1283,12 @@ watch([categoryLabel, locationAttributeLabel, hasContainerLabel, zoneLabel, leve
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+watch(selectedLevels, (levels) => {
|
|
|
+ if (expandedLevel.value !== null && !levels.includes(expandedLevel.value)) {
|
|
|
+ expandedLevel.value = null
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
onBeforeUnmount(() => {
|
|
|
if (refreshTimer !== null) {
|
|
|
window.clearTimeout(refreshTimer)
|
|
|
@@ -1481,8 +1662,55 @@ onBeforeUnmount(() => {
|
|
|
color: var(--label-text);
|
|
|
}
|
|
|
|
|
|
-.level-select-floor {
|
|
|
- min-width: 0;
|
|
|
+.multi-select {
|
|
|
+ position: relative;
|
|
|
+ display: inline-flex;
|
|
|
+}
|
|
|
+
|
|
|
+.multi-select-trigger {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.multi-select-menu {
|
|
|
+ position: absolute;
|
|
|
+ top: calc(100% + 6px);
|
|
|
+ left: 0;
|
|
|
+ z-index: 40;
|
|
|
+ min-width: 100%;
|
|
|
+ padding: 4px;
|
|
|
+ border: 1px solid var(--popover-border);
|
|
|
+ border-radius: 4px;
|
|
|
+ background: var(--popover-bg);
|
|
|
+ box-shadow: var(--panel-shadow);
|
|
|
+}
|
|
|
+
|
|
|
+.multi-select-option {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 6px 8px;
|
|
|
+ border: none;
|
|
|
+ border-radius: 3px;
|
|
|
+ background: transparent;
|
|
|
+ color: var(--input-text);
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.multi-select-option:hover {
|
|
|
+ background: var(--input-hover-bg);
|
|
|
+}
|
|
|
+
|
|
|
+.multi-select-check {
|
|
|
+ width: 12px;
|
|
|
+ color: var(--input-text);
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 1;
|
|
|
+ text-align: center;
|
|
|
}
|
|
|
|
|
|
.filter-input-wrap {
|
|
|
@@ -1769,4 +1997,94 @@ onBeforeUnmount(() => {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
}
|
|
|
+
|
|
|
+.floor-map-grid {
|
|
|
+ display: grid;
|
|
|
+ gap: 10px;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-grid-1 {
|
|
|
+ grid-template-columns: minmax(0, 1fr);
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-grid-expanded {
|
|
|
+ grid-template-columns: minmax(0, 1fr);
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-grid-2 {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-grid-3,
|
|
|
+.floor-map-grid-4 {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ grid-template-rows: repeat(2, minmax(0, 1fr));
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-grid-5,
|
|
|
+.floor-map-grid-6 {
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ grid-template-rows: repeat(2, minmax(0, 1fr));
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-panel {
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border: 1px solid var(--header-border);
|
|
|
+ background: var(--bg);
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-panel > .warehouse-map {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ height: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-panel-hidden {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-title {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 6px 8px;
|
|
|
+ border-bottom: 1px solid var(--header-border);
|
|
|
+ color: var(--text-muted);
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-expand-btn {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0;
|
|
|
+ border: 1px solid var(--btn-neutral-border);
|
|
|
+ border-radius: 4px;
|
|
|
+ background: var(--btn-neutral-bg);
|
|
|
+ color: var(--btn-neutral-text);
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-expand-btn:hover {
|
|
|
+ background: var(--btn-neutral-hover-bg);
|
|
|
+ border-color: var(--btn-neutral-hover-border);
|
|
|
+}
|
|
|
+
|
|
|
+.floor-map-expand-icon {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+}
|
|
|
</style>
|