Forráskód Böngészése

切换新的库位界面

flower_mr 1 hónapja
szülő
commit
58a4afb4ac

BIN
bin/__pycache__/filter.cpython-38.pyc


BIN
bin/__pycache__/models.cpython-38.pyc


BIN
bin/__pycache__/serializers.cpython-38.pyc


BIN
bin/__pycache__/urls.cpython-38.pyc


BIN
bin/__pycache__/views.cpython-38.pyc


+ 37 - 34
bin/filter.py

@@ -5,56 +5,59 @@ class DeviceFilter(FilterSet):
     class Meta:
         model = DeviceModel
         fields = {
-            device_id: ['icontains'],
-            device_name: ['icontains'],
-            device_type: ['icontains'],
-            ip_address: ['icontains'],
-            port: ['exact'],
-            status: ['icontains'],
-            create_time: ['exact', 'range'],
-            update_time: ['exact', 'range'],
+            "location": ['exact'],
+            "device_id": ['icontains'],
+            "device_name": ['icontains'],
+            "device_type": ['icontains'],
+            "ip_address": ['icontains'],
+            "port": ['exact'],
+            "status": ['icontains'],
+            "create_time": ['exact', 'range'],
+            "update_time": ['exact', 'range'],
         }
 
 class LocationFilter(FilterSet):
     class Meta:
         model = LocationModel
         fields = {
-            warehouse_code: ['icontains'],
-            warehouse_name: ['icontains'],
-            shelf_type: ['icontains'],
-            row: ['exact', 'range'],
-            col: ['exact', 'range'],
-            layer: ['exact', 'range'],
-            update_time: ['exact', 'range'],
-            empty_label: ['exact'],
-            location_code: ['icontains'],
-            location_type: ['icontains'],
-            status: ['icontains'],
-            max_capacity: ['exact', 'range'],
-            current_quantity: ['exact', 'range'],
-            coordinate: ['icontains'],
+            "warehouse_code": ['icontains'],
+            "warehouse_name": ['icontains'],
+            "shelf_type": ['icontains'],
+            "row": ['exact', 'range'],
+            "col": ['exact', 'range'],
+            "layer": ['exact', 'range'],
+            "update_time": ['exact', 'range'],
+            "empty_label": ['exact'],
+            "location_code": ['icontains'],
+            "location_type": ['icontains'],
+            "status": ['icontains'],
+            "max_capacity": ['exact', 'range'],
+            "current_quantity": ['exact', 'range'],
+            "coordinate": ['icontains'],
         }
 
 class LocationContainerLinkFilter(FilterSet):
     class Meta:
         model = LocationContainerLink
         fields = {
-            location: ['exact'],
-            container: ['exact'],
-            put_time: ['exact', 'range'],
-            operator: ['icontains'],
-            is_active: ['exact'],
+            "location": ['exact'],
+            "container": ['exact'],
+            "put_time": ['exact', 'range'],
+            "operator": ['icontains'],
+            "is_active": ['exact'],
         }
 
 class LocationChangeLogFilter(FilterSet):
     class Meta:
         model = LocationChangeLog        
         fields = {
-            location: ['exact'],
-            container: ['exact'],
-            operation_type: ['exact'],
-            related_location: ['exact'],
-            timestamp: ['exact', 'range'],
-            operator: ['icontains'],
-            wcs_task_id: ['icontains'],
+            "location": ['exact'],
+            "container": ['exact'],
+            "operation_type": ['exact'],
+            "related_location": ['exact'],
+            "timestamp": ['exact', 'range'],
+           
         }
+
+
+

+ 18 - 0
bin/migrations/0004_alter_locationmodel_location_type.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-04-15 22:07
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bin', '0003_locationmodel_access_priority_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='locationmodel',
+            name='location_type',
+            field=models.CharField(choices=[('T5', '5货位'), ('T4', '4货位'), ('S4', '4单货位'), ('T2', '2货位'), ('T1', '散货位'), ('M1', '通道区'), ('E1', '提升机'), ('C1', '输送机')], max_length=3, verbose_name='货位类型'),
+        ),
+    ]

