feat: 设备名称超链接到详情页; 新增设备详情页面展示完整信息
This commit is contained in:
@@ -11,10 +11,11 @@ from rest_framework_simplejwt.views import (
|
||||
TokenRefreshView,
|
||||
)
|
||||
|
||||
from device_management.views import home_page
|
||||
from device_management.views import home_page, device_detail
|
||||
|
||||
urlpatterns = [
|
||||
path('', home_page, name='home'),
|
||||
path('device/<int:device_id>/', device_detail, name='device_detail'),
|
||||
path('admin/', admin.site.urls),
|
||||
path('api/', include('device_management.urls')),
|
||||
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||
|
||||
268
device_management/templates/device_management/detail.html
Normal file
268
device_management/templates/device_management/detail.html
Normal file
@@ -0,0 +1,268 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ device.device_name }} - 设备详情</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
.gradient-bg { background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 50%, #1e3a5f 100%); }
|
||||
.status-normal { background: #dcfce7; color: #166534; }
|
||||
.status-warning { background: #fef9c3; color: #854d0e; }
|
||||
.status-offline { background: #f3f4f6; color: #6b7280; }
|
||||
.status-repair { background: #fee2e2; color: #991b1b; }
|
||||
.status-scrap { background: #e5e7eb; color: #374151; text-decoration: line-through; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<header class="gradient-bg text-white shadow-lg">
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="/" class="text-blue-200 hover:text-white">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold">{{ device.device_name }}</h1>
|
||||
<p class="text-blue-200 text-sm">{{ device.location|default:"-" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="/admin/device_management/device/{{ device.id }}/change/" class="bg-white/20 hover:bg-white/30 text-white text-sm px-4 py-2 rounded-lg transition-colors">编辑</a>
|
||||
<a href="/" class="text-blue-200 hover:text-white text-sm">返回列表</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- 基本信息 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center">
|
||||
<span class="w-1 h-5 bg-blue-500 rounded-full mr-3"></span>
|
||||
基本信息
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">状态</p>
|
||||
<p class="mt-1">
|
||||
<span class="status-{{ device.status }} text-xs font-medium px-2 py-1 rounded-full">
|
||||
{{ device.get_status_display }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">品牌</p>
|
||||
<p class="mt-1 text-gray-800 font-medium">{{ device.brand|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">型号</p>
|
||||
<p class="mt-1 text-gray-800 font-medium">{{ device.model|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">地点</p>
|
||||
<p class="mt-1 text-gray-800">{{ device.location|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">楼栋/机柜</p>
|
||||
<p class="mt-1 text-gray-800">
|
||||
{{ device.building|default:"-" }}
|
||||
{% if device.floor %} {{ device.floor }}{% endif %}
|
||||
{% if device.cabinet %} / {{ device.cabinet }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">MAC地址</p>
|
||||
<p class="mt-1 font-mono text-sm text-gray-800">{{ device.mac_address|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">运维人员</p>
|
||||
<p class="mt-1 text-gray-800">{{ device.responsible_person|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">启用日期</p>
|
||||
<p class="mt-1 text-gray-800">{{ device.enable_date|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">服役时长</p>
|
||||
<p class="mt-1 text-gray-800 font-medium">{{ device.service_duration_days }} 天</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">保修到期</p>
|
||||
<p class="mt-1 text-gray-800">{{ device.warranty_expire|default:"-" }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wider">最后巡检</p>
|
||||
<p class="mt-1 text-gray-800">{{ device.last_inspection_date|default:"-" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IP地址 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center">
|
||||
<span class="w-1 h-5 bg-green-500 rounded-full mr-3"></span>
|
||||
IP地址
|
||||
<span class="ml-2 text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full">{{ device_ips|length }}</span>
|
||||
</h2>
|
||||
{% if device_ips %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider">
|
||||
<th class="px-3 py-2 text-left">IP地址</th>
|
||||
<th class="px-3 py-2 text-left">类型</th>
|
||||
<th class="px-3 py-2 text-left">主IP</th>
|
||||
<th class="px-3 py-2 text-left">子网掩码</th>
|
||||
<th class="px-3 py-2 text-left">网关</th>
|
||||
<th class="px-3 py-2 text-left">VLAN</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
{% for ip in device_ips %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-3 py-2 font-mono">{{ ip.ip_address }}</td>
|
||||
<td class="px-3 py-2 text-gray-600">{{ ip.ip_type|default:"-" }}</td>
|
||||
<td class="px-3 py-2">
|
||||
{% if ip.is_primary %}
|
||||
<span class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">是</span>
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">否</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-3 py-2 font-mono text-gray-600">{{ ip.subnet_mask|default:"-" }}</td>
|
||||
<td class="px-3 py-2 font-mono text-gray-600">{{ ip.gateway|default:"-" }}</td>
|
||||
<td class="px-3 py-2 text-gray-600">{{ ip.vlan_id|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-400 text-sm">暂无IP地址</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 序列号 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center">
|
||||
<span class="w-1 h-5 bg-blue-500 rounded-full mr-3"></span>
|
||||
序列号
|
||||
<span class="ml-2 text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full">{{ device_serials|length }}</span>
|
||||
</h2>
|
||||
{% if device_serials %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider">
|
||||
<th class="px-3 py-2 text-left">序列号</th>
|
||||
<th class="px-3 py-2 text-left">类型</th>
|
||||
<th class="px-3 py-2 text-left">主序列号</th>
|
||||
<th class="px-3 py-2 text-left">备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
{% for serial in device_serials %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-3 py-2 font-mono">{{ serial.serial_number }}</td>
|
||||
<td class="px-3 py-2 text-gray-600">{{ serial.serial_type|default:"-" }}</td>
|
||||
<td class="px-3 py-2">
|
||||
{% if serial.is_primary %}
|
||||
<span class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">是</span>
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">否</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-3 py-2 text-gray-600">{{ serial.remark|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-400 text-sm">暂无序列号</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 维修记录 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center">
|
||||
<span class="w-1 h-5 bg-orange-500 rounded-full mr-3"></span>
|
||||
维修记录
|
||||
<span class="ml-2 text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full">{{ maintenance_records|length }}</span>
|
||||
</h2>
|
||||
{% if maintenance_records %}
|
||||
<div class="space-y-4">
|
||||
{% for record in maintenance_records %}
|
||||
<div class="border border-gray-100 rounded-lg p-4 hover:border-gray-200">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-800">{{ record.maintenance_date }}</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{% if record.cost %}费用: ¥{{ record.cost }}{% endif %}
|
||||
{% if record.maintenance_by %} · {{ record.maintenance_by }}{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% if record.fault_description %}
|
||||
<div class="mb-2">
|
||||
<p class="text-xs text-gray-500">故障描述</p>
|
||||
<p class="text-sm text-gray-700">{{ record.fault_description }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if record.repair_content %}
|
||||
<div class="mb-2">
|
||||
<p class="text-xs text-gray-500">维修内容</p>
|
||||
<p class="text-sm text-gray-700">{{ record.repair_content }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if record.replaced_parts %}
|
||||
<div>
|
||||
<p class="text-xs text-gray-500">更换配件</p>
|
||||
<p class="text-sm text-gray-700">{{ record.replaced_parts }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-400 text-sm">暂无维修记录</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 附件 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center">
|
||||
<span class="w-1 h-5 bg-purple-500 rounded-full mr-3"></span>
|
||||
附件
|
||||
<span class="ml-2 text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full">{{ attachments|length }}</span>
|
||||
</h2>
|
||||
{% if attachments %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{% for att in attachments %}
|
||||
<a href="{{ att.file.url }}" target="_blank" class="border border-gray-100 rounded-lg p-4 hover:border-blue-300 hover:bg-blue-50 transition-colors block">
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-700 truncate">{{ att.file.name }}</p>
|
||||
<p class="text-xs text-gray-400">{{ att.uploaded_at|date:"Y-m-d" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-400 text-sm">暂无附件</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="border-t border-gray-200 mt-8">
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-6 text-center text-sm text-gray-400">
|
||||
© 2026 视频主设备管理系统 · Django REST Framework
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -92,8 +92,6 @@
|
||||
<tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider">
|
||||
<th class="px-4 py-3 text-left font-medium">设备名称</th>
|
||||
<th class="px-4 py-3 text-left font-medium">型号</th>
|
||||
<th class="px-4 py-3 text-left font-medium">主IP</th>
|
||||
<th class="px-4 py-3 text-left font-medium">主序列号</th>
|
||||
<th class="px-4 py-3 text-left font-medium">状态</th>
|
||||
<th class="px-4 py-3 text-left font-medium">运维人员</th>
|
||||
<th class="px-4 py-3 text-left font-medium">服役天数</th>
|
||||
@@ -104,34 +102,12 @@
|
||||
{% for device in devices %}
|
||||
<tr class="device-row transition-colors">
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-gray-800">{{ device.device_name }}</div>
|
||||
<div class="font-medium">
|
||||
<a href="/device/{{ device.id }}/" class="text-blue-600 hover:text-blue-800">{{ device.device_name }}</a>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">{{ device.brand|default:"" }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600">{{ device.model|default:"-" }}</td>
|
||||
<td class="px-4 py-3">
|
||||
{% with primary_ip=device.ips.all|first %}
|
||||
{% if primary_ip and primary_ip.is_primary %}
|
||||
<code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{{ primary_ip.ip_address }}</code>
|
||||
{% else %}
|
||||
{% for ip in device.ips.all %}
|
||||
{% if ip.is_primary %}
|
||||
<code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{{ ip.ip_address }}</code>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<span class="text-gray-400">-</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
{% for serial in device.serials.all %}
|
||||
{% if serial.is_primary %}
|
||||
<code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{{ serial.serial_number }}</code>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<span class="text-gray-400">-</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="status-{{ device.status }} text-xs font-medium px-2 py-1 rounded-full">
|
||||
{{ device.get_status_display }}
|
||||
|
||||
@@ -50,6 +50,26 @@ def home_page(request):
|
||||
}
|
||||
return render(request, 'device_management/index.html', context)
|
||||
|
||||
|
||||
def device_detail(request, device_id):
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
device = get_object_or_404(Device, id=device_id)
|
||||
device_serials = device.serials.all()
|
||||
device_ips = device.ips.all()
|
||||
maintenance_records = device.maintenance_records.order_by('-maintenance_date')
|
||||
attachments = device.attachments.order_by('-uploaded_at')
|
||||
|
||||
context = {
|
||||
'device': device,
|
||||
'device_serials': device_serials,
|
||||
'device_ips': device_ips,
|
||||
'maintenance_records': maintenance_records,
|
||||
'attachments': attachments,
|
||||
}
|
||||
return render(request, 'device_management/detail.html', context)
|
||||
|
||||
|
||||
from .models import (
|
||||
Device, DeviceSerial, DeviceIP, MaintenanceRecord, DeviceAttachment
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user