فهرست منبع

fix: 修复导入ID重复覆盖逻辑、Excel列顺序对齐及UI优化

cnbugs 3 هفته پیش
والد
کامیت
2eac2d867e
6فایلهای تغییر یافته به همراه106 افزوده شده و 64 حذف شده
  1. 3 3
      README.md
  2. 51 39
      assetapp/excel_utils.py
  3. 1 1
      assetapp/models.py
  4. 29 8
      assetapp/views.py
  5. 1 1
      templates/assetapp/asset_detail.html
  6. 21 12
      templates/assetapp/asset_list.html

+ 3 - 3
README.md

@@ -9,7 +9,7 @@
 - **分类管理** — 自定义设备分类(服务器、网络设备、存储设备等)
 - **变更追踪** — 资产信息修改自动记录变更历史
 - **Excel 导入导出** — 批量导入(分类不存在自动创建)、筛选导出、模板下载
-- **多维度搜索** — 按编号/名称/部门/使用人/位置搜索,按分类/状态/位置筛选
+- **多维度搜索** — 按编号/名称/部门/维护人/位置搜索,按分类/状态/位置筛选
 - **质保提醒** — 已过保/即将过保状态标识
 - **用户认证** — 登录/退出,管理员权限控制
 - **深色主题** — 护眼深色 UI,适合长时间使用
@@ -40,7 +40,7 @@
 | 设备位置 | 机房/机柜/U位 |
 | 负责人 | 设备负责人 |
 | 使用部门 | 使用部门 |
-| 使用人 | 实际使用人 |
+| 维护人 | 实际使用人 |
 | 采购日期 | 采购时间 |
 | 质保到期 | 质保截止日期 |
 | 备注 | 补充说明 |
@@ -122,7 +122,7 @@ DATABASES = {
 
 ## 📊 Excel 导入格式
 
-| 资产编号 | 设备名称 | 分类 | 品牌 | 型号 | 资产面值 | 序列号 | 状态 | IP地址 | BMC地址 | 设备位置 | 负责人 | 使用部门 | 使用人 | 采购日期 | 质保到期 | 备注 |
+| 资产编号 | 设备名称 | 分类 | 品牌 | 型号 | 资产面值 | 序列号 | 状态 | IP地址 | BMC地址 | 设备位置 | 负责人 | 使用部门 | 维护人 | 采购日期 | 质保到期 | 备注 |
 |----------|---------|------|------|------|---------|--------|------|--------|---------|---------|--------|---------|--------|---------|---------|------|
 
 - 分类不存在时自动创建

+ 51 - 39
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),
-    ('purchase_date', '采购日期', 12),
-    ('warranty_expire', '质保到期', 12),
-    ('supplier', '供应商', 15),
     ('responsible_person', '负责人', 10),
     ('department', '使用部门', 15),
-    ('user', '使用人', 10),
+    ('user', '维护人', 10),
     ('business_type', '业务类型', 15),
     ('status', '状态', 8),
+    ('purchase_date', '采购日期', 12),
+    ('warranty_expire', '质保到期', 12),
+    ('supplier', '供应商', 15),
     ('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,

+ 1 - 1
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='')

+ 29 - 8
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,
     }

+ 1 - 1
templates/assetapp/asset_detail.html

@@ -61,7 +61,7 @@
                     <tr><td class="text-muted">卡数</td><td>{{ asset.gpu_count|default:"-" }}</td></tr>
                     <tr><td class="text-muted">负责人</td><td>{{ asset.responsible_person|default:"-" }}</td></tr>
                     <tr><td class="text-muted">使用部门</td><td>{{ asset.department|default:"-" }}</td></tr>
-                    <tr><td class="text-muted">使用人</td><td>{{ asset.user|default:"-" }}</td></tr>
+                    <tr><td class="text-muted">维护人</td><td>{{ asset.user|default:"-" }}</td></tr>
                     <tr><td class="text-muted">业务类型</td><td>{{ asset.business_type|default:"-" }}</td></tr>
                     <tr><td class="text-muted">状态</td>
                         <td><span class="badge 

+ 21 - 12
templates/assetapp/asset_list.html

@@ -22,10 +22,10 @@
 <div class="card card-dark mb-3">
     <div class="card-body">
         <form method="get" class="row g-2 align-items-end">