+ 17 - 0
bin/migrations/0005_alter_devicemodel_unique_together.py

@@ -0,0 +1,17 @@
+# Generated by Django 4.1.2 on 2025-04-15 22:34
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bin', '0004_alter_locationmodel_location_type'),
+    ]
+
+    operations = [
+        migrations.AlterUniqueTogether(
+            name='devicemodel',
+            unique_together={('location', 'device_id')},
+        ),
+    ]

+ 18 - 0
bin/migrations/0006_locationmodel_c_number.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-04-15 22:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bin', '0005_alter_devicemodel_unique_together'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='locationmodel',
+            name='c_number',
+            field=models.IntegerField(default=1, verbose_name='库位远近排序'),
+        ),
+    ]

BIN
bin/migrations/__pycache__/0004_alter_locationmodel_location_type.cpython-38.pyc


BIN
bin/migrations/__pycache__/0005_alter_devicemodel_unique_together.cpython-38.pyc


BIN
bin/migrations/__pycache__/0006_locationmodel_c_number.cpython-38.pyc


+ 50 - 18
bin/models.py

@@ -40,6 +40,7 @@ class LocationModel(models.Model):
     status = models.CharField(max_length=20, choices=LOCATION_STATUS, default='available', verbose_name='库位状态')
     max_capacity = models.PositiveIntegerField(verbose_name='最大容量')  # 根据类型自动设置
     current_quantity = models.PositiveIntegerField(default=0, verbose_name='当前托盘数')
+    c_number = models.IntegerField(default=1, verbose_name='库位远近排序')
     current_containers = models.ManyToManyField(ContainerListModel, 
                                               through='LocationContainerLink',
                                               verbose_name='当前存放托盘')
@@ -56,7 +57,6 @@ class LocationModel(models.Model):
         verbose_name_plural = "Location"
         ordering = ['-id']
         unique_together = ( 'warehouse_code','row', 'col', 'layer')  # 防止重复坐标
-
     
     @classmethod
     def get_existing_positions(cls, warehouse_code, rows, cols, layers):
@@ -88,6 +88,8 @@ class LocationModel(models.Model):
                     if col in MAIN_AISLES or row in SUB_AISLES:
                         loc_type = 'M1'
                         c_number = row
+                        # 通道
+                    
                         if col ==18 and row == 1:
                             loc_type = 'T1'
                             c_number = 1
@@ -110,7 +112,7 @@ class LocationModel(models.Model):
 
                     # 生成唯一编码
                     location_code = f"{loc_type}-L{layer}C{col:03d}-{c_number:02d}"
-                    print(f"生成库位:{location_code}-{row}-{col}-{layer}")
+                    # print(f"生成库位:{location_code}-{row}-{col}-{layer}")
                     
                     # 创建库位
                     cls.objects.update_or_create(
@@ -118,6 +120,8 @@ class LocationModel(models.Model):
                         row=row,
                         col=col,
                         layer=layer,
+                        c_number=c_number,
+                        shelf_type=loc_type,
                         defaults={
                             'location_code': location_code,
                             'location_type': loc_type,
@@ -133,38 +137,62 @@ class LocationModel(models.Model):
                             'coordinate': f"{row}-{col}-{layer}",
                             'is_active': True
                         },
-                        access_priority=c_number if loc_type == 'T1'or loc_type == 'T2'or loc_type == 'T4'or loc_type == 'T5' else 0
+                        access_priority=c_number 
                     )
-        print("✅ 库位生成成功!")           
+        # print("✅ 库位生成成功!")           
         for row in range(1, 18):       # 1-17行
             for col in range(19, 30):   # 19-29列
                 for layer in range(1, 4): # 1-3层
