Jelajahi Sumber

出库程序优化

flower_mr 2 minggu lalu
induk
melakukan
a20a741745

+ 2 - 0
bin/services.py

@@ -205,6 +205,8 @@ class AllocationService:
                 layer_cap = LocationQueries.get_group_capacity()
                 pressure = LocationQueries.get_current_pressure()
                 finish_task_sum = sum(LocationQueries.get_current_finish_task(container_code,batch_info))
+                if finish_task_sum == total:
+                    total += 1
                 solution, new_pressure = AllocationAlgorithm.generate_plan(total-finish_task_sum, layer_cap, pressure)
                 if not solution:
                     raise RuntimeError("无法生成有效分配方案")

+ 16 - 3
bound/filter.py

@@ -1,5 +1,16 @@
 from django_filters import FilterSet
-from .models import BoundListModel, BoundDetailModel,BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel
+from .models import BoundListModel, BoundDetailModel,BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel,MaterialStatistics
+
+class MaterialStatisticsFilter(FilterSet):
+    class Meta:
+        model = MaterialStatistics
+        fields = {
+            "goods_code": ['exact', 'icontains'],
+            "goods_desc": ['exact', 'icontains'],
+            "goods_std": ['exact', 'icontains'],
+            "goods_unit": ['exact', 'icontains'],
+            "total_quantity": ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+        }
 
 
 class BoundListFilter(FilterSet):
@@ -85,17 +96,19 @@ class BoundBatchFilter(FilterSet):
         fields = {
             "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
             'bound_number': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
+            'sourced_number': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'bound_month': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
 
             
             'warehouse_code': ['icontains', 'exact'],
             'warehouse_name': ['icontains','exact'],
             
-                'goods_code': ['icontains', 'exact'],
-                'goods_desc': ['icontains', 'exact'],
+            'goods_code': ['icontains', 'exact'],
+            'goods_desc': ['icontains', 'exact'],
             'goods_std': ['icontains', 'exact'],
             'goods_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'goods_in_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
+            'goods_in_location_qty' : ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'goods_out_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'status': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'goods_weight': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],

+ 30 - 0
bound/migrations/0013_materialstatistics.py

@@ -0,0 +1,30 @@
+# Generated by Django 4.1.2 on 2025-05-19 19:13
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0012_boundbatchmodel_goods_package'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MaterialStatistics',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('goods_code', models.CharField(max_length=255, unique=True, verbose_name='商品编码')),
+                ('goods_desc', models.CharField(max_length=255, verbose_name='商品描述')),
+                ('goods_std', models.CharField(blank=True, default='待填写', max_length=255, null=True, verbose_name='商品标准')),
+                ('goods_unit', models.CharField(default='待填写', max_length=255, verbose_name='商品单位')),
+                ('total_quantity', models.BigIntegerField(default=0, verbose_name='总数量')),
+            ],
+            options={
+                'verbose_name': '物料统计',
+                'verbose_name_plural': '物料统计',
+                'db_table': 'materialstatistics',
+                'ordering': ['goods_code'],
+            },
+        ),
+    ]

+ 28 - 0
bound/migrations/0014_alter_boundbatchmodel_goods_in_location_qty_and_more.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.1.2 on 2025-05-19 19:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0013_materialstatistics'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='boundbatchmodel',
+            name='goods_in_location_qty',
+            field=models.BigIntegerField(default=0, verbose_name='库位入库数量'),
+        ),
+        migrations.AlterField(
+            model_name='boundbatchmodel',
+            name='goods_in_qty',
+            field=models.BigIntegerField(default=0, verbose_name='组盘入库数量'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='total_quantity',
+            field=models.BigIntegerField(default=0, verbose_name='计划数量'),
+        ),
+    ]

+ 18 - 0
bound/migrations/0015_rename_goods_out_location_qty_boundbatchmodel_goods_actual_qty.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-05-19 19:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0014_alter_boundbatchmodel_goods_in_location_qty_and_more'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='boundbatchmodel',
+            old_name='goods_out_location_qty',
+            new_name='goods_actual_qty',
+        ),
+    ]

+ 63 - 4
bound/models.py

@@ -1,6 +1,8 @@
 from django.db import models
 from erp.models import InboundBill, MaterialDetail, OutboundBill,OutMaterialDetail
-
+from django.db.models import Sum
+from django.db.models.signals import post_save, post_delete
+from django.dispatch import receiver
 
 class BoundListModel(models.Model):
     STATUS =( 
@@ -67,10 +69,10 @@ class BoundBatchModel(models.Model):
     goods_unit = models.CharField(default='待填写', max_length=255, verbose_name="商品单位")
     goods_qty = models.BigIntegerField(default=0, verbose_name="商品数量")
     goods_package = models.CharField(default='待填写', max_length=255, verbose_name="商品包装")
-    goods_in_qty = models.BigIntegerField(default=0, verbose_name="入库数量")
-    goods_in_location_qty = models.BigIntegerField(default=0, verbose_name="库位入库数量")
+    goods_in_qty = models.BigIntegerField(default=0, verbose_name="组盘入库数量")
+    goods_in_location_qty = models.BigIntegerField(default=0, verbose_name="库位入库数量")
     goods_out_qty = models.BigIntegerField(default=0, verbose_name="出库数量")
-    goods_out_location_qty = models.BigIntegerField(default=0, verbose_name="库位上出库数量")
+    goods_actual_qty = models.BigIntegerField(default=0, verbose_name="库位上出库数量")
 
     status = models.IntegerField(choices=CONTAINER_STATUS, default=0, verbose_name='批次状态')
     container_number = models.IntegerField( default=0, verbose_name="托盘数目")
@@ -91,6 +93,63 @@ class BoundBatchModel(models.Model):
         verbose_name_plural = "Bound Batch"
         ordering = ['-id']
 
+    def __str__(self):
+        return f"{self.bound_number} - {self.goods_code} - {self.goods_desc}"
+    
+    def save(self, *args, **kwargs):
+
+        # self.goods_actual_qty = self.goods_in_qty - self.goods_out_qty
+        self.goods_actual_qty = self.goods_in_location_qty
+        super().save(*args, **kwargs)
+
+class MaterialStatistics(models.Model):
+    goods_code = models.CharField(max_length=255, verbose_name="商品编码", unique=True)
+    goods_desc = models.CharField(max_length=255, verbose_name="商品描述")
+    goods_std = models.CharField(default='待填写', max_length=255, verbose_name="商品标准", blank=True, null=True)
+    goods_unit = models.CharField(default='待填写', max_length=255, verbose_name="商品单位")
+    total_quantity = models.BigIntegerField(default=0, verbose_name="计划数量")
+
+    def __str__(self):
+        return f"{self.goods_code} - {self.goods_desc}"
+
+    @property
+    def bound_batches(self):
+        return BoundBatchModel.objects.filter(goods_code=self.goods_code).exclude(goods_actual_qty=0).order_by('bound_batch_order')
+
+    class Meta:
+        db_table = 'materialstatistics'
+        verbose_name = '物料统计'
+        verbose_name_plural = "物料统计"
+        ordering = ['goods_code']
+
+@receiver([post_save, post_delete], sender=BoundBatchModel)
+def update_material_statistics(sender, instance, **kwargs):
+    goods_code = instance.goods_code
+    stats, created = MaterialStatistics.objects.get_or_create(
+        goods_code=goods_code,
+        defaults={
+            'goods_desc': instance.goods_desc,
+            'goods_std': instance.goods_std or '待填写',
+            'goods_unit': instance.goods_unit or '待填写',
+        }
+    )
+    
+    # 更新物料信息为最新批次的信息(可选)
+    stats.goods_desc = instance.goods_desc
+    if instance.goods_std and instance.goods_std != '待填写':
+        stats.goods_std = instance.goods_std
+    if instance.goods_unit and instance.goods_unit != '待填写':
+        stats.goods_unit = instance.goods_unit
+    stats.save()
+
+    # 计算总数量
+    total = BoundBatchModel.objects.filter(goods_code=goods_code).aggregate(
+        total=Sum('goods_in_location_qty')
+    )['total'] or 0
+    stats.total_quantity = total
+    stats.save()
+
+
 class OutBatchModel(models.Model):
     CONTAINER_STATUS = (
         (0, '申请'),

+ 24 - 1
bound/serializers.py

@@ -1,8 +1,9 @@
 from rest_framework import serializers
-from .models import BoundListModel, BoundDetailModel, BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel
+from .models import BoundListModel, BoundDetailModel, BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel,MaterialStatistics
 from utils import datasolve
 
 
+    
 class BoundListGetSerializer(serializers.ModelSerializer):
     # 定义主单列表的序列化器,用于获取操作,字段只读
     bound_month = serializers.CharField(read_only=True, required=False)
@@ -336,3 +337,25 @@ class OutBoundDetailPostSerializer(serializers.ModelSerializer):
             }
         }
 
+class MaterialStatisticsSerializer(serializers.ModelSerializer):
+    # 定义物料统计的序列化器,用于获取操作,字段只读
+
+    class Meta:
+        # 指定模型和排除字段
+        model = MaterialStatistics
+        fields = '__all__'
+        read_only_fields = ['id']
+    
+    
+class MaterialStatisticsSerializer_items(serializers.ModelSerializer):
+    # 定义物料统计的序列化器,用于获取操作,字段只读
+    batch_items = serializers.SerializerMethodField()
+    class Meta:
+        # 指定模型和排除字段
+        model = MaterialStatistics
+        exclude = ['id', 'goods_code', 'goods_desc', 'goods_std', 'goods_unit','total_quantity']
+        read_only_fields = ['id']
+    
+    def get_batch_items(self, obj):
+        batches = obj.bound_batches
+        return BoundBatchGetSerializer(batches, many=True).data

+ 6 - 0
bound/urls.py