-            <div class="col-md-4">
+            <div class="col-md-3">
                 <label class="form-label text-muted small">搜索</label>
                 <input type="text" name="search" class="form-control form-control-sm" 
-                       placeholder="编号/名称/序列号/IP/品牌/型号/位置/负责人/部门/使用人" value="{{ search }}">
+                       placeholder="编号/名称/序列号/IP/BMC/品牌/型号/机柜/负责人/部门/维护人/业务类型..." value="{{ search }}">
             </div>
             <div class="col-md-2">
                 <label class="form-label text-muted small">分类</label>
@@ -46,16 +46,25 @@
                 </select>
             </div>
             <div class="col-md-2">
-                <label class="form-label text-muted small">位置</label>
-                <select name="location" class="form-select form-select-sm">
-                    <option value="">全部位置</option>
-                    {% for loc in locations %}
-                    <option value="{{ loc }}" {% if current_location == loc %}selected{% endif %}>{{ loc }}</option>
+                <label class="form-label text-muted small">机柜</label>
+                <select name="cabinet" class="form-select form-select-sm">
+                    <option value="">全部机柜</option>
+                    {% for cab in cabinets %}
+                    <option value="{{ cab }}" {% if current_cabinet == cab %}selected{% endif %}>{{ cab }}</option>
                     {% endfor %}
                 </select>
             </div>
             <div class="col-md-2">
-                <button type="submit" class="btn btn-primary btn-sm me-1"><i class="bi bi-search"></i> 搜索</button>
+                <label class="form-label text-muted small">业务类型</label>
+                <select name="business_type" class="form-select form-select-sm">
+                    <option value="">全部业务</option>
+                    {% for bt in business_types %}
+                    <option value="{{ bt }}" {% if current_business_type == bt %}selected{% endif %}>{{ bt }}</option>
+                    {% endfor %}
+                </select>
+            </div>
+            <div class="col-md-1">
+                <button type="submit" class="btn btn-primary btn-sm me-1"><i class="bi bi-search"></i></button>
                 <a href="{% url 'asset_list' %}" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></a>
             </div>
         </form>
@@ -84,7 +93,7 @@
                         <th>卡数</th>
                         <th style="width:60px">负责人</th>
                         <th>使用部门</th>
-                        <th style="width:60px">使用人</th>
+                        <th style="width:60px">维护人</th>
                         <th>业务类型</th>
                         <th>状态</th>
                         <th>操作</th>
@@ -145,19 +154,19 @@
 <nav class="mt-3">
     <ul class="pagination pagination-sm justify-content-center">
         {% if page_obj.has_previous %}
-        <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search %}&search={{ search }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_location %}&location={{ current_location }}{% endif %}"><i class="bi bi-chevron-left"></i></a></li>
+        <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search %}&search={{ search }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_cabinet %}&cabinet={{ current_cabinet }}{% endif %}{% if current_business_type %}&business_type={{ current_business_type }}{% endif %}"><i class="bi bi-chevron-left"></i></a></li>
         {% endif %}
         
         {% for num in page_obj.paginator.page_range %}
             {% if page_obj.number == num %}
             <li class="page-item active"><span class="page-link">{{ num }}</span></li>
             {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
-            <li class="page-item"><a class="page-link" href="?page={{ num }}{% if search %}&search={{ search }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_location %}&location={{ current_location }}{% endif %}">{{ num }}</a></li>
+            <li class="page-item"><a class="page-link" href="?page={{ num }}{% if search %}&search={{ search }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_cabinet %}&cabinet={{ current_cabinet }}{% endif %}{% if current_business_type %}&business_type={{ current_business_type }}{% endif %}">{{ num }}</a></li>
             {% endif %}
         {% endfor %}
         
         {% if page_obj.has_next %}
-        <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search %}&search={{ search }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_location %}&location={{ current_location }}{% endif %}"><i class="bi bi-chevron-right"></i></a></li>
+        <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search %}&search={{ search }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_cabinet %}&cabinet={{ current_cabinet }}{% endif %}{% if current_business_type %}&business_type={{ current_business_type }}{% endif %}"><i class="bi bi-chevron-right"></i></a></li>
         {% endif %}
     </ul>
 </nav>