-                    # 判断货位类型(根据实际规划)
-                    if row < 2:
-                        loc_type = 'T1' 
+                     # 判断通道区
+                    if col in MAIN_AISLES or row in SUB_AISLES:
+                        loc_type = 'M1'
                         c_number = row
+                        # 通道
+                    
                         if col ==18 and row == 1:
                             loc_type = 'T1'
                             c_number = 1
                         if col ==29 and row == 1:
                             loc_type = 'T1'
                             c_number = 1
-                    elif row < 8:
-                        loc_type = 'T5'
-                        c_number = row-2
-                    elif row < 13:
-                        loc_type = 'T4'
-                        c_number = row-8
+                        if col ==29 and row == 14:
+                            loc_type = 'S4'
+                            c_number = 4
+                        if col ==29 and row == 15:
+                            loc_type = 'S4'
+                            c_number = 3
+                        if col ==29 and row == 16:
+                            loc_type = 'S4'
+                            c_number = 2
+                        if col ==29 and row == 17:
+                            loc_type = 'S4'
+                            c_number = 1
+                 
+               
+                    # 判断货位类型(根据实际规划)
+                    
                     else:
-                        loc_type = 'S4'
-                        c_number = 20-row
+                        # 判断货位类型(根据实际规划)
+                        if row < 2:
+                            loc_type = 'T1' 
+                            c_number = row
+                    
+                        elif row < 8:
+                            loc_type = 'T5'
+                            c_number = row-2
+                        elif row < 13:
+                            loc_type = 'T4'
+                            c_number = row-8
+                        else:
+                            loc_type = 'S4'
+                            c_number = 18-row
 
                     # 生成唯一编码
                     location_code = f"{loc_type}-L{layer}C{col:03d}-{c_number:02d}"
                     
                     # 生成唯一编码
                     location_code = f"{loc_type}-L{layer}C{col:03d}-{c_number:02d}"
-                    print(f"生成库位:{location_code}-{row}-{col}-{layer}")
+                    # print(f"生成库位:{location_code}-{row}-{col}-{layer}")
                     
                     # 创建库位
                     cls.objects.update_or_create(
@@ -172,6 +200,8 @@ class LocationModel(models.Model):
                         row=row,
                         col=col,
                         layer=layer,
+                        c_number=c_number,
+                        shelf_type=loc_type,
                         defaults={
                             'location_code': location_code,
                             'location_type': loc_type,
@@ -188,7 +218,7 @@ class LocationModel(models.Model):
                             'coordinate': f"{row}-{col}-{layer}",
                             'is_active': True
                         },
-                        access_priority=c_number if loc_type == 'T1'or loc_type == 'T2'or loc_type == 'T4'or loc_type == 'T5' else 0
+                        access_priority=c_number 
                     )
 
 
@@ -208,6 +238,7 @@ class LocationContainerLink(models.Model):
         ordering = ['-id']
         unique_together = ( 'location', 'container')  # 防止重复关联关系
 
+
 class DeviceModel(models.Model):
     location = models.ForeignKey(LocationModel, on_delete=models.CASCADE)
     device_id = models.CharField(max_length=255, verbose_name="Device ID")
@@ -223,8 +254,9 @@ class DeviceModel(models.Model):
         verbose_name = 'Device'
         verbose_name_plural = "Device"
         ordering = ['-id']
-        unique_together = ('device_id',)  # 防止重复设备ID
+        unique_together = ( 'location', 'device_id')  # 防止重复设备
 # 库位变更记录(历史追溯)