@@ -31,6 +31,12 @@ re_path(r'^outdetail/(?P<pk>\d+)/$', views.OutBoundDetailViewSet.as_view({
 path(r'batch/', views.BoundBatchViewSet.as_view({"get": "list", "post": "create" }), name="boundbatch"), 
 path(r'batch/container/', views.BatchContainerAPIView.as_view(), name="batchcontainer"), 
 
+path(r'batch/count/', views.MaterialStatisticsViewSet.as_view({"get": "list" }), name="materialstatistics"), 
+re_path(r'^batch/count/(?P<pk>\d+)/$', views.MaterialStatisticsViewSet.as_view({
+    'get': 'retrieve',
+
+}), name="materialstatistics_1"),
+
 re_path(r'^batch/(?P<pk>\d+)/$', views.BoundBatchViewSet.as_view({
     'get': 'retrieve',
     'put': 'update',

+ 53 - 3
bound/views.py

@@ -9,19 +9,69 @@ from rest_framework.response import Response
 from rest_framework.exceptions import APIException
 from django.utils import timezone
 
-from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchLogModel, OutBatchModel,OutBoundDetailModel
+from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchLogModel, OutBatchModel,OutBoundDetailModel,MaterialStatistics
 # from .files import FileListRenderCN, FileDetailRenderCN
 
 from .serializers import BoundListGetSerializer,BoundListPostSerializer,BoundBatchGetSerializer,BoundBatchPostSerializer,BoundDetailGetSerializer,BoundDetailPostSerializer
 from .serializers import OutBoundDetailGetSerializer,OutBoundDetailPostSerializer,OutBatchGetSerializer,OutBatchPostSerializer,BatchLogGetSerializer
+from .serializers import MaterialStatisticsSerializer,MaterialStatisticsSerializer_items
 from .filter import BoundListFilter, BoundDetailFilter,BoundBatchFilter
 from .filter import OutBatchFilter,OutBoundDetailFilter,BatchlogFilter
+from .filter import MaterialStatisticsFilter
 # 以后添加模块检验
 from warehouse.models import ListModel as warehouse
 from staff.models import ListModel as staff
 from rest_framework.permissions import AllowAny
 from rest_framework.views import APIView
 
+class MaterialStatisticsViewSet(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 = MaterialStatisticsFilter
+
+    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 MaterialStatistics.objects.filter()
+            else:
+                return MaterialStatistics.objects.filter(id=id)
+        else:
+            return MaterialStatistics.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list' ]:
+            return MaterialStatisticsSerializer
+        elif self.action in ['retrieve']:
+            return MaterialStatisticsSerializer_items
+        else:
+            return self.http_method_not_allowed(request=self.request)
+        
+    
+
 class BoundListViewSet(viewsets.ModelViewSet):
     """
         retrieve:
@@ -181,8 +231,8 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
                     data['bound_batch_order'] = int(bound_last_code.split('-')[-1])+1
                     data['bound_number'] = data['goods_code'] + '-' + str(int(bound_last_code.split('-')[-1])+1)
                 else:
-                    data['bound_batch_order'] = int(order_day.split('-')[-1])*100 +1
-                    data['bound_number'] = data['goods_code'] + order_day + '01'
+                    data['bound_batch_order'] = int(order_day.split('-')[-1])*1000 +1
+                    data['bound_number'] = data['goods_code'] + order_day + '001'
             else:
                 data['bound_number'] = data['goods_code'] + '-' + str(data['bound_batch_order'])
             serializer = self.get_serializer(data=data)

+ 17 - 0
container/migrations/0018_alter_containerwcsmodel_options.py

@@ -0,0 +1,17 @@
+# Generated by Django 4.1.2 on 2025-05-18 11:22
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0017_alter_containerdetailmodel_goods_class'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='containerwcsmodel',
+            options={'ordering': ['-create_time'], 'verbose_name': 'ContainerWCS', 'verbose_name_plural': 'ContainerWCS'},
+        ),
+    ]

+ 1 - 0
container/views.py

@@ -356,6 +356,7 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
                         'data': current_task.to_dict()
                     }
                 else:
+                    # todo: 这里的入库操作记录里面的记录的数量不对
                     location_min_value,allocation_target_location, batch_info = AllocationService.allocate(container, current_location)
                     batch_id = batch_info['number']
                     if batch_info['class'] == 2:

+ 101 - 0
logs/error.log

@@ -8106,3 +8106,104 @@ Traceback (most recent call last):
     openid = request.auth.openid
 AttributeError: 'NoneType' object has no attribute 'openid'
 [2025-05-18 01:57:36,774][django.server.log_message():187] [ERROR] "GET /container/wcs_task/ HTTP/1.1" 500 119764
+[2025-05-18 21:19:26,677][django.request.log_response():241] [ERROR] Internal Server Error: /bound/outdetail/
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
+    response = get_response(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
+    response = handler(request, *args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\bound\views.py", line 380, in create
+    data['bound_batch_number'] = OutBatchModel.objects.get(id=data['bound_batch']).batch_number.id
+KeyError: 'bound_batch'
+[2025-05-18 21:19:26,680][django.server.log_message():187] [ERROR] "POST /bound/outdetail/ HTTP/1.1" 500 112376
+[2025-05-19 18:30:16,427][django.request.log_response():241] [ERROR] Internal Server Error: /bound/outdetail/
+Traceback (most recent call last):
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\asgiref\sync.py", line 472, in thread_handler
+    raise exc_info[1]
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 42, in inner
+    response = await get_response(request)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 253, in _get_response_async
+    response = await wrapped_callback(
+  File "D:\Document\code\vue\greater_wms\.venv\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:\Document\code\vue\greater_wms\.venv\lib\site-packages\asgiref\sync.py", line 476, in thread_handler
+    return func(*args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
+    response = handler(request, *args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.\bound\views.py", line 380, in create
+    data['bound_batch_number'] = OutBatchModel.objects.get(id=data['bound_batch']).batch_number.id
+KeyError: 'bound_batch'
+[2025-05-19 18:41:41,549][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
+[2025-05-19 20:27:13,786][django.request.log_response():241] [ERROR] Internal Server Error: /bound/batch/count/1/
+Traceback (most recent call last):
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\asgiref\sync.py", line 472, in thread_handler
+    raise exc_info[1]
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 42, in inner
+    response = await get_response(request)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 253, in _get_response_async
+    response = await wrapped_callback(
+  File "D:\Document\code\vue\greater_wms\.venv\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:\Document\code\vue\greater_wms\.venv\lib\site-packages\asgiref\sync.py", line 476, in thread_handler
+    return func(*args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
+    response = handler(request, *args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\mixins.py", line 56, in retrieve
+    return Response(serializer.data)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 555, in data
+    ret = super().data
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 253, in data
+    self._data = self.to_representation(self.instance)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 507, in to_representation
+    for field in fields:
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 368, in _readable_fields
+    for field in self.fields.values():
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\utils\functional.py", line 57, in __get__
+    res = instance.__dict__[self.name] = self.func(instance)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 356, in fields
+    for key, value in self.get_fields().items():
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 1052, in get_fields
+    field_names = self.get_field_names(declared_fields, info)
+  File "D:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\serializers.py", line 1106, in get_field_names
+    raise TypeError(
+TypeError: The `fields` option must be a list or tuple or "__all__". Got str.

File diff ditekan karena terlalu besar
+ 1035 - 0
logs/server.log


+ 1 - 0
templates/package.json

@@ -38,6 +38,7 @@
     "xmldom": "^0.6.0",
     "screenfull": "4.2.0",
     "yargs-parser": "^20.2.9",
+    "vuedraggable": "^2.24.3",
     "jsbarcode": "3.9.0"
   },
   "devDependencies": {

+ 116 - 0
templates/src/components/CustomDateInput.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="row q-gutter-xs">
+    <!-- 年 -->
+    <q-select
+      v-model="year"
+      :options="yearOptions"
+      label="年"
+      dense
+      emit-value
+      @update:model-value="handleChange"
+    />
+
+    <!-- 月 -->
+    <q-select
+      v-model="month"
+      :options="monthOptions"
+      label="月"
+      dense
+      emit-value
+      :disable="!year"
+      @update:model-value="handleChange"
+    />
+
+    <!-- 日 -->
+    <q-select
+      v-model="day"
+      :options="dayOptions"
+      label="日"
+      dense
+      emit-value
+      :disable="!month"
+      @update:model-value="handleChange"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  props: ['modelValue'],
+  emits: ['update:modelValue'],
+
+  data() {
+    return {
+      year: null,
+      month: null,
+      day: null
+    }
+  },
+
+  computed: {
+    yearOptions() {
+      const currentYear = new Date().getFullYear()
+      return Array.from({ length: 5 }, (_, i) => currentYear - i)
+    },
+
+    monthOptions() {
+      return Array.from({ length: 12 }, (_, i) => ({
+        label: `${i + 1}月`,
+        value: i + 1
+      }))
+    },
+
+    dayOptions() {
+      if (!this.year || !this.month) return []
+      const days = new Date(this.year, this.month, 0).getDate()
+      return Array.from({ length: days }, (_, i) => i + 1)
+    }
+  },
+
+  watch: {
+    modelValue: {
+      immediate: true,
+      handler(val) {
+        if (val) {
+          // 增强格式容错
+          const [y, m, d] = val.split('-').map(Number)
+          this.year = y || null
+          this.month = m || null
+          this.day = d || null
+        } else {
+          this.clear()
+        }
+      }
+    }
+  },
+
+  methods: {
+    handleChange() {
+      let dateStr = null
+      
+      // 允许部分选择
+      if (this.year) {
+        dateStr = `${this.year}`
+        if (this.month) {
+          dateStr += `-${this.pad(this.month)}`
+          if (this.day) {
+            dateStr += `-${this.pad(this.day)}`
+          }
+        }
+      }
+
+      this.$emit('update:modelValue', dateStr)
+    },
+
+    pad(num) {
+      return num.toString().padStart(2, '0')
+    },
+
+    clear() {
+      this.year = null
+      this.month = null
+      this.day = null
+    }
+  }
+}
+</script>

+ 181 - 195
templates/src/components/goodscard.vue

@@ -1,6 +1,5 @@
 <template>
     <div  :style="{ backgroundColor: bgColor }">
- 
 
         <q-dialog v-model="storage_dialog" transition-show="jump-down" transition-hide="jump-up" @show=prepareDialog()>
             <q-card style="min-width: 600px; ">
@@ -27,14 +26,14 @@
                     <q-tab-panel name="tab1" style="height:300px">
                         <div class="row q-gutter-x-md">
                             <div class="col column q-gutter-y-md">
-                            
+
                                 <q-input dense outlined square v-model="storage_form.goods_code"
                                     :label="$t('stock.shelf.goods_code')" :readonly="onlyread"
                                     :rules="[val => val?.length > 0 || error1]" />
-                    
+
                             </div>
                             <div class="col column q-gutter-y-md">
-  
+
                                 <q-input dense outlined square v-model="storage_form.goods_name"
                                     :label="$t('stock.shelf.goods_name')" :readonly="onlyread"
                                     :rules="[val => val?.length > 0 || error1]" />
@@ -78,8 +77,6 @@
                                     :label="$t('stock.shelf.goods_unit')" :readonly="onlyread"
                                     :rules="[val => val?.length > 0 || error1]" />
 
-
-
                             </div>
                         </div>
                     </q-tab-panel>
@@ -224,214 +221,204 @@
 <script>
 
 
-import { LocalStorage } from 'quasar'
-import { getauth, postauth, putauth, deleteauth, getfile } from 'boot/axios_request'
+import { getauth } from 'boot/axios_request'
 
+export default {
+  props: {
+
+    rowIndex: Number,
+    colIndex: Number,
+    layerIndex: Number,
+    selectedShelfType: String,
+    goodsData: Object
+
+  },
+  data () {
+    return {
+      pathname: 'bin/',
+      pathnamecontainer: 'container/detail/',
+      container_id: 123456,
+      results: [],
+      storage_form: {
+        shelf_name: '',
+        shelf_department: '',
+        shelf_warehouse: '',
+        shelf_status: '',
+        goods_code: '',
+        goods_name: '',
+        goods_std: '',
+        goods_desc: '',
+        goods_qty: '',
+        goods_unit: '',
+        goods_price: '',
+        goods_batch: '',
+        goods_notes: '',
+        goods_in: '',
+        goods_out: ''
+      },
+      elevator_form: {
+        ip: '',
+        port: 8080,
+        // username: 'admin',
+        // password: '123456',
+        status: '未连接'
+      },
+      conveyor_form: {
+        ip: '',
+        port: 8080,
+        // destination: 'A010203',
+        status: '未连接'
+      },
+      showInventoryDetails: false,
+      inventoryDetails: [
+        {
+          id: 1,
+          batch: '',
+          quantity: 0,
+          location: ''
 
+        },
+        {
+          id: 2,
+          batch: '',
+          quantity: 0,
+          location: ''
 
-export default {
-    props: {
+        }
 
-        rowIndex: Number,
-        colIndex: Number,
-        layerIndex: Number,
-        selectedShelfType: String,
-        goodsData: Object,
+      ],
+      inventoryColumns: [
+        { name: 'batch', label: '批次', field: 'batch', align: 'left' },
+        { name: 'quantity', label: '数量', field: 'quantity', align: 'right' },
+        { name: 'location', label: '位置', field: 'location', align: 'left' }
+
+      ],
+      user_id: '',
+      auth_id: '',
+      onlyread: true,
+      clickedinput: false,
+      storage_dialog: false,
+      elevator_dialog: false,
+      conveyor_dialog: false,
+      bgColor: 'transparent',
+
+      shelf_type: { shelf_type: 'undefined' },
+      ip_change: { ip_address: '192.168.1.100', port: 8080, status: 'offline' },
+      goods_change: { shelf_status: '未满', goods_batch: '20230701-001', goods_code: 'A010203', goods_name: '存货货物', goods_std: '规格型号', goods_desc: '存货描述', goods_qty: 123456, goods_unit: 'kg', goods_price: 123456, goods_notes: '备注', goods_in: 123456, goods_out: 123456 },
+
+      error1: this.$t('stock.shelf.error1'),
+      shelfLocal: '',
+      activeTab: 'tab2'
+    }
+  },
+  created () {
+    this.shelfLocal = this.selectedShelfType
+    this.handleclick()
+  },
 
+  methods: {
 
-    },
-    data() {
-        return {
-            pathname: 'bin/',
-            pathnamecontainer: 'container/detail/',
-            container_id: 123456,
-            results: [],
-            storage_form: {
-                shelf_name: '',
-                shelf_department: '',
-                shelf_warehouse: '',
-                shelf_status: '',
-                goods_code: '',
-                goods_name: '',
-                goods_std: '',
-                goods_desc: '',
-                goods_qty: '',
-                goods_unit: '',
-                goods_price: '',
-                goods_batch: '',
-                goods_notes: '',
-                goods_in: '',
-                goods_out: '',
-            },
-            elevator_form: {
-                ip: '',
-                port: 8080,
-                // username: 'admin',
-                // password: '123456',
-                status: '未连接',
-            },
-            conveyor_form: {
-                ip: '',
-                port: 8080,
-                // destination: 'A010203',
-                status: '未连接',
-            },
-            showInventoryDetails: false,
-            inventoryDetails: [
-                {
-                    id: 1,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-
-                },
-                {
-                    id: 2,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-
-                }
-
-            ],
-            inventoryColumns: [
-                { name: 'batch', label: '批次', field: 'batch', align: 'left' },
-                { name: 'quantity', label: '数量', field: 'quantity', align: 'right' },
-                { name: 'location', label: '位置', field: 'location', align: 'left' },
-
-            ],
-            user_id: "",
-            auth_id: "",
-            onlyread: true,
-            clickedinput: false,
-            storage_dialog: false,
-            elevator_dialog: false,
-            conveyor_dialog: false,
-            bgColor: "transparent",
-
-            shelf_type: { shelf_type: 'undefined' },
-            ip_change: { ip_address: '192.168.1.100', port: 8080, status: 'offline' },
-            goods_change: { shelf_status: '未满', goods_batch: '20230701-001', goods_code: 'A010203', goods_name: '存货货物', goods_std: '规格型号', goods_desc: '存货描述', goods_qty: 123456, goods_unit: 'kg', goods_price: 123456,  goods_notes: '备注', goods_in: 123456, goods_out: 123456 },
-
-            error1: this.$t('stock.shelf.error1'),
-            shelfLocal: "",
-            activeTab: 'tab2'
-        }
-    },
-    created() {
-        this.shelfLocal = this.selectedShelfType;
-        this.handleclick()
+    prepareDialog () {
+      this.onlyread = true
     },
 
-    methods: {
+    handleclick () {
+      this.shelfLocal = this.selectedShelfType
 
-        prepareDialog() {
-            this.onlyread = true;
-        },
+      if (this.shelfLocal === 'undefined') { this.clickedinput = true, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = false }
+      if (this.shelfLocal === 'storage') { this.getList(), this.clickedinput = false, this.storage_dialog = true, this.elevator_dialog = false, this.conveyor_dialog = false }
+      if (this.shelfLocal === 'elevator') { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = true, this.conveyor_dialog = false }
+      if (this.shelfLocal === 'conveyor') { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = true }
+    },
 
-                                               
-        handleclick() {
-            this.shelfLocal = this.selectedShelfType;
-            
-            if (this.shelfLocal == "undefined") { this.clickedinput = true, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = false }
-            if (this.shelfLocal == "storage") { this.getList(), this.clickedinput = false, this.storage_dialog = true, this.elevator_dialog = false, this.conveyor_dialog = false }
-            if (this.shelfLocal == "elevator") { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = true, this.conveyor_dialog = false }
-            if (this.shelfLocal == "conveyor") { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = true }
+    getList () {
+      var _this = this
+      _this.storage_form = {
+        shelf_name: '',
+        shelf_department: '',
+        shelf_warehouse: '',
+        shelf_status: '',
+        goods_code: '',
+        goods_name: '',
+        goods_std: '',
+        goods_desc: '',
+        goods_qty: 0,
+        goods_unit: '',
+        goods_price: 0,
+        goods_batch: '',
+        goods_notes: 0,
+        goods_in: 0,
+        goods_out: 0
+      }
+      _this.inventoryDetails = [
+        {
+          id: 1,
+          batch: '',
+          quantity: 0,
+          location: ''
 
         },
+        {
+          id: 2,
+          batch: '',
+          quantity: 0,
+          location: ''
+        }
 
-        getList() {
-            var _this = this;
-            _this.storage_form = {
-                shelf_name: '',
-                shelf_department: '',
-                shelf_warehouse: '',
-                shelf_status: '',
-                goods_code: '',
-                goods_name: '',
-                goods_std: '',
-                goods_desc: '',
-                goods_qty: 0,
-                goods_unit: '',
-                goods_price: 0,
-                goods_batch: '',
-                goods_notes: 0,
-                goods_in: 0,
-                goods_out: 0,
+      ]
+      getauth(_this.pathname + _this.goodsData.id + '/')
+        .then(res1 => {
+          console.log(res1)
+          console.log(res1.current_containers)
+          if (res1.current_containers.length == 0) {
+            console.log('当前托盘ID为空')
+            _this.$q.notify({
+              message: '当前库位为空',
+              icon: 'info',
+              color: 'info'
+            })
+            return
+          }
+          _this.container_id = res1.current_containers[0].id
+          console.log('当前托盘ID', _this.container_id)
+          console.log('当前托盘ID长度', res1.current_containers.length)
+
+          getauth(_this.pathnamecontainer + '?container=' + _this.container_id)
+            .then(res => {
+              var results = res.results[0]
+              this.storage_form.goods_batch = results.batch.bound_number
+              this.storage_form.goods_code = results.batch.goods_code
+              this.storage_form.goods_name = results.batch.goods_desc
+              this.storage_form.goods_std = results.batch.goods_std
+              this.storage_form.goods_desc = results.batch.goods_desc
+              this.storage_form.goods_qty = results.batch.goods_qty
+              this.storage_form.goods_unit = results.batch.goods_unit
+              this.storage_form.goods_price = results.batch.goods_price
+              this.storage_form.goods_notes = results.goods_qty
+              this.storage_form.goods_in = results.batch.goods_in
+              this.storage_form.goods_out = results.batch.goods_out
+              this.storage_form.shelf_name = results.shelf_name
+              this.storage_form.shelf_department = results.shelf_department
+              this.storage_form.shelf_warehouse = results.warehouse_name
+              this.storage_form.shelf_status = results.status
             }
-            _this.inventoryDetails = [
-                {
-                    id: 1,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-
-                },
-                {
-                    id: 2,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-                }
-
-            ]
-            getauth(_this.pathname + _this.goodsData.id + '/')
-                .then(res1 => {
-                    console.log(res1)
-                    console.log(res1.current_containers)
-                    if(res1.current_containers.length == 0)
-                    {
-                        console.log("当前托盘ID为空")
-                        _this.$q.notify({
-                            message: "当前库位为空",
-                            icon: 'info',
-                            color: 'info'
-                        });
-                        return
-                    }
-                    _this.container_id = res1.current_containers[0].id
-                    console.log("当前托盘ID",_this.container_id)
-                    console.log("当前托盘ID长度",res1.current_containers.length)
-               
-                    getauth(_this.pathnamecontainer + '?container='+_this.container_id)
-                    .then(res => {
-                        var results = res.results[0]
-                        this.storage_form.goods_batch = results.batch.bound_number
-                        this.storage_form.goods_code = results.batch.goods_code
-                        this.storage_form.goods_name = results.batch.goods_desc
-                        this.storage_form.goods_std = results.batch.goods_std
-                        this.storage_form.goods_desc = results.batch.goods_desc
-                        this.storage_form.goods_qty = results.batch.goods_qty
-                        this.storage_form.goods_unit = results.batch.goods_unit
-                        this.storage_form.goods_price = results.batch.goods_price
-                        this.storage_form.goods_notes = results.goods_qty
-                        this.storage_form.goods_in = results.batch.goods_in
-                        this.storage_form.goods_out = results.batch.goods_out
-                        this.storage_form.shelf_name = results.shelf_name
-                        this.storage_form.shelf_department = results.shelf_department
-                        this.storage_form.shelf_warehouse = results.warehouse_name
-                        this.storage_form.shelf_status = results.status
-                    }
-
-
-                ) 
-            }).catch(err => {
-                    _this.$q.notify({
-                        message: err.detail,
-                        icon: 'close',
-                        color: 'negative'
-                    });
-                });
-        },
+
+            )
+        }).catch(err => {
+          _this.$q.notify({
+            message: err.detail,
+            icon: 'close',
+            color: 'negative'
+          })
+        })
     }
+  }
 }
 
 </script>
 
 <style scoped>
 
-
-
 :deep(.q-field__label) {
 
     margin-top: 8px;
@@ -439,11 +426,10 @@ export default {
 
 }
 
-
 :deep(.q-field__control-container) {
 
     padding-left: 50px;
     margin-top: -5px;
 
 }
-</style>
+</style>

+ 2 - 2
templates/src/pages/inbound/asn.vue

@@ -657,7 +657,7 @@
             outlined
             square
             v-model.number="newBatchFormData.bound_batch_order"
-            :label="'批号——年月第几批(20250409)'"
+            :label="'批号——年月第几批(202504009)'"
             class="centered-input"
             type="number"
             :rules="[(val) => (val && val > 0) || error1]"
@@ -1668,7 +1668,7 @@ export default {
     batch_number_batch: function (val) {
       console.log(val)
       this.newBatchFormData.bound_batch_order =
-        this.batch_number_year * 1000 + this.batch_number_month * 10 + val
+        this.batch_number_year * 10000 + this.batch_number_month * 100 + val
     },
     createDate1 (val) {
       if (val) {

+ 5 - 4
templates/src/pages/inbound/inbound.vue

@@ -9,10 +9,8 @@
         <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="asn" :label="$t('inbound.asn')" icon="img:statics/inbound/asn.png" :to="{ name: 'asn' }" exact/>
         </transition>
-        <!-- <transition appear enter-active-class="animated zoomIn">
-          <q-route-tab name="predeliverystock" :label="$t('inbound.predeliverystock')"  icon="img:statics/inbound/polist.png" :to="{ name: 'predeliverystock' }" exact/>
-        </transition>
-        <transition appear enter-active-class="animated zoomIn">
+
+        <!--<transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="preloadstock" :label="$t('inbound.preloadstock')" icon="img:statics/inbound/preloadstock.png" :to="{ name: 'preloadstock' }" exact/>
         </transition>
         <transition appear enter-active-class="animated zoomIn">
@@ -21,6 +19,9 @@
         <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="sortstock" :label="$t('inbound.sortstock')" icon="img:statics/inbound/sortstock.png" :to="{ name: 'sortstock' }" exact/>
         </transition>
+        <transition appear enter-active-class="animated zoomIn">
+          <q-route-tab name="predeliverystock" :label="'分拣统计'"  icon="img:statics/inbound/polist.png" :to="{ name: 'predeliverystock' }" exact/>
+        </transition>
         <!-- <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="shortage" :label="$t('inbound.shortage')" icon="img:statics/inbound/shortage.png" :to="{ name: 'shortage' }" exact/>
         </transition>

+ 383 - 182
templates/src/pages/inbound/predeliverystock.vue

@@ -1,13 +1,12 @@
 <template>
-    <div>
-      <transition appear enter-active-class="animated fadeIn">
+  <div>
+    <transition appear enter-active-class="animated fadeIn">
       <q-table
-        class="my-sticky-header-table shadow-24"
+        class="my-sticky-header-column-table shadow-24"
         :data="table_list"
         row-key="id"
         :separator="separator"
         :loading="loading"
-        :filter="filter"
         :columns="columns"
         hide-bottom
         :pagination.sync="pagination"
@@ -17,116 +16,197 @@
         flat
         bordered
       >
-         <template v-slot:top>
-           <q-btn-group push>
-             <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-btn-group>
-           <q-space />
-           <q-input outlined rounded dense debounce="300" color="primary" v-model="filter" :placeholder="$t('search')" @input="getSearchList()" @keyup.enter="getSearchList()">
-             <template v-slot:append>
-               <q-icon name="search" @click="getSearchList()"/>
-             </template>
-           </q-input>
-         </template>
-         <template v-slot:body="props">
-           <q-tr :props="props">
-               <q-td key="asn_code" :props="props">
-                 {{ props.row.asn_code }}
-               </q-td>
-               <q-td key="goods_code" :props="props">
-                 {{ props.row.goods_code }}
-               </q-td>
-               <q-td key="goods_desc" :props="props">
-                 {{ props.row.goods_desc }}
-               </q-td>
-               <q-td key="goods_qty" :props="props">
-                 {{ props.row.goods_qty }}
-               </q-td>
-               <q-td key="goods_weight" :props="props">
-                 {{ props.row.goods_weight }}
-               </q-td>
-             <q-td key="goods_volume" :props="props">
-               {{ props.row.goods_volume }}
-             </q-td>
-             <q-td key="supplier" :props="props">
-               {{ props.row.supplier }}
-             </q-td>
-             <q-td key="creater" :props="props">
-               {{ props.row.creater }}
-             </q-td>
-             <q-td key="create_time" :props="props">
-               {{ props.row.create_time }}
-             </q-td>
-             <q-td key="update_time" :props="props">
-               {{ props.row.update_time }}
-             </q-td>
-           </q-tr>
-         </template>
+        <template v-slot:top>
+          <q-btn-group push>
+            <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-btn :label="'日志'" icon="logout" @click="getlog()"> </q-btn> -->
+          </q-btn-group>
+
+          <q-space />
+
+          <div class="flex items-center">
+
+            <q-btn-group push class="q-ml-md"> </q-btn-group>
+            <q-input
+              outlined
+              rounded
+              dense
+              debounce="300"
+              color="primary"
+              v-model="filter"
+              :placeholder="$t('search')"
+              @input="getSearchList()"
+              @keyup.enter="getSearchList()"
+            >
+              <template v-slot:append>
+                <q-icon name="search" @click="getSearchList()" />
+              </template>
+            </q-input>
+          </div>
+        </template>
+
+        <template v-slot:body="props">
+          <q-tr :props="props">
+            <q-td auto-width>
+              <q-btn
+                size="sm"
+                round
+                :icon="props.row.expand ? 'remove' : 'ballot'"
+                @click="handle_row_expand(props.row)"
+              />
+            </q-td>
+            <q-td
+              v-for="col in columns.filter((c) => c.name !== 'expand')"
+              :key="col.name"
+              :props="props"
+            >
+              {{ col.field ? props.row[col.field] : props.row[col.name] }}
+            </q-td>
+          </q-tr>
+
+          <!-- 第二级:时间轴 -->
+          <q-tr v-show="props.row.expand" :props="props" class="expanded-row">
+            <q-td colspan="100%">
+              <div class="q-pa-md timeline-wrapper">
+                <q-timeline
+                  color="#e0e0e0"
+                  v-if="props.row.batch_items?.length"
+                >
+                  <q-timeline-entry
+                    v-for="(batch_item, index) in props.row.batch_items"
+                    :key="index"
+                    class="custom-node"
+                  >
+                    <template v-slot:title>
+                      <span>
+                        <div>批次 {{ batch_item.batch_code }}</div>
+                        <div class="row">
+                          <div class="col">
+                            <div class="custom-title">
+                              {{ batch_item.goods_desc }}
+                            </div>
+                          </div>
+                          <div class="col">
+                            <div class="custom-title">
+                              数量:{{ batch_item.goods_in_qty }}
+                            </div>
+                          </div>
+
+                        </div>
+                      </span>
+                    </template>
+                  </q-timeline-entry>
+                </q-timeline>
+                <div v-else-if="props.row.loading" class="text-center q-pa-md">
+                  <q-spinner color="primary" size="2em" />
+                  <div class="q-mt-sm">正在加载信息...</div>
+                </div>
+              </div>
+            </q-td>
+          </q-tr>
+        </template>
       </q-table>
-        </transition>
-      <template>
-        <div v-show="max !== 0" class="q-pa-lg flex flex-center">
-           <div>{{ total }} </div>
-          <q-pagination
-            v-model="current"
-            color="black"
-            :max="max"
-            :max-pages="6"
-            boundary-links
-            @click="getList()"
+    </transition>
+    <template>
+      <div v-show="max !== 0" class="q-pa-lg flex flex-center">
+        <div>{{ total }}</div>
+        <q-pagination
+          v-model="current"
+          color="black"
+          :max="max"
+          :max-pages="6"
+          boundary-links
+          @click="
+            getSearchList(current);
+            paginationIpt = current;
+          "
+        />
+        <div>
+          <input
+            v-model="paginationIpt"
+            @blur="changePageEnter"
+            @keyup.enter="changePageEnter"
+            style="width: 60px; text-align: center"
           />
-          <div>
-            <input
-              v-model="paginationIpt"
-              @blur="changePageEnter"
-              @keyup.enter="changePageEnter"
-              style="width: 60px; text-align: center"
-            />
-          </div>
-        </div>
-        <div v-show="max === 0" class="q-pa-lg flex flex-center">
-          <q-btn flat push color="dark" :label="$t('no_data')"></q-btn>
         </div>
+      </div>
+      <div v-show="max === 0" class="q-pa-lg flex flex-center">
+        <q-btn flat push color="dark" :label="$t('no_data')"></q-btn>
+      </div>
     </template>
-    </div>
+  </div>
 </template>
-    <router-view />
+<router-view />
 
 <script>
-import { getauth } from 'boot/axios_request'
+import { getauth, postauth, putauth, deleteauth } from 'boot/axios_request'
+import { date, LocalStorage } from 'quasar'
 
 export default {
-  name: 'Pageasndetail',
+  name: 'PageTask',
   data () {
     return {
+      createDate1: '',
+      createDate2: '',
+      date_range: '',
+      proxyDate: '',
+      date: '',
+      goods_code: '',
+      goods_desc: '',
       openid: '',
       login_name: '',
       authin: '0',
-      pathname: 'asn/detail/?asn_status=1',
+      searchUrl: '',
+      pathname: 'bound/batch/count/',
       pathname_previous: '',
       pathname_next: '',
       separator: 'cell',
       loading: false,
       height: '',
+      viewForm: false,
+
       table_list: [],
-      bin_size_list: [],
-      bin_property_list: [],
-      warehouse_list: [],
       columns: [
-        { name: 'asn_code', required: true, label: this.$t('inbound.view_asn.asn_code'), align: 'left', field: 'asn_code' },
-        { name: 'goods_code', label: this.$t('goods.view_goodslist.goods_code'), field: 'goods_code', align: 'center' },
-        { name: 'goods_desc', label: this.$t('goods.view_goodslist.goods_desc'), field: 'goods_desc', align: 'center' },
-        { name: 'goods_qty', label: this.$t('inbound.view_asn.goods_qty'), field: 'goods_qty', align: 'center' },
-        { name: 'goods_weight', label: this.$t('inbound.view_asn.total_weight'), field: 'goods_weight', align: 'center' },
-        { name: 'goods_volume', label: this.$t('inbound.view_asn.total_volume'), field: 'goods_volume', align: 'center' },
-        { name: 'supplier', label: this.$t('baseinfo.view_supplier.supplier_name'), field: 'supplier', align: 'center' },
-        { name: 'creater', label: this.$t('creater'), field: 'creater', align: 'center' },
-        { name: 'create_time', label: this.$t('createtime'), field: 'create_time', align: 'center' },
-        { name: 'update_time', label: this.$t('updatetime'), field: 'update_time', align: 'center' }
+        {
+          name: 'expand',
+          label: '',
+          align: 'left',
+          headerStyle: 'width: 50px'
+        },
+
+        {
+          name: 'goods_code',
+          label: '存货编码',
+          field: 'goods_code',
+          align: 'center'
+        },
+        {
+          name: 'goods_desc',
+          label: '存货名称',
+          field: 'goods_desc',
+          align: 'center'
+        },
+
+        {
+          name: 'total_quantity',
+          label: '在库数目',
+          field: 'total_quantity',
+          align: 'center'
+        },
+        {
+          name: 'goods_unit',
+          label: '单位',
+          field: 'goods_unit',
+          align: 'center',
+          headerStyle: 'width: 20px'
+        }
       ],
       filter: '',
       pagination: {
@@ -136,135 +216,190 @@ export default {
       current: 1,
       max: 0,
       total: 0,
-      paginationIpt: 1
+      paginationIpt: 1,
+      containers: {},
+      timer: null
+    }
+  },
+  computed: {
+    interval () {
+      return (
+        this.$t('download_center.start') +
+        ' - ' +
+        this.$t('download_center.end')
+      )
     }
   },
   methods: {
-    getList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
+    class_to_name (class_id) {
+      const class_map = {
+        1: '整盘',
+        2: '托盘组',
+        3: '零盘'
       }
+      return class_map[class_id]
     },
-    changePageEnter(e) {
-      if (Number(this.paginationIpt) < 1) {
-        this.current = 1;
-        this.paginationIpt = 1;
-      } else if (Number(this.paginationIpt) > this.max) {
-        this.current = this.max;
-        this.paginationIpt = this.max;
-      } else {
-        this.current = Number(this.paginationIpt);
+    handle_row_expand (row) {
+      const _this = this
+      row.expand = !row.expand
+      if (row.expand) {
+        // 添加行级 loading 状态
+        _this.$set(row, 'loading', true)
+        getauth('bound/batch/count/' + row.id + '/', {})
+          .then((res) => {
+            _this.$set(row, 'batch_items', res.batch_items)
+            console.log('当前的', row.batch_items)
+          })
+          .catch((err) => {
+            _this.$q.notify({ message: err.detail, color: 'negative' })
+          })
+          .finally(() => {
+            row.loading = false // 关闭加载状态
+          })
       }
-      this.getList();
     },
-    getSearchList () {
+    getlog () {
+      // console.log(this.table_list)
+      console.log('当前loading状态:', this.loading)
+    },
+    getList (params = {}) {
       var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        _this.current = 1
-        _this.paginationIpt = 1
-        getauth(_this.pathname + '&asn_code__icontains=' + _this.filter + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
+      _this.loading = true
+      // 合并基础参数
+      const baseParams = {
+        page: _this.current,
+        page_size: _this.pagination.rowsPerPage
+      }
+
+      // 创建URLSearchParams处理参数
+      const queryParams = new URLSearchParams({
+        ...baseParams,
+        ...params
+      })
+      console.log(queryParams)
+      // 过滤空值参数
+      Array.from(queryParams.entries()).forEach(([key, value]) => {
+        if (value === '' || value === null || value === undefined) {
+          queryParams.delete(key)
+        }
+      })
+
+      getauth(`${_this.pathname}?${queryParams}`)
+        .then((res) => {
+          _this.table_list = res.results.map((item) => ({
+            ...item,
+            expand: false,
+            batch_items: [
+
+            ],
+            loading: false
+          }))
           _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
+          _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage) || 0
           _this.pathname_previous = res.previous
           _this.pathname_next = res.next
-        }).catch(err => {
+        })
+        .catch((err) => {
           _this.$q.notify({
             message: err.detail,
             icon: 'close',
             color: 'negative'
           })
         })
+        .finally(() => {
+          _this.loading = false
+        })
+    },
+    changePageEnter () {
+      if (Number(this.paginationIpt) < 1) {
+        this.current = 1
+        this.paginationIpt = 1
+      } else if (Number(this.paginationIpt) > this.max) {
+        this.current = this.max
+        this.paginationIpt = this.max
       } else {
+        this.current = Number(this.paginationIpt)
       }
+      this.getSearchList(this.current)
     },
+
+    // 带搜索条件加载
+    getSearchList (page = 1) {
+      this.current = page
+      this.paginationIpt = page
+      this.getList({
+        goods_desc__icontains: this.filter,
+        create_time__range: this.date_range
+      })
+    },
+
     getListPrevious () {
       var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_previous, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
+      if (LocalStorage.has('auth')) {
+        getauth(_this.pathname_previous, {})
+          .then((res) => {
+            _this.table_list = res.results
+            _this.pathname_previous = res.previous
+            _this.pathname_next = res.next
+          })
+          .catch((err) => {
+            _this.$q.notify({
+              message: err.detail,
+              icon: 'close',
+              color: 'negative'
+            })
           })
-        })
       } else {
       }
     },
     getListNext () {
       var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_next, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
+      if (LocalStorage.has('auth')) {
+        getauth(_this.pathname_next, {})
+          .then((res) => {
+            _this.table_list = res.results
+
+            _this.pathname_previous = res.previous
+            _this.pathname_next = res.next
+          })
+          .catch((err) => {
+            _this.$q.notify({
+              message: err.detail,
+              icon: 'close',
+              color: 'negative'
+            })
           })
-        })
-      } else {
       }
     },
     reFresh () {
       var _this = this
-      _this.getList()
+      _this.getSearchList()
+    },
+
+    updateProxy () {
+      var _this = this
+      _this.proxyDate = _this.date
     }
   },
   created () {
     var _this = this
-    if (_this.$q.localStorage.has('openid')) {
-      _this.openid = _this.$q.localStorage.getItem('openid')
+    if (LocalStorage.has('openid')) {
+      _this.openid = LocalStorage.getItem('openid')
     } else {
       _this.openid = ''
-      _this.$q.localStorage.set('openid', '')
+      LocalStorage.set('openid', '')
     }
-    if (_this.$q.localStorage.has('login_name')) {
-      _this.login_name = _this.$q.localStorage.getItem('login_name')
+    if (LocalStorage.has('login_name')) {
+      _this.login_name = LocalStorage.getItem('login_name')
     } else {
       _this.login_name = ''
-      _this.$q.localStorage.set('login_name', '')
+      LocalStorage.set('login_name', '')
     }
-    if (_this.$q.localStorage.has('auth')) {
+    if (LocalStorage.has('auth')) {
+      const timeStamp = Date.now()
+      const formattedString = date.formatDate(timeStamp, 'YYYY/MM/DD')
+      _this.date = formattedString
+      console.log(_this.date)
       _this.authin = '1'
       _this.getList()
     } else {
@@ -278,10 +413,76 @@ export default {
     } else {
       _this.height = _this.$q.screen.height - 290 + '' + 'px'
     }
+    // _this.timer = setInterval(() => {
+    //   _this.getlog()
+    // }, 1000)
   },
-  updated () {
-  },
-  destroyed () {
+  updated () {},
+  destroyed () {},
+  // 在 watch 或方法中添加调试代码
+  watch: {
+    createDate1 (val) {
+      if (val) {
+        if (val.to) {
+          this.createDate2 = `${val.from} - ${val.to}`
+          this.date_range = `${val.from},${val.to} `
+
+          // this.downloadhUrl = this.pathname + 'filelist/?' + 'document_date__range=' + this.date_range
+        } else {
+          this.createDate2 = `${val}`
+          this.dateArray = val.split('/')
+          this.searchUrl =
+            this.pathname +
+            '?' +
+            'document_date__year=' +
+            this.dateArray[0] +
+            '&' +
+            'document_date__month=' +
+            this.dateArray[1] +
+            '&' +
+            'document_date__day=' +
+            this.dateArray[2]
+          // this.downloadhUrl = this.pathname + 'filelist/?' + 'document_date__year=' + this.dateArray[0] + '&' + 'document_date__month=' + this.dateArray[1] + '&' + 'document_date__day=' + this.dateArray[2]
+        }
+        this.date_range = this.date_range.replace(/\//g, '-')
+
+        this.getSearchList()
+        this.$refs.qDateProxy.hide()
+      } else {
+        this.createDate2 = ''
+        this.date_range = ''
+        this.getSearchList()
+      }
+    }
   }
 }
 </script>
+<style scoped>
+/* 添加在 <style> 中 */
+.q-date__calendar-item--selected {
+  transition: all 0.3s ease;
+  background-color: #1976d2 !important;
+}
+
+.q-date__range {
+  background-color: rgba(25, 118, 210, 0.1);
+}
+
+.custom-title {
+  font-size: 0.9rem; /* 推荐使用相对单位 */
+  font-weight: 500;
+}
+/* 添加以下样式 */
+.custom-timeline {
+  --q-timeline-color: #e0e0e0; /* 覆盖时间轴线颜色变量 */
+}
+
+.custom-node .q-timeline__dot {
+  background: #485573 !important; /* 节点填充色 */
+  border: 2px solid #5c6b8c !important; /* 节点边框色 */
+}
+
+.custom-node .q-timeline__content {
+  color: #485573; /* 文字颜色 */
+}
+</style>

+ 2 - 2
templates/src/pages/inbound/sortstock.vue

@@ -258,7 +258,7 @@ export default {
         },
         {
           name: 'goods_in_qty',
-          label: '已分拣数目',
+          label: '已组盘数目',
           field: 'goods_in_qty',
           align: 'center'
         },
@@ -419,7 +419,7 @@ export default {
       this.current = page
       this.paginationIpt = page
       this.getList({
-        container_detail__goods_desc__icontains: this.filter,
+        goods_desc__icontains: this.filter,
         create_time__range: this.date_range
       })
     },

+ 338 - 76
templates/src/pages/outbound/dn.vue

@@ -518,7 +518,7 @@
       </q-card>
     </q-dialog>
     <q-dialog v-model="newBatchForm">
-      <q-card class="shadow-24">
+      <q-card class="shadow-24" style="min-width: 800px">
         <q-bar
           class="bg-light-blue-10 text-white rounded-borders"
           style="height: 50px"
@@ -531,7 +531,7 @@
             }}</q-tooltip>
           </q-btn>
         </q-bar>
-        <q-card-section style="min-height: 325px; width: 550px" class="scroll">
+        <q-card-section style="min-height: 325px; width: 800px" class="scroll">
           <div class="text-h6 q-mb-md">
             {{ "目标批次信息" }}
             <q-select
@@ -539,7 +539,7 @@
               outlined
               square
               v-model="newBatchFormData.out_type"
-              :label="'物料'"
+              :label="'出库方式'"
               :options="out_type_list"
               option-label="label"
               option-value="value"
@@ -549,60 +549,179 @@
               transition-hide="scale"
               :rules="[(val) => (val && val.length > 0) || error1]"
             />
+            <div style="margin-left: -10px">
+              <q-card class="q-pa-md">
+                <!-- 条件组操作栏 -->
+                <div class="q-mb-md">
+                  <div class="row">
+                    <q-btn
+                      label="添加条件"
+                      icon="add"
+                      color="primary"
+                      @click="addCondition"
+                    />
+                    <q-btn
+                      label="显示条件"
+                      class="q-ml-md"
+                      @click="showConditions = !showConditions"
+                    />
+                    <q-select
+                      v-model="logicOperator"
+                      :options="['AND', 'OR']"
+                      dense
+                      class="q-ml-md"
+                      style="width: 100px"
+                    />
+                    <q-space />
+                    <q-btn
+                      label="重置"
+                      class="q-ml-md"
+                      @click="resetConditions"
+                    />
+                    <q-btn
+                      label="执行查询"
+                      color="primary"
+                      @click="getProductSearchList"
+                    />
+                  </div>
+                </div>
 
-            <div class="row q-gutter-x-md">
-              <div class="col column q-gutter-y-md">
-                <q-select
-                  dense
-                  outlined
-                  square
-                  v-model="target_batch"
-                  :label="'物料编码'"
-                  :options="product_list"
-                  option-label="value"
-                  option-value="value"
-                  emit-value
-                  map-options
-                  transition-show="scale"
-                  transition-hide="scale"
-                  :rules="[(val) => (val && val.length > 0) || error1]"
-                />
-              </div>
+                <!-- 拖拽式条件容器 -->
+                <draggable :list="conditions" handle=".handle">
+                  <div
+                    v-for="(condition, index) in conditions"
+                    :key="condition.id"
+                    class="row q-gutter-sm q-mb-md"
+                  >
+                    <q-icon
+                      name="drag_indicator"
+                      class="handle text-grey-6"
+                      size="24px"
+                    />
 
-              <div class="col column q-gutter-y-md">
-                <q-input
-                  outlined
-                  dense
-                  color="primary"
-                  v-model="product_filter"
-                  :placeholder="'搜索批次号'"
-                  autofocus
-                  @input="getProductSearchList()"
-                  @keyup.enter="getProductSearchList()"
-                >
-                  <template v-slot:append>
-                    <q-icon name="search" />
-                  </template>
-                </q-input>
-              </div>
-            </div>
+                    <q-select
+                      v-model="condition.field"
+                      :options="fieldOptions"
+                      label="字段"
+                      dense
+                      emit-value
+                      map-options
+                      style="min-width: 160px"
+                    />
 
-            <q-select
-              dense
-              outlined
-              square
-              v-model="newBatchFormData.goods_code"
-              :label="'物料'"
-              :options="product_list"
-              option-label="label"
-              option-value="value"
-              emit-value
-              map-options
-              :readonly="true"
-              transition-show="scale"
-              transition-hide="scale"
-              :rules="[(val) => (val && val.length > 0) || error1]"
-            />
+                    <!-- 运算符 -->
+                    <q-select
+                      v-model="condition.operator"
+                      :options="operatorOptions(condition.field)"
+                      label="条件"
+                      dense
+                      emit-value
+                      map-options
+                      style="min-width: 130px"
+                    />
+
+                    <!-- 值输入 -->
+                    <component
+                      v-if="condition.field !== 'create_time'"
+                      :is="inputComponent(condition.field)"
+                      v-model="condition.value"
+                      class="col"
+                    />
+                    <template v-else>
+                      <div class="row q-gutter-xs">
+                        <!-- 年 -->
+                        <q-select
+                          v-model="condition.year"
+                          :options="yearOptions"
+                          label="年"
+                          dense
+                          emit-value
+                          @input="handleDateChange(condition)"
+                        />
+
+                        <!-- 月 -->
+                        <q-select
+                          v-model="condition.month"
+                          :options="monthOptions"
+                          label="月"
+                          dense
+                          emit-value
+                          :disable="!condition.year"
+                          @input="handleDateChange(condition)"
+                        />
+
+                        <!-- 日 -->
+                        <q-select
+                          v-model="condition.day"
+                          :options="dayOptions(condition.year, condition.month)"
+                          label="日"
+                          dense
+                          emit-value
+                          :disable="!condition.month"
+                          @input="handleDateChange(condition)"
+                        />
+                      </div>
+                    </template>
+
+                    <q-btn
+                      flat
+                      round
+                      icon="delete"
+                      color="negative"
+                      @click="removeCondition(index)"
+                    />
+                  </div>
+                </draggable>
+
+                <div class="row q-gutter-x-md">
+                  <div class="col column q-gutter-y-md">
+                    <q-select
+                      dense
+                      outlined
+                      square
+                      v-model="target_batch"
+                      :label="'管理批次'"
+                      :options="product_list"
+                      option-label="value"
+                      option-value="value"
+                      emit-value
+                      map-options
+                      transition-show="scale"
+                      transition-hide="scale"
+                    />
+                  </div>
+                  <div class="col column q-gutter-y-md">
+                    <q-select
+                      dense
+                      outlined
+                      square
+                      v-model="target_batch"
+                      :label="'物料'"
+                      :options="product_list"
+                      option-label="label"
+                      option-value="value"
+                      emit-value
+                      map-options
+                      :readonly="false"
+                      transition-show="scale"
+                      transition-hide="scale"
+                      :rules="[(val) => (val && val.length > 0) || error1]"
+                    />
+                  </div>
+                </div>
+
+                <div class="q-mt-lg" v-show="showConditions">
+                  <q-card flat bordered>
+                    <q-card-section>
+                      <div class="text-caption">生成查询条件:</div>
+                      <pre>{{ generatedQuery }}</pre>
+                    </q-card-section>
+
+                    <q-card-actions align="right"> </q-card-actions>
+                  </q-card>
+                </div>
+              </q-card>
+            </div>
             <div class="q-mb-md">
               {{ "已有批次信息" }}
               <div style="float: right; padding: 15px 15px 15px 0">
@@ -622,7 +741,6 @@
                       v-model.number="newBatchFormData.goods_weight"
                       :label="'单重'"
                       type="number"
-                      :rules="[(val) => (val && val > 0) || error1]"
                       :readonly="true"
                     />
                     <q-input
@@ -633,7 +751,6 @@
                       :label="'当前数量'"
                       type="number"
                       :readonly="true"
-                      :rules="[(val) => (val && val > 0) || error1]"
                     />
                   </div>
                   <div class="col column q-gutter-y-md">
@@ -643,7 +760,6 @@
                       square
                       v-model="newBatchFormData.goods_unit"
                       :label="'单位'"
-                      :rules="[(val) => (val && val.length > 0) || error1]"
                       :readonly="true"
                     />
                     <q-input
@@ -652,7 +768,6 @@
                       square
                       v-model="newBatchFormData.goods_std"
                       :label="'规格/备注'"
-                      :rules="[(val) => (val && val.length > 0) || error1]"
                       :readonly="true"
                     />
                   </div>
@@ -666,7 +781,6 @@
             square
             v-model="newBatchFormData.goods_desc"
             :label="'名称'"
-            :rules="[(val) => (val && val.length > 0) || error1]"
           />
 
           <q-input
@@ -848,13 +962,21 @@
 
 <script>
 import { getauth, postauth, putauth, deleteauth } from 'boot/axios_request'
-
+import draggable from 'vuedraggable'
 import { date, exportFile, LocalStorage } from 'quasar'
 
 export default {
   name: 'Pagednlist',
+  components: {
+    draggable
+  },
   data () {
     return {
+      showConditions: false,
+      year: null,
+      month: null,
+      day: null,
+      dateStr: '',
       createDate1: '',
       createDate2: '',
       date_range: '',
@@ -968,7 +1090,6 @@ export default {
         creater: '',
         out_number: '',
         out_type: '0',
-
         goods_std: '',
         goods_batch: ''
       },
@@ -990,19 +1111,146 @@ export default {
       bound_batch_list: [],
 
       activeTab: 'tab1',
-      target_batch: ''
+      target_batch: '',
+      target_goods_desc: '',
+      conditions: [],
+      logicOperator: 'AND',
+      fieldOptions: [
+        { label: '物料编码', value: 'goods_code' },
+        { label: '物料名称', value: 'goods_desc' },
+        { label: '管理批次号', value: 'bound_number' },
+        { label: 'ERP批次号', value: 'sourced_number' },
+        { label: '库存数量', value: 'goods_in_location_qty' },
+        { label: '入库日期', value: 'create_time' }
+      ],
+      operatorMap: {
+        default: [
+          { label: '等于', value: 'eq' },
+          { label: '包含', value: 'contains' }
+        ],
+        goods_in_location_qty: [
+          { label: '大于', value: 'gt' },
+          { label: '等于', value: 'eq' },
+          { label: '小于', value: 'lt' }
+        ],
+        create_time: [
+          { label: '之前', value: 'lte' },
+          { label: '之后', value: 'gte' }
+        ]
+      }
     }
   },
   computed: {
+    yearOptions () {
+      const currentYear = new Date().getFullYear()
+      return Array.from({ length: 5 }, (_, i) => currentYear - i)
+    },
+
+    monthOptions () {
+      return Array.from({ length: 12 }, (_, i) => ({
+        label: `${i + 1}月`,
+        value: i + 1
+      }))
+    },
+
     interval () {
       return (
         this.$t('download_center.start') +
         ' - ' +
         this.$t('download_center.end')
       )
+    },
+    generatedQuery () {
+      return this.conditions
+        .map((cond) => {
+          const suffix = {
+            eq: '',
+            contains: '__icontains',
+            gt: '__gt',
+            lt: '__lt',
+            gte: '__gte',
+            lte: '__lte'
+          }[cond.operator]
+
+          return `${cond.field}${suffix}=${cond.value}`
+        })
+        .join(` ${this.logicOperator} `)
+    },
+
+    // 动态输入组件
+    inputComponent () {
+      return function (field) {
+        const components = {
+          goods_code: 'q-input',
+          goods_desc: 'q-input',
+          bound_number: 'q-input',
+          sourced_number: 'q-input',
+          goods_in_location_qty: 'q-input'
+        }
+        return components[field] || 'q-input'
+      }
     }
   },
   methods: {
+    dayOptions (year, month) {
+      if (!year || !month) return []
+      const days = new Date(year, month, 0).getDate()
+      return Array.from({ length: days }, (_, i) => i + 1)
+    },
+    handleDateChange (condition) {
+      console.log('当前条件:', condition.value)
+      if (condition.year && condition.month && condition.day) {
+        condition.value = `${condition.year}-${this.pad(
+          condition.month
+        )}-${this.pad(condition.day)}`
+      } else if (condition.year && condition.month) {
+        condition.value = `${condition.year}-${this.pad(condition.month)}`
+      } else if (condition.year) {
+        condition.value = `${condition.year}`
+      }
+    },
+    pad (num) {
+      return num.toString().padStart(2, '0')
+    },
+    // 获取运算符选项
+    operatorOptions (field) {
+      return this.operatorMap[field] || this.operatorMap.default
+    },
+
+    // 添加条件
+    addCondition () {
+      this.conditions.push({
+        id: Date.now(),
+        field: 'goods_desc',
+        operator: 'eq',
+        value: '',
+        year: null,
+        month: null,
+        day: null
+      })
+    },
+
+    // 删除条件
+    removeCondition (index) {
+      this.conditions.splice(index, 1)
+    },
+
+    // 重置条件
+    resetConditions () {
+      this.conditions = []
+      this.newBatchFormData.goods_code = ''
+      this.newBatchFormData.goods_desc = ''
+      this.newBatchFormData.goods_weight = 0
+      this.newBatchFormData.goods_qty = 0
+      this.newBatchFormData.goods_unit = ''
+      this.newBatchFormData.goods_out_qty = 0
+      this.newBatchFormData.creater = ''
+      this.newBatchFormData.out_number = ''
+      this.newBatchFormData.out_type = '0'
+      this.newBatchFormData.goods_std = ''
+      this.newBatchFormData.goods_batch = ''
+    },
+
     getList (params = {}) {
       var _this = this
       _this.loading = true
@@ -1086,24 +1334,34 @@ export default {
         })
     },
     getProductSearchList () {
-      var _this = this
+      const _this = this
       _this.loading = true
-      const params = {
-        bound_number__icontains: _this.product_filter,
+
+      // 1. 创建基础参数对象
+      const baseParams = {
         max_page: 1000
       }
-      const queryParams = new URLSearchParams({
-        ...params
-      })
-      console.log(queryParams)
-      // 过滤空值参数
-      Array.from(queryParams.entries()).forEach(([key, value]) => {
-        if (value === '' || value === null || value === undefined) {
-          queryParams.delete(key)
-        }
-      })
 
-      getauth(`bound/batch/?${queryParams}`)
+      // 2. 解析生成的条件查询参数
+      const queryParams = new URLSearchParams(_this.generatedQuery)
+      const generatedParams = Object.fromEntries(queryParams.entries())
+
+      // 3. 合并参数
+      const mergedParams = {
+        ...baseParams,
+        ...generatedParams
+      }
+
+      // 4. 创建最终查询参数
+      const finalQuery = new URLSearchParams(
+        Object.entries(mergedParams).filter(
+          ([_, v]) => v !== null && v !== undefined && v !== ''
+        )
+      ).toString()
+
+      console.log('查询参数', finalQuery)
+
+      getauth(`bound/batch/?${finalQuery}`)
         .then((res) => {
           _this.product_list = res.results.map((item) => ({
             label: item.goods_desc,
@@ -1721,4 +1979,8 @@ export default {
   padding-left: 50px;
   margin-top: -5px;
 }
+.handle {
+  cursor: move;
+  padding: 8px;
+}
 </style>

+ 221 - 265
templates/src/pages/outbound/freshorder.vue

@@ -1,287 +1,243 @@
 <template>
-    <div>
-      <transition appear enter-active-class="animated fadeIn">
-      <q-table
-        class="my-sticky-header-table shadow-24"
-        :data="table_list"
-        row-key="id"
-        :separator="separator"
-        :loading="loading"
-        :filter="filter"
-        :columns="columns"
-        hide-bottom
-        :pagination.sync="pagination"
-        no-data-label="No data"
-        no-results-label="No data you want"
-        :table-style="{ height: height }"
-        flat
-        bordered
+  <q-card class="q-pa-md shadow-5">
+    <!-- 条件组操作栏 -->
+    <div class="q-mb-md">
+      <div class="row">
+        <q-btn
+          label="添加条件"
+          icon="add"
+          color="primary"
+          @click="addCondition"
+        />
+        <q-select
+          v-model="logicOperator"
+          :options="['AND', 'OR']"
+          dense
+          class="q-ml-md"
+          style="width: 100px"
+        />
+      </div>
+    </div>
+
+    <!-- 拖拽式条件容器 -->
+    <draggable :list="conditions" handle=".handle">
+      <div
+        v-for="(condition, index) in conditions"
+        :key="condition.id"
+        class="row q-gutter-sm q-mb-md"
       >
-         <template v-slot:top>
-           <q-btn-group push>
-             <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-btn-group>
-           <q-space />
-           <q-input outlined rounded dense debounce="300" color="primary" v-model="filter" :placeholder="$t('search')" @input="getSearchList()" @keyup.enter="getSearchList()">
-             <template v-slot:append>
-               <q-icon name="search" @click="getSearchList()"/>
-             </template>
-           </q-input>
-         </template>
-         <template v-slot:body="props">
-           <q-tr :props="props">
-               <q-td key="dn_code" :props="props">
-                 {{ props.row.dn_code }}
-               </q-td>
-               <q-td key="goods_code" :props="props">
-                 {{ props.row.goods_code }}
-               </q-td>
-               <q-td key="goods_desc" :props="props">
-                 {{ props.row.goods_desc }}
-               </q-td>
-               <q-td key="goods_qty" :props="props">
-                 {{ props.row.goods_qty }}
-               </q-td>
-               <q-td key="goods_weight" :props="props">
-                 {{ props.row.goods_weight }}
-               </q-td>
-             <q-td key="goods_volume" :props="props">
-               {{ props.row.goods_volume }}
-             </q-td>
-             <q-td key="customer" :props="props">
-               {{ props.row.customer }}
-             </q-td>
-             <q-td key="creater" :props="props">
-               {{ props.row.creater }}
-             </q-td>
-             <q-td key="create_time" :props="props">
-               {{ props.row.create_time }}
-             </q-td>
-             <q-td key="update_time" :props="props">
-               {{ props.row.update_time }}
-             </q-td>
-           </q-tr>
-         </template>
-      </q-table>
-        </transition>
-      <template>
-        <div v-show="max !== 0" class="q-pa-lg flex flex-center">
-           <div>{{ total }} </div>
-          <q-pagination
-            v-model="current"
-            color="black"
-            :max="max"
-            :max-pages="6"
-            boundary-links
-            @click="getList()"
-          />
-          <div>
-            <input
-              v-model="paginationIpt"
-              @blur="changePageEnter"
-              @keyup.enter="changePageEnter"
-              style="width: 60px; text-align: center"
+        <!-- 拖拽手柄 -->
+
+        <q-icon name="drag_indicator" class="handle text-grey-6" size="24px" />
+
+        <!-- 字段选择 -->
+        <q-select
+          v-model="condition.field"
+          :options="fieldOptions"
+          label="字段"
+          dense
+          emit-value
+          map-options
+          style="min-width: 160px"
+        />
+
+        <!-- 运算符 -->
+        <q-select
+          v-model="condition.operator"
+          :options="operatorOptions(condition.field)"
+          label="条件"
+          dense
+          emit-value
+          map-options
+          style="min-width: 130px"
+        />
+
+        <!-- 值输入 -->
+        <component
+          v-if="condition.field !== 'date'"
+          :is="inputComponent(condition.field)"
+          class="col"
+        />
+        <template v-else>
+          <div class="row q-gutter-xs">
+            <!-- 年 -->
+            <q-select
+              v-model="year"
+              :options="yearOptions"
+              label="年"
+              dense
+              emit-value
+              @update:model-value="handleChange"
+            />
+
+            <!-- 月 -->
+            <q-select
+              v-model="month"
+              :options="monthOptions"
+              label="月"
+              dense
+              emit-value
+              :disable="!year"
+              @update:model-value="handleChange"
+            />
+
+            <!-- 日 -->
+            <q-select
+              v-model="day"
+              :options="dayOptions"
+              label="日"
+              dense
+              emit-value
+              :disable="!month"
+              @update:model-value="handleChange"
             />
           </div>
-        </div>
-        <div v-show="max === 0" class="q-pa-lg flex flex-center">
-          <q-btn flat push color="dark" :label="$t('no_data')"></q-btn>
-        </div>
-    </template>
+        </template>
+
+        <!-- 删除按钮 -->
+        <q-btn
+          flat
+          round
+          icon="delete"
+          color="negative"
+          @click="removeCondition(index)"
+        />
+      </div>
+    </draggable>
+    <div class="q-mt-md" align="right">
+      <q-btn label="重置" @click="resetConditions" />
+      <q-btn label="执行查询" color="primary" @click="executeQuery" />
+    </div>
+    <!-- 查询预览 & 操作 -->
+    <div class="q-mt-lg">
+      <q-card flat bordered>
+        <q-card-section>
+          <div class="text-caption">生成查询条件:</div>
+          <pre>{{ generatedQuery }}</pre>
+        </q-card-section>
+
+        <q-card-actions align="right"> </q-card-actions>
+      </q-card>
     </div>
+  </q-card>
 </template>
-    <router-view />
 
 <script>
-import { getauth } from 'boot/axios_request'
+import draggable from "vuedraggable";
+import CustomDateInput from "components/CustomDateInput.vue";
 
 export default {
-  name: 'Pagednneworder',
-  data () {
+  components: {
+    draggable,
+    CustomDateInput,
+  },
+  data() {
     return {
-      openid: '',
-      login_name: '',
-      authin: '0',
-      pathname: 'dn/detail/?dn_status=1',
-      pathname_previous: '',
-      pathname_next: '',
-      separator: 'cell',
-      loading: false,
-      height: '',
-      table_list: [],
-      bin_size_list: [],
-      bin_property_list: [],
-      warehouse_list: [],
-      columns: [
-        { name: 'dn_code', required: true, label: this.$t('outbound.view_dn.dn_code'), align: 'left', field: 'dn_code' },
-        { name: 'goods_code', label: this.$t('goods.view_goodslist.goods_code'), field: 'goods_code', align: 'center' },
-        { name: 'goods_desc', label: this.$t('goods.view_goodslist.goods_desc'), field: 'goods_desc', align: 'center' },
-        { name: 'goods_qty', label: this.$t('outbound.view_dn.goods_qty'), field: 'goods_qty', align: 'center' },
-        { name: 'goods_weight', label: this.$t('outbound.view_dn.total_weight'), field: 'goods_weight', align: 'center' },
-        { name: 'goods_volume', label: this.$t('outbound.view_dn.total_volume'), field: 'empty_label', align: 'center' },
-        { name: 'customer', label: this.$t('baseinfo.view_customer.customer_name'), field: 'customer', align: 'center' },
-        { name: 'creater', label: this.$t('creater'), field: 'creater', align: 'center' },
-        { name: 'create_time', label: this.$t('createtime'), field: 'create_time', align: 'center' },
-        { name: 'update_time', label: this.$t('updatetime'), field: 'update_time', align: 'center' }
+      conditions: [],
+      logicOperator: "AND",
+      fieldOptions: [
+        { label: "物料编码", value: "material_code" },
+        { label: "物料名称", value: "material_name" },
+        { label: "批次号", value: "batch" },
+        { label: "库存数量", value: "quantity" },
+        { label: "入库日期", value: "date" },
       ],
-      filter: '',
-      pagination: {
-        page: 1,
-        rowsPerPage: 11
+      operatorMap: {
+        default: [
+          { label: "等于", value: "eq" },
+          { label: "包含", value: "contains" },
+          { label: "开头为", value: "startswith" },
+        ],
+        quantity: [
+          { label: "大于", value: "gt" },
+          { label: "等于", value: "eq" },
+          { label: "小于", value: "lt" },
+        ],
+        date: [
+          { label: "之前", value: "lte" },
+          { label: "之后", value: "gte" },
+        ],
       },
-      current: 1,
-      max: 0,
-      total: 0,
-      paginationIpt: 1
-    }
+    };
   },
-  methods: {
-    getList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
+  computed: {
+    // 生成查询参数
+    generatedQuery() {
+      return this.conditions
+        .map((cond) => {
+          const suffix = {
+            eq: "",
+            contains: "__icontains",
+            startswith: "__istartswith",
+            gt: "__gt",
+            lt: "__lt",
+            gte: "__gte",
+            lte: "__lte",
+          }[cond.operator];
+
+          return `${cond.field}${suffix}=${cond.value}`;
         })
-      }
+        .join(` ${this.logicOperator} `);
+    },
+
+    // 动态输入组件
+    inputComponent() {
+      return function (field) {
+        const components = {
+          material_code: "q-input",
+          material_name: "q-input",
+          batch: "q-input",
+          quantity: "q-input",
+          date: "CustomDateInput",
+        };
+        return components[field] || "q-input";
+      };
     },
-    changePageEnter(e) {
-      if (Number(this.paginationIpt) < 1) {
-        this.current = 1;
-        this.paginationIpt = 1;
-      } else if (Number(this.paginationIpt) > this.max) {
-        this.current = this.max;
-        this.paginationIpt = this.max;
-      } else {
-        this.current = Number(this.paginationIpt);
-      }
-      this.getList();
+  },
+  methods: {
+    // 获取运算符选项
+    operatorOptions(field) {
+      return this.operatorMap[field] || this.operatorMap.default;
     },
-    getSearchList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        _this.current = 1
-        _this.paginationIpt = 1
-        getauth(_this.pathname + '&dn_code__icontains=' + _this.filter + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+
+    // 添加条件
+    addCondition() {
+      this.conditions.push({
+        id: Date.now(),
+        field: "material_code",
+        operator: "eq",
+        value: "",
+        ...(this.field === "date" && { value: "2025-01-01" }),
+      });
     },
-    getListPrevious () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_previous, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+    getDefaultDate() {
+      const today = new Date();
+      return `${today.getFullYear()}-${(today.getMonth() + 1)
+        .toString()
+        .padStart(2, "0")}-01`;
     },
-    getListNext () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_next, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+    // 删除条件
+    removeCondition(index) {
+      this.conditions.splice(index, 1);
+    },
+
+    // 重置条件
+    resetConditions() {
+      this.conditions = [];
+    },
+
+    // 执行查询
+    executeQuery() {
+      console.log("执行查询:", this.generatedQuery);
+      // 这里接入实际API调用
     },
-    reFresh () {
-      var _this = this
-      _this.getList()
-    }
-  },
-  created () {
-    var _this = this
-    if (_this.$q.localStorage.has('openid')) {
-      _this.openid = _this.$q.localStorage.getItem('openid')
-    } else {
-      _this.openid = ''
-      _this.$q.localStorage.set('openid', '')
-    }
-    if (_this.$q.localStorage.has('login_name')) {
-      _this.login_name = _this.$q.localStorage.getItem('login_name')
-    } else {
-      _this.login_name = ''
-      _this.$q.localStorage.set('login_name', '')
-    }
-    if (_this.$q.localStorage.has('auth')) {
-      _this.authin = '1'
-      _this.getList()
-    } else {
-      _this.authin = '0'
-    }
-  },
-  mounted () {
-    var _this = this
-    if (_this.$q.platform.is.electron) {
-      _this.height = String(_this.$q.screen.height - 290) + 'px'
-    } else {
-      _this.height = _this.$q.screen.height - 290 + '' + 'px'
-    }
-  },
-  updated () {
   },
-  destroyed () {
-  }
-}
+};
 </script>
+
+<style scoped>
+.handle {
+  cursor: move;
+  padding: 8px;
+}
+</style>

+ 81 - 270
templates/src/pages/outbound/neworder.vue

@@ -1,287 +1,98 @@
 <template>
-    <div>
-      <transition appear enter-active-class="animated fadeIn">
-      <q-table
-        class="my-sticky-header-table shadow-24"
-        :data="table_list"
-        row-key="id"
-        :separator="separator"
-        :loading="loading"
-        :filter="filter"
-        :columns="columns"
-        hide-bottom
-        :pagination.sync="pagination"
-        no-data-label="No data"
-        no-results-label="No data you want"
-        :table-style="{ height: height }"
-        flat
-        bordered
-      >
-         <template v-slot:top>
-           <q-btn-group push>
-             <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-btn-group>
-           <q-space />
-           <q-input outlined rounded dense debounce="300" color="primary" v-model="filter" :placeholder="$t('search')" @input="getSearchList()" @keyup.enter="getSearchList()">
-             <template v-slot:append>
-               <q-icon name="search" @click="getSearchList()"/>
-             </template>
-           </q-input>
-         </template>
-         <template v-slot:body="props">
-           <q-tr :props="props">
-               <q-td key="dn_code" :props="props">
-                 {{ props.row.dn_code }}
-               </q-td>
-               <q-td key="goods_code" :props="props">
-                 {{ props.row.goods_code }}
-               </q-td>
-               <q-td key="goods_desc" :props="props">
-                 {{ props.row.goods_desc }}
-               </q-td>
-               <q-td key="goods_qty" :props="props">
-                 {{ props.row.goods_qty }}
-               </q-td>
-               <q-td key="goods_weight" :props="props">
-                 {{ props.row.goods_weight }}
-               </q-td>
-             <q-td key="goods_volume" :props="props">
-               {{ props.row.goods_volume }}
-             </q-td>
-             <q-td key="customer" :props="props">
-               {{ props.row.customer }}
-             </q-td>
-             <q-td key="creater" :props="props">
-               {{ props.row.creater }}
-             </q-td>
-             <q-td key="create_time" :props="props">
-               {{ props.row.create_time }}
-             </q-td>
-             <q-td key="update_time" :props="props">
-               {{ props.row.update_time }}
-             </q-td>
-           </q-tr>
-         </template>
-      </q-table>
-        </transition>
-      <template>
-        <div v-show="max !== 0" class="q-pa-lg flex flex-center">
-           <div>{{ total }} </div>
-          <q-pagination
-            v-model="current"
-            color="black"
-            :max="max"
-            :max-pages="6"
-            boundary-links
-            @click="getList()"
-          />
-          <div>
-            <input
-              v-model="paginationIpt"
-              @blur="changePageEnter"
-              @keyup.enter="changePageEnter"
-              style="width: 60px; text-align: center"
-            />
-          </div>
-        </div>
-        <div v-show="max === 0" class="q-pa-lg flex flex-center">
-          <q-btn flat push color="dark" :label="$t('no_data')"></q-btn>
-        </div>
-    </template>
-    </div>
+  <div class="row q-gutter-xs">
+    <!-- 年份选择 -->
+    <q-select
+      v-model="year"
+      :options="yearOptions"
+      label="年"
+      dense
+      style="min-width: 90px"
+      @update:model-value="updateDate"
+    />
+
+    <!-- 月份选择 -->
+    <q-select
+      v-model="month"
+      :options="monthOptions"
+      label="月"
+      dense
+      style="min-width: 80px"
+      @update:model-value="updateDate"
+    />
+
+    <!-- 日期选择 -->
+    <q-select
+      v-model="day"
+      :options="dayOptions"
+      label="日"
+      dense
+      style="min-width: 80px"
+      @update:model-value="updateDate"
+    />
+  </div>
 </template>
-    <router-view />
 
 <script>
-import { getauth } from 'boot/axios_request'
-
 export default {
-  name: 'Pagednneworder',
-  data () {
+  props: ['modelValue'],
+  emits: ['update:modelValue'],
+  
+  data() {
     return {
-      openid: '',
-      login_name: '',
-      authin: '0',
-      pathname: 'dn/detail/?dn_status=2',
-      pathname_previous: '',
-      pathname_next: '',
-      separator: 'cell',
-      loading: false,
-      height: '',
-      table_list: [],
-      bin_size_list: [],
-      bin_property_list: [],
-      warehouse_list: [],
-      columns: [
-        { name: 'dn_code', required: true, label: this.$t('outbound.view_dn.dn_code'), align: 'left', field: 'dn_code' },
-        { name: 'goods_code', label: this.$t('goods.view_goodslist.goods_code'), field: 'goods_code', align: 'center' },
-        { name: 'goods_desc', label: this.$t('goods.view_goodslist.goods_desc'), field: 'goods_desc', align: 'center' },
-        { name: 'goods_qty', label: this.$t('outbound.view_dn.goods_qty'), field: 'goods_qty', align: 'center' },
-        { name: 'goods_weight', label: this.$t('outbound.view_dn.total_weight'), field: 'goods_weight', align: 'center' },
-        { name: 'goods_volume', label: this.$t('outbound.view_dn.total_volume'), field: 'empty_label', align: 'center' },
-        { name: 'customer', label: this.$t('baseinfo.view_customer.customer_name'), field: 'customer', align: 'center' },
-        { name: 'creater', label: this.$t('creater'), field: 'creater', align: 'center' },
-        { name: 'create_time', label: this.$t('createtime'), field: 'create_time', align: 'center' },
-        { name: 'update_time', label: this.$t('updatetime'), field: 'update_time', align: 'center' }
-      ],
-      filter: '',
-      pagination: {
-        page: 1,
-        rowsPerPage: 11
-      },
-      current: 1,
-      max: 0,
-      total: 0,
-      paginationIpt: 1
+      year: null,
+      month: null,
+      day: null
     }
   },
-  methods: {
-    getList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      }
-    },
-    changePageEnter(e) {
-      if (Number(this.paginationIpt) < 1) {
-        this.current = 1;
-        this.paginationIpt = 1;
-      } else if (Number(this.paginationIpt) > this.max) {
-        this.current = this.max;
-        this.paginationIpt = this.max;
-      } else {
-        this.current = Number(this.paginationIpt);
-      }
-      this.getList();
-    },
-    getSearchList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        _this.current = 1
-        _this.paginationIpt = 1
-        getauth(_this.pathname + '&dn_code__icontains=' + _this.filter + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
-    },
-    getListPrevious () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_previous, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+
+  computed: {
+    // 生成年份选项(最近5年)
+    yearOptions() {
+      const currentYear = new Date().getFullYear()
+      return Array.from({length: 5}, (_, i) => currentYear - i)
     },
-    getListNext () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_next, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+
+    // 月份选项(1-12月)
+    monthOptions() {
+      return Array.from({length: 12}, (_, i) => i + 1)
     },
-    reFresh () {
-      var _this = this
-      _this.getList()
+
+    // 动态生成日期选项
+    dayOptions() {
+      if (!this.year || !this.month) return []
+      const days = new Date(this.year, this.month, 0).getDate()
+      return Array.from({length: days}, (_, i) => i + 1)
     }
   },
-  created () {
-    var _this = this
-    if (_this.$q.localStorage.has('openid')) {
-      _this.openid = _this.$q.localStorage.getItem('openid')
-    } else {
-      _this.openid = ''
-      _this.$q.localStorage.set('openid', '')
-    }
-    if (_this.$q.localStorage.has('login_name')) {
-      _this.login_name = _this.$q.localStorage.getItem('login_name')
-    } else {
-      _this.login_name = ''
-      _this.$q.localStorage.set('login_name', '')
-    }
-    if (_this.$q.localStorage.has('auth')) {
-      _this.authin = '1'
-      _this.getList()
-    } else {
-      _this.authin = '0'
+
+  methods: {
+    // 组合日期并触发更新
+    updateDate() {
+      if (this.year && this.month && this.day) {
+        const dateStr = `${this.year}-${this.pad(this.month)}-${this.pad(this.day)}`
+        this.$emit('update:modelValue', dateStr)
+      }
+    },
+
+    // 补零函数
+    pad(num) {
+      return num.toString().padStart(2, '0')
     }
   },
-  mounted () {
-    var _this = this
-    if (_this.$q.platform.is.electron) {
-      _this.height = String(_this.$q.screen.height - 290) + 'px'
-    } else {
-      _this.height = _this.$q.screen.height - 290 + '' + 'px'
+
+  watch: {
+    // 解析外部传入的日期值
+    modelValue: {
+      immediate: true,
+      handler(value) {
+        if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
+          const [y, m, d] = value.split('-')
+          this.year = parseInt(y)
+          this.month = parseInt(m)
+          this.day = parseInt(d)
+        }
+      }
     }
-  },
-  updated () {
-  },
-  destroyed () {
   }
 }
-</script>
+</script>

+ 12 - 0
templates/yarn.lock

@@ -9400,6 +9400,11 @@ sort-keys@^2.0.0:
   dependencies:
     is-plain-obj "^1.0.0"
 
+sortablejs@1.10.2:
+  version "1.10.2"
+  resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
+  integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
+
 source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
@@ -10569,6 +10574,13 @@ vue@^2.6.10, vue@^2.6.11, vue@^2.7.1:
     "@vue/compiler-sfc" "2.7.8"
     csstype "^3.1.0"
 
+vuedraggable@^2.24.3:
+  version "2.24.3"
+  resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.3.tgz#43c93849b746a24ce503e123d5b259c701ba0d19"
+  integrity sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==
+  dependencies:
+    sortablejs "1.10.2"
+
 vuex@3.6.2:
   version "3.6.2"
   resolved "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz"