|
|
@@ -0,0 +1,461 @@
|
|
|
+<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>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed } from 'vue';
|
|
|
+
|
|
|
+// 接收父组件传递的数据
|
|
|
+const props = defineProps({
|
|
|
+ shelfCode: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ highlightCells: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ rows: {
|
|
|
+ type: Number,
|
|
|
+ default: 4
|
|
|
+ },
|
|
|
+ cols: {
|
|
|
+ type: Number,
|
|
|
+ default: 2
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 计算属性:从大到小排列,让第一层在底部
|
|
|
+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
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果货架编码没有指定面,则根据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面
|
|
|
+});
|
|
|
+
|
|
|
+// 货架名称
|
|
|
+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'; // 默认值
|
|
|
+});
|
|
|
+
|
|
|
+// 判断格口是否需要高亮
|
|
|
+const isHighlighted = (cellId) => {
|
|
|
+ if (!cellId || !props.highlightCells || props.highlightCells.length === 0) return false;
|
|
|
+
|
|
|
+ return props.highlightCells.includes(cellId);
|
|
|
+};
|
|
|
+
|
|
|
+// 暴露高亮方法
|
|
|
+const highlightCell = (cellId) => {
|
|
|
+ // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
|
|
|
+ // 实际的高亮控制应该通过父组件更新 highlightCells prop
|
|
|
+ console.log('Highlight cell:', cellId);
|
|
|
+};
|
|
|
+
|
|
|
+const clearHighlight = () => {
|
|
|
+ // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
|
|
|
+ // 实际的高亮控制应该通过父组件更新 highlightCells prop
|
|
|
+ console.log('Clear highlight');
|
|
|
+};
|
|
|
+
|
|
|
+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);
|
|
|
+ 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;
|
|
|
+ margin: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 0px;
|
|
|
+}
|
|
|
+
|
|
|
+.rack-shelf {
|
|
|
+ position: relative;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.shelf-board {
|
|
|
+ background: #fafafa;
|
|
|
+ padding: 2px 1px;
|
|
|
+ margin: 0;
|
|
|
+ display: flex;
|
|
|
+ gap: 0px;
|
|
|
+ align-items: center;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+/* 货架格子 */
|
|
|
+.shelf-cell {
|
|
|
+ flex: 1;
|
|
|
+ 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%);
|
|
|
+}
|
|
|
+
|
|
|
+/* 单元格编号 */
|
|
|
+.cell-number {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a202c;
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+.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%);
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+.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);
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|