+
 class LocationChangeLog(models.Model):
     OPERATION_TYPES = (
         ('put', '上架'),

+ 24 - 0
bin/serializers.py

@@ -0,0 +1,24 @@
+from rest_framework import serializers
+
+from .models import DeviceModel,LocationModel,LocationContainerLink,LocationChangeLog
+
+
+class LocationSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = LocationModel
+        fields = '__all__'
+class LocationListSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = LocationModel
+        fields = '__all__'
+        read_only_fields = ['id']
+
+class LocationPostSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = LocationModel
+        fields = '__all__'
+        read_only_fields = ['id']
+
+
+
+

+ 3 - 0
bin/urls.py

@@ -2,6 +2,9 @@ from django.urls import path, re_path
 from . import views
 
 urlpatterns = [
+    path ('', views.locationViewSet.as_view({"get": "list"}), name='location_list'),
+   re_path(r'^(?P<pk>\d+)/$',  views.locationViewSet.as_view({"get": "retrieve", "put": "update"}), name='location_detail'),
+
     # path(r'management/', views.stockshelfViewSet.as_view({"get": "list", "post": "create"}), name="management"),
     # re_path(r'^management/(?P<pk>\d+)/$', views.stockshelfViewSet.as_view({'get': 'retrieve','put': 'update','patch': 'partial_update','delete': 'destroy'}), name="staff_1"),
 

+ 90 - 2
bin/views.py

@@ -1,3 +1,91 @@
-from django.shortcuts import render
+from rest_framework import viewsets
+from utils.page import MyPageNumberPagination
+from utils.datasolve import sumOfList, transportation_calculate
+from utils.md5 import Md5
+from rest_framework.filters import OrderingFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.response import Response
+from rest_framework.exceptions import APIException
+from django.utils import timezone
+
+from django.db import transaction
+import logging
+from rest_framework import status
+from .models import DeviceModel,LocationModel,LocationContainerLink,LocationChangeLog
+from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel
+
+
+from .filter import DeviceFilter,LocationFilter,LocationContainerLinkFilter,LocationChangeLogFilter
+from .serializers import LocationListSerializer,LocationPostSerializer
+# 以后添加模块时,只需要在这里添加即可
+from rest_framework.permissions import AllowAny
+
+logger = logging.getLogger(__name__)
+
+class locationViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+            list:
+            Response a data list(all)
+        create:
+            Create a data line(post)
+
+        delete:
+            Delete a data line(delete)
+
+    """
+    # authentication_classes = []  # 禁用所有认证类
+    # permission_classes = [AllowAny]  # 允许任意访问
+    
+    pagination_class = MyPageNumberPagination   
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = LocationFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return LocationModel.objects.filter()
+            else:
+                return LocationModel.objects.filter( id=id)
+        else:
+            return LocationModel.objects.none()
+    def get_serializer_class(self):
+        if self.action == 'list':
+            return LocationListSerializer
+        elif self.action == 'update':
+            return LocationPostSerializer
+        elif self.action =='retrieve':
+            return LocationListSerializer
+
+
+
+
+    def update(self, request, *args, **kwargs):
+        data = self.request.data
+        order_month = str(timezone.now().strftime('%Y%m'))
+        data['month'] = order_month
+        location_code = data.get('location_code')
+        # 处理库位对象
+        location_obj = LocationModel.objects.filter(location_code=location_code).first()
+        if location_obj:
+            data['id'] = location_obj.id
+            logger.info(f"库位 {location_code} 已存在")
+        else:
+            logger.info(f"库位 {location_code} 不存在,创建库位对象")
+            serializer_list = LocationPostSerializer(data=data)
+            serializer_list.is_valid(raise_exception=True)
+            serializer_list.save()
+            data['id'] = serializer_list.data.get('id')
+        return Response(data, status=status.HTTP_201_CREATED)
+
 
-# Create your views here.

BIN
container/__pycache__/models.cpython-38.pyc


BIN
db.sqlite3


+ 35 - 0
logs/error.log

@@ -2260,3 +2260,38 @@ Traceback (most recent call last):
 RuntimeError: You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to 192.168.18.69:8008/container/container_wcs/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.
 [2025-04-15 10:20:36,698][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
 [2025-04-15 10:20:59,725][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
+[2025-04-15 22:37:35,916][django.request.log_response():241] [ERROR] Internal Server Error: /bin/
+Traceback (most recent call last):
+  File "d:\language\python38\lib\site-packages\asgiref\sync.py", line 472, in thread_handler
+    raise exc_info[1]
+  File "d:\language\python38\lib\site-packages\django\core\handlers\exception.py", line 42, in inner
+    response = await get_response(request)
+  File "d:\language\python38\lib\site-packages\django\core\handlers\base.py", line 253, in _get_response_async
+    response = await wrapped_callback(
+  File "d:\language\python38\lib\site-packages\asgiref\sync.py", line 435, in __call__
+    ret = await asyncio.wait_for(future, timeout=None)
+  File "d:\language\python38\lib\asyncio\tasks.py", line 455, in wait_for
+    return await fut
+  File "d:\language\python38\lib\concurrent\futures\thread.py", line 57, in run
+    result = self.fn(*self.args, **self.kwargs)
+  File "d:\language\python38\lib\site-packages\asgiref\sync.py", line 476, in thread_handler
+    return func(*args, **kwargs)
+  File "d:\language\python38\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\language\python38\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 506, in dispatch
+    response = handler(request, *args, **kwargs)
+  File "d:\language\python38\lib\site-packages\rest_framework\mixins.py", line 42, in list
+    serializer = self.get_serializer(page, many=True)
+  File "d:\language\python38\lib\site-packages\rest_framework\generics.py", line 108, in get_serializer
+    serializer_class = self.get_serializer_class()
+  File "d:\language\python38\lib\site-packages\rest_framework\generics.py", line 122, in get_serializer_class
+    assert self.serializer_class is not None, (
+AssertionError: 'locationViewSet' should either include a `serializer_class` attribute, or override the `get_serializer_class()` method.

+ 38 - 0
logs/server.log

@@ -2486,3 +2486,41 @@ RuntimeError: You called this URL via POST, but the URL doesn't end in a slash a
 [2025-04-15 15:24:08,294][django.request.log_response():241] [WARNING] Not Found: /cyclecount/qtyrecorviewset/
 [2025-04-15 15:26:37,231][django.request.log_response():241] [WARNING] Not Found: /asn/list/
 [2025-04-15 15:34:53,059][django.request.log_response():241] [WARNING] Not Found: /cyclecount/qtyrecorviewset/
+[2025-04-15 22:37:35,916][django.request.log_response():241] [ERROR] Internal Server Error: /bin/
+Traceback (most recent call last):
+  File "d:\language\python38\lib\site-packages\asgiref\sync.py", line 472, in thread_handler
+    raise exc_info[1]
+  File "d:\language\python38\lib\site-packages\django\core\handlers\exception.py", line 42, in inner
+    response = await get_response(request)
+  File "d:\language\python38\lib\site-packages\django\core\handlers\base.py", line 253, in _get_response_async
+    response = await wrapped_callback(
+  File "d:\language\python38\lib\site-packages\asgiref\sync.py", line 435, in __call__
+    ret = await asyncio.wait_for(future, timeout=None)
+  File "d:\language\python38\lib\asyncio\tasks.py", line 455, in wait_for
+    return await fut
+  File "d:\language\python38\lib\concurrent\futures\thread.py", line 57, in run
+    result = self.fn(*self.args, **self.kwargs)
+  File "d:\language\python38\lib\site-packages\asgiref\sync.py", line 476, in thread_handler
+    return func(*args, **kwargs)
+  File "d:\language\python38\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\language\python38\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\language\python38\lib\site-packages\rest_framework\views.py", line 506, in dispatch
+    response = handler(request, *args, **kwargs)
+  File "d:\language\python38\lib\site-packages\rest_framework\mixins.py", line 42, in list
+    serializer = self.get_serializer(page, many=True)
+  File "d:\language\python38\lib\site-packages\rest_framework\generics.py", line 108, in get_serializer
+    serializer_class = self.get_serializer_class()
+  File "d:\language\python38\lib\site-packages\rest_framework\generics.py", line 122, in get_serializer_class
+    assert self.serializer_class is not None, (
+AssertionError: 'locationViewSet' should either include a `serializer_class` attribute, or override the `get_serializer_class()` method.
+[2025-04-15 22:54:15,305][django.request.log_response():241] [WARNING] Not Found: /bin./
+[2025-04-15 22:58:08,323][django.request.log_response():241] [WARNING] Not Found: /binset/
+[2025-04-15 22:59:59,529][django.request.log_response():241] [WARNING] Not Found: /bin/management/

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 472 - 732
templates/src/pages/stock/binset.vue


+ 494 - 0
templates/src/pages/stock/management copy.vue

@@ -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>

+ 16 - 15
templates/src/pages/stock/management.vue

@@ -80,7 +80,6 @@
                             >
                                 <!-- 确保没有子元素获得焦点 -->
                             </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 }}
@@ -92,12 +91,7 @@
                 </div>
             </div>
         </q-page>
-
-        
-    </div>
- 
-
-    
+    </div>    
 </template>
 
 <script>
@@ -110,7 +104,7 @@ export default {
     // 选项式 API 写法
     data() {
         return {
-            pathname: 'stock/management/',
+            pathname: 'bin/',
 
             warehouse_code: '',
             warehouse_name: '',
@@ -129,12 +123,19 @@ export default {
             goodsMatrix: [] ,   // 二维数据矩阵
    
             // 颜色配置
+             // 颜色配置
             binColors: {
-            elevator: 'rgba(255, 215, 0, 0.5)',  // 黄色半透明
-            storage: "#4CAF50" ,//绿色,  // 蓝色低透明度
-            occupied: '#32CD3280',  // 绿色带透明度(HEX 8位格式)
-            default: 'transparent' ,  // 完全透明
-            } ,
+            T1: 'rgba(255, 215, 0, 0.5)',  // 黄色半透明
+            T2: 'rgba(255, 165, 0, 0.5)',  // 黄色半透明
+            T4: 'rgba(255, 115, 0, 0.5)',  // 黄色半透明
+            T5: 'rgba(255, 65, 0, 0.5)',  // 黄色半透明
+            S4: 'rgba(255, 115, 0, 0.5)',  // 红色半透明
+            M1: 'rgba(255, 255, 0, 0.5)',  // 黄色半透明
+            E1: 'rgba(255, 255, 0, 0.5)',  // 黄色半透明
+            C1: 'rgba(255, 255, 0, 0.5)',  // 黄色半透明
+            default: 'rgba(255, 0.5)' // 白色半透明
+            
+             }   ,
             showInventoryDetails : false, // 显示库存详情
             select_Inventory: {
                 rowIndex : 0,
@@ -155,7 +156,7 @@ export default {
         },
         beforeDestroy() {
             //清空数据
-            this.goodsMap = {}
+            this.goodsMap = {} 
             this.goodsMatrix = []
     
         },
@@ -163,7 +164,7 @@ export default {
         shouldShowButton(row, col, layer) {
             const bin = this.goodsMap[`${row}-${col}-${layer}`]
             // 示例:仅显示storage类型的库位
-            return bin?.shelf_type === 'storage' 
+            return ['T1', 'T2', 'T4', 'T5', 'S4', 'M1', 'E1', 'C1'].includes(bin?.location_type) // 显示多种类型
             // return ['storage', 'occupied'].includes(bin?.shelf_type) // 显示多种类型
         },
         getList() {

+ 6 - 0
templates/src/router/routes.js

@@ -38,6 +38,12 @@ const routes = [{
         name: 'occupiedbin',
         component: () => import('pages/stock/occupiedbin.vue')
       },
+      {
+        path: 'binset',
+        name: 'binset',
+        component: () => import('pages/stock/binset.vue')
+      },
+      
 
       // {
       //   path: 'cyclecount',