handy há 1 mês atrás
pai
commit
b7d2ea7f79
2 ficheiros alterados com 79 adições e 24 exclusões
  1. 53 0
      src/App.vue
  2. 26 24
      src/components/WarehouseMap.vue

+ 53 - 0
src/App.vue

@@ -49,6 +49,13 @@
             placeholder="输入库位组"
           />
         </label>
+        <label class="toggle-item">
+          <span class="selector-label">库位组边框</span>
+          <input v-model="showGroupBorder" class="toggle-input" type="checkbox" />
+          <span class="toggle-track">
+            <span class="toggle-thumb"></span>
+          </span>
+        </label>
         <label class="level-selector">
           <span class="selector-label">楼层</span>
           <select v-model.number="currentLevel" class="level-select" @change="handleLevelChange">
@@ -76,6 +83,7 @@
           :selected-category="selectedCategory"
           :selected-location-attribute="selectedLocationAttribute"
           :loc-group-keyword="locGroupKeyword"
+          :show-group-border="showGroupBorder"
         />
       </div>
     </main>
@@ -99,6 +107,7 @@ const showLoginModal = ref(false)
 const selectedCategory = ref('')
 const selectedLocationAttribute = ref<LocationAttributeCode | ''>('')
 const locGroupKeyword = ref('')
+const showGroupBorder = ref(true)
 const now = ref(Date.now())
 const nextRefreshAt = ref(Date.now() + config.refreshInterval)
 let refreshTimer: number | null = null
@@ -329,6 +338,13 @@ onBeforeUnmount(() => {
   min-width: 180px;
 }
 
+.toggle-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  color: #d4e2f2;
+}
+
 .selector-label {
   font-size: 11px;
   color: #b9cbdb;
@@ -379,6 +395,43 @@ onBeforeUnmount(() => {
   border-color: #7a96af;
 }
 
+.toggle-input {
+  position: absolute;
+  opacity: 0;
+  pointer-events: none;
+}
+
+.toggle-track {
+  position: relative;
+  width: 34px;
+  height: 18px;
+  border-radius: 999px;
+  background: rgba(111, 140, 167, 0.24);
+  border: 1px solid rgba(111, 140, 167, 0.5);
+  transition: all 0.2s;
+  cursor: pointer;
+}
+
+.toggle-thumb {
+  position: absolute;
+  top: 1px;
+  left: 1px;
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+  background: #eef4f8;
+  transition: transform 0.2s;
+}
+
+.toggle-input:checked + .toggle-track {
+  background: rgba(111, 140, 167, 0.45);
+  border-color: #d7e2ea;
+}
+
+.toggle-input:checked + .toggle-track .toggle-thumb {
+  transform: translateX(16px);
+}
+
 .level-select:hover,
 .level-select:focus {
   background: rgba(111, 140, 167, 0.12);

+ 26 - 24
src/components/WarehouseMap.vue

@@ -33,6 +33,7 @@ interface Props {
   selectedCategory: string
   selectedLocationAttribute: string
   locGroupKeyword: string
+  showGroupBorder: boolean
 }
 
 interface ParsedLocation {
@@ -83,17 +84,6 @@ const CATEGORY_THEME_MAP: Record<string, { solid: string; soft: string; text: st
   }
 }
 
-const GROUP_COLOR_PALETTE = [
-  '#bba28a',
-  '#8ea6c6',
-  '#9ab59d',
-  '#b9a0c9',
-  '#c29f7b',
-  '#8eb7bf',
-  '#c39aac',
-  '#9ba7b4'
-]
-
 const LOCATION_ATTRIBUTE_LABEL_MAP: Record<string, string> = {
   OK: '正常',
   FI: '禁入',
@@ -248,7 +238,10 @@ const getCellClass = (cell: GridCell | null) => {
   if (!cell) return ['aisle']
   if (!isCellMatched(cell)) return ['aisle']
 
-  const classNames = [`category-${cell.category.toLowerCase()}`, 'grouped']
+  const classNames = [`category-${cell.category.toLowerCase()}`]
+  if (props.showGroupBorder && hasSameGroupNeighbor(cell)) {
+    classNames.push('grouped')
+  }
   if (cell.categoryMismatch) {
     classNames.push('category-mismatch')
   }
@@ -301,14 +294,6 @@ const getCategoryStyle = (cell: GridCell) => {
   }
 }
 
-const getGroupAccentColor = (locGroup1: string) => {
-  let hash = 0
-  for (let index = 0; index < locGroup1.length; index++) {
-    hash = (hash * 31 + locGroup1.charCodeAt(index)) >>> 0
-  }
-  return GROUP_COLOR_PALETTE[hash % GROUP_COLOR_PALETTE.length]
-}
-
 const isSameGroupNeighbor = (cell: GridCell, xOffset: number, yOffset: number) => {
   const neighbor = locationMap.value.get(
     `${cell.parsed.gridRow + xOffset}-${cell.parsed.gridCol + yOffset}`
@@ -316,14 +301,20 @@ const isSameGroupNeighbor = (cell: GridCell, xOffset: number, yOffset: number) =
   return Boolean(neighbor && neighbor.locGroup1 === cell.locGroup1)
 }
 
+const hasSameGroupNeighbor = (cell: GridCell) => {
+  return isSameGroupNeighbor(cell, -1, 0)
+    || isSameGroupNeighbor(cell, 1, 0)
+    || isSameGroupNeighbor(cell, 0, -1)
+    || isSameGroupNeighbor(cell, 0, 1)
+}
+
 const getCellStyle = (cell: GridCell | null): CSSProperties => {
-  if (!cell || !isCellMatched(cell)) return {}
+  if (!cell || !isCellMatched(cell) || !props.showGroupBorder) return {}
 
-  const groupColor = getGroupAccentColor(cell.locGroup1)
   const borderWidth = 'var(--group-outline-width, 2px)'
 
   return {
-    '--group-border-color': groupColor,
+    '--group-border-color': '#ffffff',
     '--group-border-top': isSameGroupNeighbor(cell, -1, 0) ? '0px' : borderWidth,
     '--group-border-right': isSameGroupNeighbor(cell, 0, 1) ? '0px' : borderWidth,
     '--group-border-bottom': isSameGroupNeighbor(cell, 1, 0) ? '0px' : borderWidth,
@@ -401,7 +392,18 @@ onBeforeUnmount(() => {
 }
 
 .grid-cell.grouped::after {
-  content: none;
+  content: '';
+  position: absolute;
+  inset: 0;
+  border-style: solid;
+  border-color: var(--group-border-color, #ffffff);
+  border-top-width: var(--group-border-top, 0px);
+  border-right-width: var(--group-border-right, 0px);
+  border-bottom-width: var(--group-border-bottom, 0px);
+  border-left-width: var(--group-border-left, 0px);
+  border-radius: 4px;
+  pointer-events: none;
+  opacity: 0.95;
 }
 
 .grid-cell.aisle {