From 2eac2d867ef2bbd85c587193b169a8393ab21eeb Mon Sep 17 00:00:00 2001 From: cnbugs Date: Wed, 29 Apr 2026 13:48:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=BC=E5=85=A5ID?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=A6=86=E7=9B=96=E9=80=BB=E8=BE=91=E3=80=81?= =?UTF-8?q?Excel=E5=88=97=E9=A1=BA=E5=BA=8F=E5=AF=B9=E9=BD=90=E5=8F=8AUI?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- assetapp/excel_utils.py | 92 ++++++++++++++++------------ assetapp/models.py | 2 +- assetapp/views.py | 37 ++++++++--- templates/assetapp/asset_detail.html | 2 +- templates/assetapp/asset_list.html | 33 ++++++---- 6 files changed, 107 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 6ee146d..64a2684 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - **分类管理** — 自定义设备分类(服务器、网络设备、存储设备等) - **变更追踪** — 资产信息修改自动记录变更历史 - **Excel 导入导出** — 批量导入(分类不存在自动创建)、筛选导出、模板下载 -- **多维度搜索** — 按编号/名称/部门/使用人/位置搜索,按分类/状态/位置筛选 +- **多维度搜索** — 按编号/名称/部门/维护人/位置搜索,按分类/状态/位置筛选 - **质保提醒** — 已过保/即将过保状态标识 - **用户认证** — 登录/退出,管理员权限控制 - **深色主题** — 护眼深色 UI,适合长时间使用 @@ -40,7 +40,7 @@ | 设备位置 | 机房/机柜/U位 | | 负责人 | 设备负责人 | | 使用部门 | 使用部门 | -| 使用人 | 实际使用人 | +| 维护人 | 实际使用人 | | 采购日期 | 采购时间 | | 质保到期 | 质保截止日期 | | 备注 | 补充说明 | @@ -122,7 +122,7 @@ DATABASES = { ## 📊 Excel 导入格式 -| 资产编号 | 设备名称 | 分类 | 品牌 | 型号 | 资产面值 | 序列号 | 状态 | IP地址 | BMC地址 | 设备位置 | 负责人 | 使用部门 | 使用人 | 采购日期 | 质保到期 | 备注 | +| 资产编号 | 设备名称 | 分类 | 品牌 | 型号 | 资产面值 | 序列号 | 状态 | IP地址 | BMC地址 | 设备位置 | 负责人 | 使用部门 | 维护人 | 采购日期 | 质保到期 | 备注 | |----------|---------|------|------|------|---------|--------|------|--------|---------|---------|--------|---------|--------|---------|---------|------| - 分类不存在时自动创建 diff --git a/assetapp/excel_utils.py b/assetapp/excel_utils.py index 223b60a..3abafe3 100644 --- a/assetapp/excel_utils.py +++ b/assetapp/excel_utils.py @@ -6,9 +6,12 @@ from django.utils import timezone from .models import Category -# Excel列定义 +# Excel列定义(顺序与资产列表一致) EXPORT_COLUMNS = [ ('id', 'ID', 8), + ('location', '机房', 20), + ('cabinet', '机柜', 10), + ('cabinet_position', '机柜位置', 10), ('asset_number', '资产编号', 18), ('name', '设备名称', 20), ('category', '设备分类', 12), @@ -16,21 +19,18 @@ EXPORT_COLUMNS = [ ('model', '型号', 20), ('asset_value', '资产面值', 12), ('serial_number', '序列号', 25), - ('location', '机房', 20), - ('cabinet', '机柜', 10), - ('cabinet_position', '机柜位置', 10), ('bmc_address', 'BMC地址', 16), ('ip_address', 'IP地址', 16), ('gpu_type', '显卡类型', 15), ('gpu_count', '卡数', 6), + ('responsible_person', '负责人', 10), + ('department', '使用部门', 15), + ('user', '维护人', 10), + ('business_type', '业务类型', 15), + ('status', '状态', 8), ('purchase_date', '采购日期', 12), ('warranty_expire', '质保到期', 12), ('supplier', '供应商', 15), - ('responsible_person', '负责人', 10), - ('department', '使用部门', 15), - ('user', '使用人', 10), - ('business_type', '业务类型', 15), - ('status', '状态', 8), ('remark', '备注', 30), ] @@ -106,12 +106,13 @@ def generate_import_template(): cell.border = THIN_BORDER ws.column_dimensions[get_column_letter(col_idx)].width = width - # 示例数据行 + # 示例数据行(顺序与EXPORT_COLUMNS一致) example_data = [ - '1', 'IT-2024-0001', '测试服务器', '服务器', 'Dell', 'PowerEdge R740', - '50000.00', 'ABC123456', '3楼机房A区', 'A01', 'U10-U15', '192.168.1.200', - '192.168.1.100', 'NVIDIA A100', '8', '2024-01-15', '2027-01-15', '戴尔科技', - '张三', '研发部', '李四', 'AI训练', '在用', '测试备注' + '1', '3楼机房A区', 'A01', 'U10-U15', 'IT-2024-0001', '测试服务器', + '服务器', 'Dell', 'PowerEdge R740', '50000.00', 'ABC123456', + '192.168.1.200', '192.168.1.100', 'NVIDIA A100', '8', '张三', + '研发部', '李四', 'AI训练', '在用', '2024-01-15', '2027-01-15', + '戴尔科技', '测试备注' ] for col_idx, value in enumerate(example_data, 1): cell = ws.cell(row=2, column=col_idx, value=value) @@ -250,32 +251,43 @@ def import_assets_from_excel(ws, category_map, operator=None): # 创建新记录 gpu_count_str = data.get('gpu_count', '').strip() gpu_count = int(gpu_count_str) if gpu_count_str else None - asset = Asset.objects.create( - asset_number=asset_number, - name=data.get('name', ''), - category=category, - brand=data.get('brand', ''), - model=data.get('model', ''), - asset_value=asset_value, - serial_number=data.get('serial_number', ''), - location=data.get('location', ''), - cabinet=data.get('cabinet', ''), - cabinet_position=data.get('cabinet_position', ''), - bmc_address=bmc_address, - ip_address=ip_address, - gpu_type=data.get('gpu_type', ''), - gpu_count=gpu_count, - purchase_date=purchase_date, - warranty_expire=warranty_expire, - supplier=data.get('supplier', ''), - responsible_person=data.get('responsible_person', ''), - department=data.get('department', ''), - user=data.get('user', ''), - business_type=data.get('business_type', ''), - status=status, - remark=data.get('remark', ''), - created_by=operator, - ) + + # 创建参数 + create_kwargs = { + 'asset_number': asset_number, + 'name': data.get('name', ''), + 'category': category, + 'brand': data.get('brand', ''), + 'model': data.get('model', ''), + 'asset_value': asset_value, + 'serial_number': data.get('serial_number', ''), + 'location': data.get('location', ''), + 'cabinet': data.get('cabinet', ''), + 'cabinet_position': data.get('cabinet_position', ''), + 'bmc_address': bmc_address, + 'ip_address': ip_address, + 'gpu_type': data.get('gpu_type', ''), + 'gpu_count': gpu_count, + 'purchase_date': purchase_date, + 'warranty_expire': warranty_expire, + 'supplier': data.get('supplier', ''), + 'responsible_person': data.get('responsible_person', ''), + 'department': data.get('department', ''), + 'user': data.get('user', ''), + 'business_type': data.get('business_type', ''), + 'status': status, + 'remark': data.get('remark', ''), + 'created_by': operator, + } + + # 如果Excel提供了ID,使用该ID创建 + if import_id: + try: + create_kwargs['id'] = int(import_id) + except ValueError: + pass + + asset = Asset.objects.create(**create_kwargs) AssetChangeLog.objects.create( asset=asset, diff --git a/assetapp/models.py b/assetapp/models.py index a76861b..601e740 100644 --- a/assetapp/models.py +++ b/assetapp/models.py @@ -61,7 +61,7 @@ class Asset(models.Model): # 管理信息 responsible_person = models.CharField('负责人', max_length=50, blank=True, default='') department = models.CharField('使用部门', max_length=100, blank=True, default='') - user = models.CharField('使用人', max_length=50, blank=True, default='') + user = models.CharField('维护人', max_length=50, blank=True, default='') business_type = models.CharField('业务类型', max_length=100, blank=True, default='') status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='in_use') remark = models.TextField('备注', blank=True, default='') diff --git a/assetapp/views.py b/assetapp/views.py index 97356a5..41baf72 100644 --- a/assetapp/views.py +++ b/assetapp/views.py @@ -88,7 +88,7 @@ def dashboard(request): def asset_list(request): queryset = Asset.objects.select_related('category') - # 搜索 + # 搜索(模糊搜索所有字段) search = request.GET.get('search', '').strip() if search: queryset = queryset.filter( @@ -96,12 +96,18 @@ def asset_list(request): Q(name__icontains=search) | Q(serial_number__icontains=search) | Q(ip_address__icontains=search) | + Q(bmc_address__icontains=search) | Q(brand__icontains=search) | Q(model__icontains=search) | Q(location__icontains=search) | + Q(cabinet__icontains=search) | + Q(cabinet_position__icontains=search) | Q(responsible_person__icontains=search) | Q(department__icontains=search) | - Q(user__icontains=search) + Q(user__icontains=search) | + Q(business_type__icontains=search) | + Q(gpu_type__icontains=search) | + Q(gpu_count__icontains=search) ) # 筛选 @@ -113,9 +119,13 @@ def asset_list(request): if status: queryset = queryset.filter(status=status) - location = request.GET.get('location') - if location: - queryset = queryset.filter(location__icontains=location) + cabinet = request.GET.get('cabinet') + if cabinet: + queryset = queryset.filter(cabinet__icontains=cabinet) + + business_type = request.GET.get('business_type') + if business_type: + queryset = queryset.filter(business_type=business_type) # 排序 sort = request.GET.get('sort', 'id') @@ -132,7 +142,16 @@ def asset_list(request): page_obj = paginator.get_page(page_number) categories = Category.objects.all() - locations = Asset.objects.values_list('location', flat=True).exclude(location='').distinct() + cabinets = sorted(set( + Asset.objects.values_list('cabinet', flat=True) + .exclude(cabinet='') + .exclude(cabinet=None) + )) + business_types = sorted(set( + Asset.objects.values_list('business_type', flat=True) + .exclude(business_type='') + .exclude(business_type=None) + )) context = { 'page_obj': page_obj, @@ -140,8 +159,10 @@ def asset_list(request): 'categories': categories, 'current_category': category_id, 'current_status': status, - 'current_location': location, - 'locations': locations, + 'current_cabinet': cabinet, + 'current_business_type': business_type, + 'cabinets': cabinets, + 'business_types': business_types, 'status_map': STATUS_MAP, 'sort': sort, } diff --git a/templates/assetapp/asset_detail.html b/templates/assetapp/asset_detail.html index 039bcb5..3e533e0 100644 --- a/templates/assetapp/asset_detail.html +++ b/templates/assetapp/asset_detail.html @@ -61,7 +61,7 @@ 卡数{{ asset.gpu_count|default:"-" }} 负责人{{ asset.responsible_person|default:"-" }} 使用部门{{ asset.department|default:"-" }} - 使用人{{ asset.user|default:"-" }} + 维护人{{ asset.user|default:"-" }} 业务类型{{ asset.business_type|default:"-" }} 状态
-
+
+ placeholder="编号/名称/序列号/IP/BMC/品牌/型号/机柜/负责人/部门/维护人/业务类型..." value="{{ search }}">
@@ -46,16 +46,25 @@
- - + + {% for cab in cabinets %} + {% endfor %}
- + + +
+
+
@@ -84,7 +93,7 @@ 卡数 负责人 使用部门 - 使用人 + 维护人 业务类型 状态 操作 @@ -145,19 +154,19 @@