|
@@ -0,0 +1,494 @@
|
|
|
|
+<template>
|
|
|
|
+ <div>
|
|
|
|
+ <q-toolbar class="row items-center ">
|
|
|
|
+ <q-btn-group push class="btn-group">
|
|
|
|
+ <q-btn :label="$t('stock.shelf.shelf_up')" icon="upload" @click="handleShelfUp()" />
|
|
|
|
+ <div class="self-center text-center q-px-sm">
|
|
|
|
+ {{ $t('stock.layertip') }}
|
|
|
|
+ </div>
|
|
|
|
+ <q-input dense color="primary" v-model="shelf.layer_now" style="width: 50px;" />
|
|
|
|
+ <q-btn :label="$t('stock.shelf.shelf_down')" icon="download" @click="handleShelfDown()" />
|
|
|
|
+ <q-btn :label="$t('refresh')" icon="refresh" @click="reFresh()">
|
|
|
|
+ <q-tooltip content-class="bg-amber text-black shadow-4" :offset="[10, 10]"
|
|
|
|
+ content-style="font-size: 12px">
|
|
|
|
+ {{ $t('refreshtip') }}
|
|
|
|
+ </q-tooltip>
|
|
|
|
+ </q-btn>
|
|
|
|
+ <q-separator />
|
|
|
|
+ <q-btn :label="$t('stock.edit')" icon="edit" @click="handle_edit()" />
|
|
|
|
+
|
|
|
|
+ </q-btn-group>
|
|
|
|
+ <goodscard
|
|
|
|
+ v-if="showInventoryDetails"
|
|
|
|
+ ref ="goodscard"
|
|
|
|
+ :col-index="select_Inventory.colIndex"
|
|
|
|
+ :row-index="select_Inventory.rowIndex"
|
|
|
|
+ :layer-index="select_Inventory.layerIndex"
|
|
|
|
+ :goods-data="select_Inventory.goods_data"
|
|
|
|
+ @close="showInventoryDetails = false"
|
|
|
|
+
|
|
|
|
+ />
|
|
|
|
+ </q-toolbar>
|
|
|
|
+ <q-page class="q-pa-md ">
|
|
|
|
+ <div class="grid-system">
|
|
|
|
+ <!-- Y轴 -->
|
|
|
|
+ <div class="axis y-axis">
|
|
|
|
+ <div class="axis-numbers">
|
|
|
|
+ <div v-for="index in shelf.rows" :key="'y' + index">
|
|
|
|
+ {{ index }}
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="axis-arrow"></div>
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- X轴 -->
|
|
|
|
+ <div class="axis x-axis">
|
|
|
|
+ <div class="axis-arrow"></div>
|
|
|
|
+ <div class="axis-numbers">
|
|
|
|
+ <div v-for="col in shelf.cols" :key="'x' + col" class="axis-label">
|
|
|
|
+ {{ col }}
|
|
|
|
+ </div>
|
|
|
|
+ <div class="axis-label">
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 网格系统 -->
|
|
|
|
+ <div class="grid-container">
|
|
|
|
+ <!-- 内容层 -->
|
|
|
|
+ <div class="grid-content">
|
|
|
|
+ <div v-for="(row, rowIndex) in shelf.rows" :key="`row-${rowIndex}|${shelf.layer_now}`" class="grid-row">
|
|
|
|
+ <div v-for="(col, colIndex) in shelf.cols" :key="`col-${colIndex}|${shelf.layer_now}`" class="grid-item">
|
|
|
|
+ <div
|
|
|
|
+ v-if="shouldShowButton(
|
|
|
|
+ shelf.rows - rowIndex,
|
|
|
|
+ colIndex + 1,
|
|
|
|
+ shelf.layer_now
|
|
|
|
+ )"
|
|
|
|
+ :key="`${shelf.rows - rowIndex}-${colIndex}-${shelf.layer_now}`"
|
|
|
|
+ :style="{
|
|
|
|
+ border: '1px solid #ccc',
|
|
|
|
+ borderRadius: '5px',
|
|
|
|
+ width: 'var(--cell-d)',
|
|
|
|
+ height: 'var(--cell-d)',
|
|
|
|
+ backgroundColor: getBinColor(shelf.rows - rowIndex, colIndex+1, shelf.layer_now),
|
|
|
|
+ cursor: 'pointer' // 明确指示点击行为
|
|
|
|
+ }"
|
|
|
|
+ @click="handleBinClick(shelf.rows - rowIndex, colIndex+1, shelf.layer_now)"
|
|
|
|
+ >
|
|
|
|
+ <!-- 确保没有子元素获得焦点 -->
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <q-tooltip content-class="bg-amber text-black shadow-4" :offset="[20, 20]"
|
|
|
|
+ content-style="font-size: 10px">
|
|
|
|
+ {{ $t('stock.rowtip') }} {{ shelf.rows - rowIndex }}
|
|
|
|
+ {{ $t('stock.coltip') }} {{ colIndex + 1 }}
|
|
|
|
+ </q-tooltip>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </q-page>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+import goodscard from 'components/goodscard.vue'
|
|
|
|
+import { LocalStorage } from 'quasar'
|
|
|
|
+import { getauth, postauth, putauth, deleteauth, getfile } from 'boot/axios_request'
|
|
|
|
+
|
|
|
|
+export default {
|
|
|
|
+ components: { goodscard },
|
|
|
|
+ // 选项式 API 写法
|
|
|
|
+ data() {
|
|
|
|
+ return {
|
|
|
|
+ pathname: 'stock/management/',
|
|
|
|
+
|
|
|
|
+ warehouse_code: '',
|
|
|
|
+ warehouse_name: '',
|
|
|
|
+ shelf_name: 'A区货架',
|
|
|
|
+
|
|
|
|
+ shelf: {
|
|
|
|
+ rows: 17, // 控制行数
|
|
|
|
+ cols: 29, // 控制列数
|
|
|
|
+ layers: 3, // 控制层数
|
|
|
|
+ layer_now: 1, // 当前层数
|
|
|
|
+ },
|
|
|
|
+ filter: "",
|
|
|
|
+ auth_edit: false,
|
|
|
|
+ goodsMap: {}, //
|
|
|
|
+
|
|
|
|
+ goodsMatrix: [] , // 二维数据矩阵
|
|
|
|
+
|
|
|
|
+ // 颜色配置
|
|
|
|
+ binColors: {
|
|
|
|
+ elevator: 'rgba(255, 215, 0, 0.5)', // 黄色半透明
|
|
|
|
+ storage: "#4CAF50" ,//绿色, // 蓝色低透明度
|
|
|
|
+ occupied: '#32CD3280', // 绿色带透明度(HEX 8位格式)
|
|
|
|
+ default: 'transparent' , // 完全透明
|
|
|
|
+ } ,
|
|
|
|
+ showInventoryDetails : false, // 显示库存详情
|
|
|
|
+ select_Inventory: {
|
|
|
|
+ rowIndex : 0,
|
|
|
|
+ colIndex : 0,
|
|
|
|
+ layerIndex : 0,
|
|
|
|
+ goods_data : {}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // 修正后的路由守卫
|
|
|
|
+ beforeRouteEnter(to, from, next) {
|
|
|
|
+ next(vm => {
|
|
|
|
+ // 使用 vm 访问组件实例
|
|
|
|
+ vm.goodsMatrix = []
|
|
|
|
+
|
|
|
|
+ vm.goodsMap = {}
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ beforeDestroy() {
|
|
|
|
+ //清空数据
|
|
|
|
+ this.goodsMap = {}
|
|
|
|
+ this.goodsMatrix = []
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ shouldShowButton(row, col, layer) {
|
|
|
|
+ const bin = this.goodsMap[`${row}-${col}-${layer}`]
|
|
|
|
+ // 示例:仅显示storage类型的库位
|
|
|
|
+ return bin?.shelf_type === 'storage'
|
|
|
|
+ // return ['storage', 'occupied'].includes(bin?.shelf_type) // 显示多种类型
|
|
|
|
+ },
|
|
|
|
+ getList() {
|
|
|
|
+ var _this = this;
|
|
|
|
+ getauth(_this.pathname + '?layer=' + '' + _this.shelf.layer_now + '&warehouse_code='
|
|
|
|
+ + '' + _this.warehouse_code + '&max_page=1000' + '&shelf_name=' + _this.shelf_name
|
|
|
|
+ , {})
|
|
|
|
+ .then(res => {
|
|
|
|
+ _this.goodsMap = {};
|
|
|
|
+ res.results.forEach(item => {
|
|
|
|
+ const key = `${item.row}-${item.col}-${item.layer}`;
|
|
|
|
+ _this.goodsMap[key] = item;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ _this.$q.notify({
|
|
|
|
+ message: res.detail,
|
|
|
|
+ icon: 'done',
|
|
|
|
+ color: 'positive'
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ .catch(err => {
|
|
|
|
+ _this.$q.notify({
|
|
|
|
+ message: err.detail,
|
|
|
|
+ icon: 'close',
|
|
|
|
+ color: 'negative'
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ handle_setting() {
|
|
|
|
+ if (LocalStorage.has('warehouse_code')) {
|
|
|
|
+ this.warehouse_code = LocalStorage.getItem('warehouse_code')
|
|
|
|
+ }
|
|
|
|
+ if (LocalStorage.has('warehouse_name')) {
|
|
|
|
+ this.warehouse_name = LocalStorage.getItem('warehouse_name')
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // 获取货位颜色
|
|
|
|
+ getBinColor(row, col, layer) {
|
|
|
|
+ const bin = this.goodsMap[`${row}-${col}-${layer}`];
|
|
|
|
+
|
|
|
|
+ if (!bin) return this.binColors.default;
|
|
|
|
+ return this.binColors[bin.shelf_type] || this.binColors.default;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 货位点击处理
|
|
|
|
+ handleBinClick(row, col, layer) {
|
|
|
|
+ this.select_Inventory.rowIndex = row
|
|
|
|
+ this.select_Inventory.colIndex = col
|
|
|
|
+ this.select_Inventory.layerIndex = layer
|
|
|
|
+ this.select_Inventory.goods_data = this.goodsMap[`${row}-${col}-${layer}`]
|
|
|
|
+ this.showInventoryDetails = true
|
|
|
|
+ console.log(this.select_Inventory)
|
|
|
|
+ this.$refs.goodscard.handleclick()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ updateCSSVariables() {
|
|
|
|
+ const root = document.documentElement
|
|
|
|
+ // 获取组件容器的实际尺寸
|
|
|
|
+ const dwidth = document.documentElement.clientWidth
|
|
|
|
+ const dheight = document.documentElement.clientHeight
|
|
|
|
+ console.log(dwidth, dheight)
|
|
|
|
+
|
|
|
|
+ //
|
|
|
|
+ const width = dwidth * 0.6
|
|
|
|
+ const height = dheight * 0.6
|
|
|
|
+
|
|
|
|
+ //计算网格的宽度
|
|
|
|
+ var cell_d = width * 7 / 8 / this.shelf.cols //页面占比
|
|
|
|
+ var cell_x = cell_d * 1/ 5 //网格占比
|
|
|
|
+ var cellSize = cell_x / 2
|
|
|
|
+ var cellGap = height / this.shelf.rows - cell_d
|
|
|
|
+ //console.log(cellSize, cellGap)
|
|
|
|
+ if (cellGap < 2) {
|
|
|
|
+ cellGap = 2
|
|
|
|
+ cell_d = (height - cellGap * this.shelf.rows) / this.shelf.rows;
|
|
|
|
+ cell_x = cell_d * 3 / 5
|
|
|
|
+ cellSize = cell_x / 2
|
|
|
|
+ }
|
|
|
|
+ var cellSize_2 = cellGap / 2
|
|
|
|
+ var axis_x = cell_x * this.shelf.cols + cell_d * this.shelf.cols
|
|
|
|
+ //console.log(axis_x)
|
|
|
|
+ root.style.setProperty('--cell-d', `${cell_d}px`)
|
|
|
|
+ root.style.setProperty('--cell-d-x', `${cell_d + cell_x}px`)
|
|
|
|
+
|
|
|
|
+ root.style.setProperty('--cell-x-2', `${cellSize}px`)
|
|
|
|
+ root.style.setProperty('--cell-x', `${cell_x}px`)
|
|
|
|
+ root.style.setProperty('--cell-y', `${cellGap + cell_d}px`)
|
|
|
|
+ root.style.setProperty('--cell-y-2', `${cellSize_2}px`)
|
|
|
|
+ root.style.setProperty('--axis-x', `${axis_x}px`)
|
|
|
|
+ },
|
|
|
|
+ // 新增防抖方法避免频繁触发
|
|
|
|
+ handleResize() {
|
|
|
|
+ clearTimeout(this.resizeTimer)
|
|
|
|
+ this.resizeTimer = setTimeout(() => {
|
|
|
|
+ this.updateCSSVariables()
|
|
|
|
+ }, 200)
|
|
|
|
+ },
|
|
|
|
+ handleShelfDown() {
|
|
|
|
+ if (this.shelf.layer_now > this.shelf.layers) { this.shelf.layer_now = this.shelf.layers }
|
|
|
|
+ if (this.shelf.layer_now > 1) {
|
|
|
|
+ this.shelf.layer_now -= 1
|
|
|
|
+ this.reFresh()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+ handleShelfUp() {
|
|
|
|
+ if (this.shelf.layer_now < this.shelf.layers) {
|
|
|
|
+ this.shelf.layer_now += 1
|
|
|
|
+ this.reFresh()
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ this.shelf.layer_now = this.shelf.layers
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ reFresh() {
|
|
|
|
+ this.handle_setting()
|
|
|
|
+ this.getList()
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+ handle_edit() {
|
|
|
|
+ // 取反
|
|
|
|
+ this.auth_edit = !this.auth_edit
|
|
|
|
+ LocalStorage.set('auth_edit', this.auth_edit)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+ mounted() {
|
|
|
|
+ this.updateCSSVariables()
|
|
|
|
+ window.addEventListener('resize', this.handleResize)
|
|
|
|
+ },
|
|
|
|
+ // 添加 beforeUnmount 生命周期
|
|
|
|
+ beforeUnmount() {
|
|
|
|
+ // 清理数据
|
|
|
|
+ this.goodsMap = {}
|
|
|
|
+ this.goodsMatrix = []
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // 清理事件监听
|
|
|
|
+ window.removeEventListener('resize', this.handleResize)
|
|
|
|
+ clearTimeout(this.resizeTimer)
|
|
|
|
+
|
|
|
|
+ // 清理 DOM
|
|
|
|
+ if (this.$refs.goodscard) {
|
|
|
|
+ this.$refs.goodscard.$destroy()
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ created() {
|
|
|
|
+
|
|
|
|
+ LocalStorage.set('auth_edit', this.auth_edit)
|
|
|
|
+ if (LocalStorage.has('warehouse_code')) {
|
|
|
|
+ this.warehouse_code = LocalStorage.getItem('warehouse_code')
|
|
|
|
+ }
|
|
|
|
+ if (LocalStorage.has('warehouse_name')) {
|
|
|
|
+ this.warehouse_name = LocalStorage.getItem('warehouse_name')
|
|
|
|
+ }
|
|
|
|
+ this.getList()
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+:root {
|
|
|
|
+
|
|
|
|
+ --cell-d: 40px;
|
|
|
|
+ --cell-d-x: 100px;
|
|
|
|
+ --cell-x-2: 20px;
|
|
|
|
+ --cell-x: 20px;
|
|
|
|
+ --cell-y: 100px;
|
|
|
|
+ --cell-y-2: 20px;
|
|
|
|
+ --axis-x: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn-group {
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: var(--cell-x-2);
|
|
|
|
+ display: flex;
|
|
|
|
+ gap: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* 网格系统容器 */
|
|
|
|
+.grid-system {
|
|
|
|
+ position: relative;
|
|
|
|
+ padding-left: var(--cell-x-2);
|
|
|
|
+ padding-right: 330px;
|
|
|
|
+ /* 左边留出Y轴空间 */
|
|
|
|
+ padding-top: 10px;
|
|
|
|
+ /* 下边留出X轴空间 */
|
|
|
|
+ min-width: max-content;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 坐标轴通用样式 */
|
|
|
|
+.axis {
|
|
|
|
+
|
|
|
|
+ position: absolute;
|
|
|
|
+
|
|
|
|
+ background: #333;
|
|
|
|
+ z-index: 2;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 箭头 */
|
|
|
|
+.axis-arrow {
|
|
|
|
+ position: absolute;
|
|
|
|
+
|
|
|
|
+ border-top: 6px solid transparent;
|
|
|
|
+ border-bottom: 6px solid transparent;
|
|
|
|
+ border-left: 12px solid #555;
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.y-axis .axis-arrow {
|
|
|
|
+ top: -10px;
|
|
|
|
+ left: -4px;
|
|
|
|
+ border-left: 5px solid transparent;
|
|
|
|
+ border-right: 5px solid transparent;
|
|
|
|
+ border-bottom: 10px solid #333;
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.x-axis .axis-arrow {
|
|
|
|
+ right: -3px;
|
|
|
|
+ top: -5px;
|
|
|
|
+ border-left-color: #333;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Y轴样式 */
|
|
|
|
+.y-axis {
|
|
|
|
+ left: 30px;
|
|
|
|
+ top: 0;
|
|
|
|
+ bottom: -10px;
|
|
|
|
+ /* 留出X轴空间 */
|
|
|
|
+ width: 2px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.y-axis .axis-numbers {
|
|
|
|
+ position: absolute;
|
|
|
|
+ right: 6px;
|
|
|
|
+
|
|
|
|
+ height: 100%;
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column-reverse;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.y-axis .axis-numbers div {
|
|
|
|
+ position: relative;
|
|
|
|
+ height: var(--cell-y);
|
|
|
|
+ /* 与网格行高一致 */
|
|
|
|
+ line-height: var(--cell-x);
|
|
|
|
+ top: -0.5em;
|
|
|
|
+ /* 垂直居中 */
|
|
|
|
+ color: #111;
|
|
|
|
+ font-size: larger;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* X轴样式 */
|
|
|
|
+.x-axis {
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: 30px;
|
|
|
|
+ width: var(--axis-x);
|
|
|
|
+ /* 直接使用变量控制宽度 */
|
|
|
|
+ right: auto;
|
|
|
|
+ bottom: -10px;
|
|
|
|
+
|
|
|
|
+ height: 2px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.x-axis .axis-numbers {
|
|
|
|
+ position: absolute;
|
|
|
|
+ top: 10px;
|
|
|
|
+ /* 数字显示在轴线下方 */
|
|
|
|
+
|
|
|
|
+ display: flex;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.x-axis .axis-numbers div {
|
|
|
|
+ width: var(--cell-d-x);
|
|
|
|
+ /* 与网格列宽一致 */
|
|
|
|
+ text-align: center;
|
|
|
|
+ color: #333;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 网格系统 */
|
|
|
|
+.grid-container {
|
|
|
|
+ position: relative;
|
|
|
|
+ margin-left: 30px;
|
|
|
|
+ /* 与Y轴对齐 */
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 网格内容 */
|
|
|
|
+.grid-content {
|
|
|
|
+ position: relative;
|
|
|
|
+ z-index: 2;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.grid-row {
|
|
|
|
+ display: flex;
|
|
|
|
+ height: var(--cell-y);
|
|
|
|
+ /* 固定行高上列下行 */
|
|
|
|
+ gap: var(--cell-x);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.grid-item {
|
|
|
|
+
|
|
|
|
+ width: var(--cell-d);
|
|
|
|
+ /* 固定宽度 */
|
|
|
|
+ height: var(--cell-d);
|
|
|
|
+
|
|
|
|
+ background: transparent;
|
|
|
|
+ transition: transform 0.2s;
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+.q-btn:hover {
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+</style>
|