|
|
@@ -1,36 +1,20 @@
|
|
|
<template>
|
|
|
- <div class="shelf-container" :style="{ '--cols': cols, '--rows': rows }">
|
|
|
-
|
|
|
- <!-- 当前显示的面 -->
|
|
|
- <div class="shelf-rack">
|
|
|
- <!-- 货架主体 -->
|
|
|
- <div class="rack-body">
|
|
|
-
|
|
|
- <!-- 货架支柱 -->
|
|
|
- <div class="rack-pillars">
|
|
|
- <div class="rack-main">
|
|
|
- <!-- 货架层板 -->
|
|
|
- <div class="rack-shelves">
|
|
|
- <div
|
|
|
- v-for="row in reversedRows"
|
|
|
- :key="`shelf-${row}`"
|
|
|
- class="rack-shelf"
|
|
|
- >
|
|
|
- <!-- 货架板 -->
|
|
|
- <div class="shelf-board">
|
|
|
- <div
|
|
|
- v-for="col in cols"
|
|
|
- :key="`cell-${shelfName}-${currentFace}${row}${col}`"
|
|
|
- class="shelf-cell"
|
|
|
- :class="{ 'highlight': isHighlighted(`${shelfName}-${currentFace}${row}${col}`) }"
|
|
|
- >
|
|
|
- <!-- 完整编号显示 -->
|
|
|
- <div class="cell-number">
|
|
|
- {{ shelfName }}-{{ currentFace }}{{ row }}{{ col }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="shelf-container">
|
|
|
+ <div ref="shelfScrollRef" class="shelf-scroll">
|
|
|
+ <div class="shelf-grid">
|
|
|
+ <div
|
|
|
+ v-for="row in reversedRows"
|
|
|
+ :key="`shelf-${row}`"
|
|
|
+ class="shelf-row"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="col in cols"
|
|
|
+ :key="`cell-${shelfName}-${currentFace}${row}${col}`"
|
|
|
+ class="shelf-cell"
|
|
|
+ :class="{ highlight: isHighlighted(`${shelfName}-${currentFace}${row}${col}`) }"
|
|
|
+ >
|
|
|
+ <div class="cell-number">
|
|
|
+ {{ shelfName }}-{{ currentFace }}{{ row }}{{ col }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -40,9 +24,8 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { computed } from 'vue';
|
|
|
+import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
|
|
|
|
|
-// 接收父组件传递的数据
|
|
|
const props = defineProps({
|
|
|
shelfCode: {
|
|
|
type: String,
|
|
|
@@ -62,63 +45,69 @@ const props = defineProps({
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-// 计算属性:从大到小排列,让第一层在底部
|
|
|
const reversedRows = computed(() => {
|
|
|
return Array.from({ length: props.rows }, (_, i) => props.rows - i);
|
|
|
});
|
|
|
|
|
|
-// 当前显示的面
|
|
|
const currentFace = computed(() => {
|
|
|
- // 优先根据货架编码判断显示的面
|
|
|
if (props.shelfCode) {
|
|
|
const match = props.shelfCode.match(/^([A-Z]+\d+)-([AB])$/);
|
|
|
if (match) {
|
|
|
- return match[2]; // 返回A或B
|
|
|
+ return match[2];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 如果货架编码没有指定面,则根据highlightCells中的编号自动判断显示的面
|
|
|
if (props.highlightCells && props.highlightCells.length > 0) {
|
|
|
- // 检查是否有A面或B面的编号
|
|
|
const hasAFace = props.highlightCells.some(cell => cell.includes('-A'));
|
|
|
const hasBFace = props.highlightCells.some(cell => cell.includes('-B'));
|
|
|
|
|
|
- // 如果同时有AB面,默认显示A面;如果只有一面,显示那一面
|
|
|
if (hasAFace) return 'A';
|
|
|
if (hasBFace) return 'B';
|
|
|
}
|
|
|
- return 'A'; // 默认显示A面
|
|
|
+ return 'A';
|
|
|
});
|
|
|
|
|
|
-// 货架名称
|
|
|
const shelfName = computed(() => {
|
|
|
- // 从货架编码中提取货架名称,如 HJ001-A -> HJ001
|
|
|
if (props.shelfCode) {
|
|
|
const match = props.shelfCode.match(/^([A-Z]+\d+)-[AB]$/);
|
|
|
if (match) {
|
|
|
return match[1];
|
|
|
}
|
|
|
}
|
|
|
- return 'HJ001'; // 默认值
|
|
|
+ return 'HJ001';
|
|
|
});
|
|
|
|
|
|
-// 判断格口是否需要高亮
|
|
|
const isHighlighted = (cellId) => {
|
|
|
if (!cellId || !props.highlightCells || props.highlightCells.length === 0) return false;
|
|
|
-
|
|
|
return props.highlightCells.includes(cellId);
|
|
|
};
|
|
|
|
|
|
-// 暴露高亮方法
|
|
|
+const shelfScrollRef = ref(null);
|
|
|
+
|
|
|
+function scrollHighlightIntoView() {
|
|
|
+ const scrollEl = shelfScrollRef.value;
|
|
|
+ if (!scrollEl || !props.highlightCells?.length) return;
|
|
|
+ const highlighted = scrollEl.querySelector('.shelf-cell.highlight');
|
|
|
+ highlighted?.scrollIntoView({ block: 'center', behavior: 'smooth', inline: 'nearest' });
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [props.highlightCells, props.shelfCode, props.rows, props.cols],
|
|
|
+ () => {
|
|
|
+ nextTick(scrollHighlightIntoView);
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ nextTick(scrollHighlightIntoView);
|
|
|
+});
|
|
|
+
|
|
|
const highlightCell = (cellId) => {
|
|
|
- // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
|
|
|
- // 实际的高亮控制应该通过父组件更新 highlightCells prop
|
|
|
console.log('Highlight cell:', cellId);
|
|
|
};
|
|
|
|
|
|
const clearHighlight = () => {
|
|
|
- // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
|
|
|
- // 实际的高亮控制应该通过父组件更新 highlightCells prop
|
|
|
console.log('Clear highlight');
|
|
|
};
|
|
|
|
|
|
@@ -126,336 +115,74 @@ defineExpose({ highlightCell, clearHighlight });
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+
|
|
|
.shelf-container {
|
|
|
- width: 75%;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- background:
|
|
|
- linear-gradient(135deg, rgba(248, 249, 250, 0.95) 0%, rgba(233, 236, 239, 0.95) 100%),
|
|
|
- radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
|
|
|
- radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
|
|
|
- border-radius: 16px;
|
|
|
- box-shadow:
|
|
|
- 0 8px 32px rgba(0, 0, 0, 0.12),
|
|
|
- 0 2px 8px rgba(0, 0, 0, 0.06),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.3),
|
|
|
- inset 0 -1px 0 rgba(0, 0, 0, 0.05);
|
|
|
+ box-sizing: border-box;
|
|
|
+ width: 80%;
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ background: #f3f4f6;
|
|
|
overflow: hidden;
|
|
|
- position: relative;
|
|
|
- border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
-}
|
|
|
-
|
|
|
-.shelf-container::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- height: 2px;
|
|
|
- background: linear-gradient(90deg,
|
|
|
- transparent 0%,
|
|
|
- rgba(255, 255, 255, 0.9) 20%,
|
|
|
- rgba(255, 255, 255, 0.6) 50%,
|
|
|
- rgba(255, 255, 255, 0.9) 80%,
|
|
|
- transparent 100%);
|
|
|
- border-radius: 16px 16px 0 0;
|
|
|
-}
|
|
|
-
|
|
|
-.shelf-container::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- height: 1px;
|
|
|
- background: linear-gradient(90deg,
|
|
|
- transparent 0%,
|
|
|
- rgba(0, 0, 0, 0.1) 50%,
|
|
|
- transparent 100%);
|
|
|
}
|
|
|
|
|
|
-.shelf-rack {
|
|
|
- background:
|
|
|
- linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.9) 100%),
|
|
|
- radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.05) 0%, transparent 40%),
|
|
|
- radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.05) 0%, transparent 40%);
|
|
|
- overflow: hidden;
|
|
|
- position: relative;
|
|
|
- backdrop-filter: blur(12px) saturate(1.2);
|
|
|
- border-radius: 14px;
|
|
|
+.shelf-scroll {
|
|
|
+ box-sizing: border-box;
|
|
|
+ height: 100px;
|
|
|
margin: 2px;
|
|
|
+ padding: 2px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #fff;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
}
|
|
|
|
|
|
-.rack-title {
|
|
|
- margin: 0;
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 500;
|
|
|
- letter-spacing: 0.5px;
|
|
|
-}
|
|
|
-
|
|
|
-.rack-footer {
|
|
|
- background: #f5f5f5;
|
|
|
- padding: 8px 15px;
|
|
|
- text-align: center;
|
|
|
- border-radius: 3px;
|
|
|
-}
|
|
|
-
|
|
|
-.rack-footer .rack-title {
|
|
|
- margin: 0;
|
|
|
- font-size: 18px;
|
|
|
- font-weight: 600;
|
|
|
- color: #409eff;
|
|
|
- letter-spacing: 0.5px;
|
|
|
-}
|
|
|
-
|
|
|
-.rack-body {
|
|
|
- position: relative;
|
|
|
- display: block;
|
|
|
- min-height: calc(28px * var(--rows) + 4px);
|
|
|
-}
|
|
|
-
|
|
|
-/* 货架支柱 */
|
|
|
-.rack-pillars {
|
|
|
- display: flex;
|
|
|
- align-items: stretch;
|
|
|
- position: relative;
|
|
|
-}
|
|
|
-
|
|
|
-.rack-pillar {
|
|
|
- width: 5px;
|
|
|
- background:
|
|
|
- linear-gradient(180deg,
|
|
|
- #9ca3af 0%,
|
|
|
- #6b7280 20%,
|
|
|
- #4b5563 50%,
|
|
|
- #374151 80%,
|
|
|
- #1f2937 100%);
|
|
|
- position: relative;
|
|
|
- border-radius: 2.5px;
|
|
|
- box-shadow:
|
|
|
- 0 3px 8px rgba(0, 0, 0, 0.2),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.15),
|
|
|
- inset 0 -1px 0 rgba(0, 0, 0, 0.2),
|
|
|
- inset 1px 0 0 rgba(255, 255, 255, 0.05);
|
|
|
- border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
-}
|
|
|
-
|
|
|
-.left-pillar {
|
|
|
- margin-right: 10px;
|
|
|
- transform: skewY(-1deg);
|
|
|
-}
|
|
|
-
|
|
|
-.right-pillar {
|
|
|
- margin-left: 10px;
|
|
|
- transform: skewY(1deg);
|
|
|
-}
|
|
|
-
|
|
|
-.rack-main {
|
|
|
- flex: 1;
|
|
|
- position: relative;
|
|
|
-}
|
|
|
-
|
|
|
-/* 货架层板 */
|
|
|
-.rack-shelves {
|
|
|
- flex: 1;
|
|
|
+.shelf-grid {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 0px;
|
|
|
-}
|
|
|
-
|
|
|
-.rack-shelf {
|
|
|
- position: relative;
|
|
|
- flex: 1;
|
|
|
}
|
|
|
|
|
|
-.shelf-board {
|
|
|
- background: #fafafa;
|
|
|
- padding: 2px 1px;
|
|
|
- margin: 0;
|
|
|
+.shelf-row {
|
|
|
display: flex;
|
|
|
- gap: 0px;
|
|
|
- align-items: center;
|
|
|
- position: relative;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ min-height: 28px;
|
|
|
+ padding: 1px 0;
|
|
|
+ background: #fafafa;
|
|
|
}
|
|
|
|
|
|
-/* 货架格子 */
|
|
|
.shelf-cell {
|
|
|
- flex: 1;
|
|
|
+ flex: 1 1 0;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 24px;
|
|
|
+ margin: 1px;
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- background:
|
|
|
- linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 249, 250, 0.95) 100%),
|
|
|
- radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.8) 0%, transparent 50%),
|
|
|
- radial-gradient(circle at 75% 75%, rgba(0, 0, 0, 0.02) 0%, transparent 50%);
|
|
|
- border: 1px solid rgba(222, 226, 230, 0.8);
|
|
|
- border-bottom: 2px solid rgba(226, 232, 240, 0.9);
|
|
|
- border-radius: 6px;
|
|
|
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
- position: relative;
|
|
|
- min-width: 50px;
|
|
|
- min-height: 24px;
|
|
|
- margin: 0.5px;
|
|
|
- box-shadow:
|
|
|
- 0 1px 3px rgba(0, 0, 0, 0.06),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
|
|
- inset 0 -1px 0 rgba(0, 0, 0, 0.05);
|
|
|
- cursor: pointer;
|
|
|
-}
|
|
|
-
|
|
|
-.shelf-cell:hover {
|
|
|
- transform: translateY(-0.5px) scale(1.01);
|
|
|
- box-shadow:
|
|
|
- 0 3px 8px rgba(0, 0, 0, 0.1),
|
|
|
- 0 1px 3px rgba(0, 0, 0, 0.06),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.5),
|
|
|
- inset 0 -1px 0 rgba(0, 0, 0, 0.03);
|
|
|
- border-color: #a0aec0;
|
|
|
- background:
|
|
|
- linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(241, 243, 244, 0.98) 100%),
|
|
|
- radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.9) 0%, transparent 50%),
|
|
|
- radial-gradient(circle at 75% 75%, rgba(0, 0, 0, 0.01) 0%, transparent 50%);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
}
|
|
|
|
|
|
-/* 单元格编号 */
|
|
|
.cell-number {
|
|
|
- font-size: 16px;
|
|
|
+ font-size: 11px;
|
|
|
font-weight: 600;
|
|
|
- color: #1a202c;
|
|
|
+ color: #111827;
|
|
|
text-align: center;
|
|
|
- line-height: 1;
|
|
|
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
|
|
- letter-spacing: 0.01em;
|
|
|
- text-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.03);
|
|
|
- -webkit-font-smoothing: antialiased;
|
|
|
- -moz-osx-font-smoothing: grayscale;
|
|
|
+ line-height: 1.2;
|
|
|
+ padding: 1px 2px;
|
|
|
+ word-break: break-all;
|
|
|
}
|
|
|
|
|
|
.shelf-cell.highlight {
|
|
|
- background:
|
|
|
- linear-gradient(135deg, rgba(240, 253, 244, 0.95) 0%, rgba(220, 252, 231, 0.95) 100%),
|
|
|
- radial-gradient(circle at 30% 30%, rgba(34, 197, 94, 0.1) 0%, transparent 60%),
|
|
|
- radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.3) 0%, transparent 60%);
|
|
|
+ background: #dcfce7;
|
|
|
border-color: #22c55e;
|
|
|
- border-width: 1.5px;
|
|
|
- box-shadow:
|
|
|
- 0 4px 12px rgba(34, 197, 94, 0.25),
|
|
|
- 0 2px 6px rgba(34, 197, 94, 0.15),
|
|
|
- 0 0 0 1px rgba(34, 197, 94, 0.1),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
|
|
- transform: translateY(-0.5px) scale(1.02);
|
|
|
- animation: highlight-glow 2s ease-in-out infinite alternate;
|
|
|
+ border-width: 2px;
|
|
|
}
|
|
|
|
|
|
.shelf-cell.highlight .cell-number {
|
|
|
color: #166534;
|
|
|
- font-weight: 800;
|
|
|
- text-shadow:
|
|
|
- 0 1px 2px rgba(22, 101, 52, 0.2),
|
|
|
- 0 0 0 1px rgba(34, 197, 94, 0.1);
|
|
|
- background: linear-gradient(135deg, #166534 0%, #15803d 100%);
|
|
|
- -webkit-background-clip: text;
|
|
|
- -webkit-text-fill-color: transparent;
|
|
|
- background-clip: text;
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes highlight-glow {
|
|
|
- 0% {
|
|
|
- box-shadow:
|
|
|
- 0 4px 12px rgba(34, 197, 94, 0.25),
|
|
|
- 0 2px 6px rgba(34, 197, 94, 0.15),
|
|
|
- 0 0 0 1px rgba(34, 197, 94, 0.1),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
|
|
- }
|
|
|
- 100% {
|
|
|
- box-shadow:
|
|
|
- 0 6px 16px rgba(34, 197, 94, 0.35),
|
|
|
- 0 3px 8px rgba(34, 197, 94, 0.2),
|
|
|
- 0 0 0 1px rgba(34, 197, 94, 0.15),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/* 响应式调整 */
|
|
|
-@media (max-width: 768px) {
|
|
|
- .shelf-container {
|
|
|
- width: 260px !important;
|
|
|
- height: 100px !important;
|
|
|
- padding: 0;
|
|
|
- margin: 0 auto;
|
|
|
- border-radius: 12px;
|
|
|
- box-shadow:
|
|
|
- 0 6px 24px rgba(0, 0, 0, 0.15),
|
|
|
- 0 2px 8px rgba(0, 0, 0, 0.08),
|
|
|
- inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
|
|
- inset 0 -1px 0 rgba(0, 0, 0, 0.03);
|
|
|
- border: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
- }
|
|
|
-
|
|
|
- .shelf-rack {
|
|
|
- background:
|
|
|
- linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.9) 100%),
|
|
|
- radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.03) 0%, transparent 40%),
|
|
|
- radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.03) 0%, transparent 40%);
|
|
|
- backdrop-filter: blur(8px) saturate(1.1);
|
|
|
- border-radius: 10px;
|
|
|
- margin: 1px;
|
|
|
- }
|
|
|
-
|
|
|
- .rack-body {
|
|
|
- min-height: calc(18px * var(--rows) + 4px);
|
|
|
- height: calc(100% - 8px);
|
|
|
- }
|
|
|
-
|
|
|
- .rack-pillars {
|
|
|
- position: relative;
|
|
|
- margin-bottom: 3px;
|
|
|
- }
|
|
|
-
|
|
|
- .rack-pillar {
|
|
|
- width: 3px;
|
|
|
- }
|
|
|
-
|
|
|
- .left-pillar {
|
|
|
- margin-right: 3px;
|
|
|
- }
|
|
|
-
|
|
|
- .right-pillar {
|
|
|
- margin-left: 3px;
|
|
|
- }
|
|
|
-
|
|
|
- .rack-shelves {
|
|
|
- gap: 2px;
|
|
|
- }
|
|
|
-
|
|
|
- .shelf-board {
|
|
|
- padding: 1px 0px;
|
|
|
- gap: 1px;
|
|
|
- }
|
|
|
-
|
|
|
- .shelf-cell {
|
|
|
- min-height: 16px;
|
|
|
- min-width: 32px;
|
|
|
- padding: 1px;
|
|
|
- border-width: 1px;
|
|
|
- margin: 0.5px;
|
|
|
- border-radius: 3px;
|
|
|
- }
|
|
|
-
|
|
|
- .cell-number {
|
|
|
- font-size: 11px;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
-
|
|
|
- .shelf-cell:hover {
|
|
|
- transform: none;
|
|
|
- }
|
|
|
-
|
|
|
- .shelf-cell.highlight {
|
|
|
- border-color: #22c55e;
|
|
|
- border-width: 1.5px;
|
|
|
- box-shadow: 0 1px 4px rgba(34, 197, 94, 0.15), 0 0 0 1px rgba(34, 197, 94, 0.08);
|
|
|
- }
|
|
|
+ font-weight: 700;
|
|
|
}
|
|
|
</style>
|