feat: 移除Swagger/Redoc; 首页按区域分块显示设备清单
This commit is contained in:
@@ -30,7 +30,6 @@ INSTALLED_APPS = [
|
|||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_simplejwt',
|
'rest_framework_simplejwt',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'drf_yasg',
|
|
||||||
'device_management',
|
'device_management',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -119,17 +118,6 @@ REST_FRAMEWORK = {
|
|||||||
'PAGE_SIZE': 20,
|
'PAGE_SIZE': 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
SWAGGER_SETTINGS = {
|
|
||||||
'SECURITY_DEFINITIONS': {
|
|
||||||
'Bearer': {
|
|
||||||
'type': 'apiKey',
|
|
||||||
'name': 'Authorization',
|
|
||||||
'in': 'header'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'USE_SESSION_AUTH': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
SIMPLE_JWT = {
|
SIMPLE_JWT = {
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ from django.urls import path, include
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
from rest_framework import permissions
|
|
||||||
from drf_yasg.views import get_schema_view
|
|
||||||
from drf_yasg import openapi
|
|
||||||
|
|
||||||
from rest_framework_simplejwt.views import (
|
from rest_framework_simplejwt.views import (
|
||||||
TokenObtainPairView,
|
TokenObtainPairView,
|
||||||
TokenRefreshView,
|
TokenRefreshView,
|
||||||
@@ -17,19 +13,6 @@ from rest_framework_simplejwt.views import (
|
|||||||
|
|
||||||
from device_management.views import home_page
|
from device_management.views import home_page
|
||||||
|
|
||||||
schema_view = get_schema_view(
|
|
||||||
openapi.Info(
|
|
||||||
title="视频主设备管理系统 API",
|
|
||||||
default_version='v1',
|
|
||||||
description="视频编码器、解码器、矩阵、NVR等设备管理系统API文档",
|
|
||||||
terms_of_service="https://www.example.com/terms/",
|
|
||||||
contact=openapi.Contact(email="contact@example.com"),
|
|
||||||
license=openapi.License(name="BSD License"),
|
|
||||||
),
|
|
||||||
public=True,
|
|
||||||
permission_classes=(permissions.AllowAny,),
|
|
||||||
)
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', home_page, name='home'),
|
path('', home_page, name='home'),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
@@ -37,7 +20,4 @@ urlpatterns = [
|
|||||||
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
|
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||||
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||||
path('api-auth/', include('rest_framework.urls')),
|
path('api-auth/', include('rest_framework.urls')),
|
||||||
path('swagger<format>/', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
|
||||||
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
|
||||||
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
@@ -6,243 +6,163 @@
|
|||||||
<title>视频主设备管理系统</title>
|
<title>视频主设备管理系统</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<style>
|
||||||
.gradient-bg {
|
.gradient-bg { background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 50%, #1e3a5f 100%); }
|
||||||
background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 50%, #1e3a5f 100%);
|
.status-normal { background: #dcfce7; color: #166534; }
|
||||||
}
|
.status-warning { background: #fef9c3; color: #854d0e; }
|
||||||
.card-hover:hover {
|
.status-offline { background: #f3f4f6; color: #6b7280; }
|
||||||
transform: translateY(-4px);
|
.status-repair { background: #fee2e2; color: #991b1b; }
|
||||||
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
|
.status-scrap { background: #e5e7eb; color: #374151; text-decoration: line-through; }
|
||||||
}
|
.pulse-dot { animation: pulse 2s infinite; }
|
||||||
.pulse-dot {
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||||
animation: pulse 2s infinite;
|
.device-row:hover { background: #f8fafc; }
|
||||||
}
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.5; }
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 min-h-screen">
|
<body class="bg-gray-50 min-h-screen">
|
||||||
<!-- Header -->
|
|
||||||
<header class="gradient-bg text-white shadow-lg">
|
<header class="gradient-bg text-white shadow-lg">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-5">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center">
|
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
|
||||||
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
<svg class="w-7 h-7" fill="currentColor" viewBox="0 0 24 24">
|
||||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold tracking-tight">视频主设备管理系统</h1>
|
<h1 class="text-2xl font-bold tracking-tight">视频主设备管理系统</h1>
|
||||||
<p class="text-blue-200 text-sm mt-1">Device Management System · RESTful API Service</p>
|
<p class="text-blue-200 text-sm mt-0.5">Device Management System</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-6">
|
||||||
<span class="pulse-dot w-2 h-2 bg-green-400 rounded-full"></span>
|
<a href="/admin/" class="text-blue-200 hover:text-white text-sm transition-colors">后台管理</a>
|
||||||
<span class="text-green-300 text-sm font-medium">服务运行中</span>
|
<a href="/api/devices/" class="text-blue-200 hover:text-white text-sm transition-colors">API</a>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="pulse-dot w-2 h-2 bg-green-400 rounded-full"></span>
|
||||||
|
<span class="text-green-300 text-sm">运行中</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<!-- 统计概览 -->
|
||||||
<!-- Stats Overview -->
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3 mb-8">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 mb-10">
|
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 text-center">
|
||||||
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 card-hover transition-all duration-300">
|
<p class="text-2xl font-bold text-gray-800">{{ total_count }}</p>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<p class="text-xs text-gray-500 mt-1">设备总数</p>
|
||||||
<div class="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full">核心</span>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-1">设备管理</h3>
|
|
||||||
<p class="text-gray-500 text-sm">全生命周期设备资产库</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 text-center">
|
||||||
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 card-hover transition-all duration-300">
|
<p class="text-2xl font-bold text-green-600">{{ status_counts.normal }}</p>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<p class="text-xs text-gray-500 mt-1">正常运行</p>
|
||||||
<div class="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs font-medium text-green-600 bg-green-50 px-2.5 py-1 rounded-full">网络</span>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-1">IP/序列号管理</h3>
|
|
||||||
<p class="text-gray-500 text-sm">唯一性校验防止冲突</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 text-center">
|
||||||
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 card-hover transition-all duration-300">
|
<p class="text-2xl font-bold text-yellow-600">{{ status_counts.warning }}</p>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<p class="text-xs text-gray-500 mt-1">告警</p>
|
||||||
<div class="w-12 h-12 bg-orange-100 rounded-xl flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs font-medium text-orange-600 bg-orange-50 px-2.5 py-1 rounded-full">运维</span>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-1">维修/巡检追踪</h3>
|
|
||||||
<p class="text-gray-500 text-sm">历史可追溯保修预警</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 text-center">
|
||||||
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 card-hover transition-all duration-300">
|
<p class="text-2xl font-bold text-gray-400">{{ status_counts.offline }}</p>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<p class="text-xs text-gray-500 mt-1">离线</p>
|
||||||
<div class="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center">
|
</div>
|
||||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 text-center">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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"/>
|
<p class="text-2xl font-bold text-red-600">{{ status_counts.repair }}</p>
|
||||||
</svg>
|
<p class="text-xs text-gray-500 mt-1">维修中</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs font-medium text-purple-600 bg-purple-50 px-2.5 py-1 rounded-full">数据</span>
|
<div class="bg-white rounded-xl p-4 shadow-sm border border-gray-100 text-center">
|
||||||
</div>
|
<p class="text-2xl font-bold text-orange-500">{{ warranty_expiring }}</p>
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-1">Excel导入导出</h3>
|
<p class="text-xs text-gray-500 mt-1">保修即将到期</p>
|
||||||
<p class="text-gray-500 text-sm">批量数据高效管理</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Links -->
|
<!-- 按区域分块显示设备 -->
|
||||||
<div class="mb-10">
|
{% if devices_by_location %}
|
||||||
<h2 class="text-xl font-bold text-gray-800 mb-5">快捷入口</h2>
|
{% for location, devices in devices_by_location.items %}
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="mb-6">
|
||||||
<a href="/swagger/" class="group bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl p-6 text-white card-hover transition-all duration-300 shadow-lg shadow-blue-200">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center group-hover:bg-white/30 transition-colors">
|
<span class="w-1 h-6 bg-blue-500 rounded-full"></span>
|
||||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<h2 class="text-lg font-bold text-gray-800">{{ location }}</h2>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
<span class="text-xs bg-blue-50 text-blue-600 px-2 py-0.5 rounded-full">{{ devices|length }} 台</span>
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<svg class="w-6 h-6 opacity-0 group-hover:opacity-100 transition-opacity transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-xl font-bold mb-1">Swagger API 文档</h3>
|
|
||||||
<p class="text-blue-100 text-sm">交互式接口调试,支持在线测试</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/redoc/" class="group bg-gradient-to-br from-green-500 to-green-600 rounded-2xl p-6 text-white card-hover transition-all duration-300 shadow-lg shadow-green-200">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<div class="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center group-hover:bg-white/30 transition-colors">
|
|
||||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
|
|
||||||
<svg class="w-6 h-6 opacity-0 group-hover:opacity-100 transition-opacity transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-xl font-bold mb-1">Redoc API 文档</h3>
|
|
||||||
<p class="text-green-100 text-sm">美观的文档展示,适合阅读浏览</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/admin/" class="group bg-gradient-to-br from-gray-700 to-gray-800 rounded-2xl p-6 text-white card-hover transition-all duration-300 shadow-lg shadow-gray-200">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<div class="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center group-hover:bg-white/30 transition-colors">
|
|
||||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<svg class="w-6 h-6 opacity-0 group-hover:opacity-100 transition-opacity transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-xl font-bold mb-1">Django Admin</h3>
|
|
||||||
<p class="text-gray-300 text-sm">后台管理界面,数据可视化管理</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tech Stack & API Info -->
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<!-- Tech Stack -->
|
|
||||||
<div class="bg-white rounded-2xl p-7 shadow-sm border border-gray-100">
|
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-5 flex items-center">
|
|
||||||
<span class="w-1.5 h-6 bg-blue-500 rounded-full mr-3"></span>
|
|
||||||
技术栈
|
|
||||||
</h3>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors">
|
|
||||||
<span class="text-gray-700 font-medium">后端框架</span>
|
|
||||||
<span class="text-sm bg-white px-3 py-1.5 rounded-lg text-gray-600 shadow-sm">Django 4.2 + DRF 3.15</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors">
|
|
||||||
<span class="text-gray-700 font-medium">身份认证</span>
|
|
||||||
<span class="text-sm bg-white px-3 py-1.5 rounded-lg text-gray-600 shadow-sm">JWT Token + Session</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors">
|
|
||||||
<span class="text-gray-700 font-medium">图片处理</span>
|
|
||||||
<span class="text-sm bg-white px-3 py-1.5 rounded-lg text-gray-600 shadow-sm">Pillow (自动缩略图)</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors">
|
|
||||||
<span class="text-gray-700 font-medium">Excel处理</span>
|
|
||||||
<span class="text-sm bg-white px-3 py-1.5 rounded-lg text-gray-600 shadow-sm">openpyxl + pandas</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors">
|
|
||||||
<span class="text-gray-700 font-medium">日志系统</span>
|
|
||||||
<span class="text-sm bg-white px-3 py-1.5 rounded-lg text-gray-600 shadow-sm">loguru</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<table class="w-full text-sm">
|
||||||
<!-- API Endpoints -->
|
<thead>
|
||||||
<div class="bg-white rounded-2xl p-7 shadow-sm border border-gray-100">
|
<tr class="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider">
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-5 flex items-center">
|
<th class="px-4 py-3 text-left font-medium">设备名称</th>
|
||||||
<span class="w-1.5 h-6 bg-green-500 rounded-full mr-3"></span>
|
<th class="px-4 py-3 text-left font-medium">型号</th>
|
||||||
API 端点
|
<th class="px-4 py-3 text-left font-medium">楼栋/机柜</th>
|
||||||
</h3>
|
<th class="px-4 py-3 text-left font-medium">主IP</th>
|
||||||
<div class="space-y-3">
|
<th class="px-4 py-3 text-left font-medium">主序列号</th>
|
||||||
<div class="flex items-center space-x-3">
|
<th class="px-4 py-3 text-left font-medium">状态</th>
|
||||||
<span class="text-xs font-bold bg-green-100 text-green-700 px-2 py-1 rounded">GET</span>
|
<th class="px-4 py-3 text-left font-medium">负责人</th>
|
||||||
<code class="text-sm bg-gray-900 text-green-400 px-3 py-1.5 rounded-lg flex-1">/api/devices/</code>
|
<th class="px-4 py-3 text-left font-medium">服役天数</th>
|
||||||
<span class="text-xs text-gray-500">设备列表</span>
|
</tr>
|
||||||
</div>
|
</thead>
|
||||||
<div class="flex items-center space-x-3">
|
<tbody class="divide-y divide-gray-50">
|
||||||
<span class="text-xs font-bold bg-blue-100 text-blue-700 px-2 py-1 rounded">POST</span>
|
{% for device in devices %}
|
||||||
<code class="text-sm bg-gray-900 text-green-400 px-3 py-1.5 rounded-lg flex-1">/api/devices/</code>
|
<tr class="device-row transition-colors">
|
||||||
<span class="text-xs text-gray-500">创建设备</span>
|
<td class="px-4 py-3">
|
||||||
</div>
|
<div class="font-medium text-gray-800">{{ device.device_name }}</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="text-xs text-gray-400">{{ device.brand|default:"" }}</div>
|
||||||
<span class="text-xs font-bold bg-green-100 text-green-700 px-2 py-1 rounded">GET</span>
|
</td>
|
||||||
<code class="text-sm bg-gray-900 text-green-400 px-3 py-1.5 rounded-lg flex-1">/api/devices/{id}/</code>
|
<td class="px-4 py-3 text-gray-600">{{ device.model|default:"-" }}</td>
|
||||||
<span class="text-xs text-gray-500">设备详情</span>
|
<td class="px-4 py-3 text-gray-600">
|
||||||
</div>
|
{{ device.building|default:"-" }}
|
||||||
<div class="flex items-center space-x-3">
|
{% if device.cabinet %} / {{ device.cabinet }}{% endif %}
|
||||||
<span class="text-xs font-bold bg-green-100 text-green-700 px-2 py-1 rounded">GET</span>
|
{% if device.floor %} {{ device.floor }}{% endif %}
|
||||||
<code class="text-sm bg-gray-900 text-green-400 px-3 py-1.5 rounded-lg flex-1">/api/devices/export_excel/</code>
|
</td>
|
||||||
<span class="text-xs text-gray-500">导出Excel</span>
|
<td class="px-4 py-3">
|
||||||
</div>
|
{% with primary_ip=device.ips.all|first %}
|
||||||
<div class="flex items-center space-x-3">
|
{% if primary_ip and primary_ip.is_primary %}
|
||||||
<span class="text-xs font-bold bg-orange-100 text-orange-700 px-2 py-1 rounded">POST</span>
|
<code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{{ primary_ip.ip_address }}</code>
|
||||||
<code class="text-sm bg-gray-900 text-green-400 px-3 py-1.5 rounded-lg flex-1">/api/devices/import_excel/</code>
|
{% else %}
|
||||||
<span class="text-xs text-gray-500">导入Excel</span>
|
{% for ip in device.ips.all %}
|
||||||
</div>
|
{% if ip.is_primary %}
|
||||||
<div class="flex items-center space-x-3">
|
<code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{{ ip.ip_address }}</code>
|
||||||
<span class="text-xs font-bold bg-orange-100 text-orange-700 px-2 py-1 rounded">POST</span>
|
{% endif %}
|
||||||
<code class="text-sm bg-gray-900 text-green-400 px-3 py-1.5 rounded-lg flex-1">/api/token/</code>
|
{% empty %}
|
||||||
<span class="text-xs text-gray-500">JWT认证</span>
|
<span class="text-gray-400">-</span>
|
||||||
</div>
|
{% 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 }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-gray-600">{{ device.responsible_person|default:"-" }}</td>
|
||||||
|
<td class="px-4 py-3 text-gray-600">{{ device.service_duration_days }} 天</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-20">
|
||||||
|
<svg class="w-16 h-16 mx-auto text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
||||||
|
</svg>
|
||||||
|
<p class="text-gray-400 text-lg">暂无设备数据</p>
|
||||||
|
<a href="/admin/device_management/device/add/" class="inline-block mt-4 text-blue-500 hover:text-blue-600 text-sm">前往后台添加设备 →</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Footer -->
|
<footer class="border-t border-gray-200 mt-8">
|
||||||
<footer class="border-t border-gray-200 mt-16">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 text-center text-sm text-gray-400">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
© 2026 视频主设备管理系统 · Django REST Framework
|
||||||
<div class="flex flex-col md:flex-row items-center justify-between text-sm text-gray-500">
|
|
||||||
<p>© 2026 视频主设备管理系统 · Django REST Framework</p>
|
|
||||||
<p class="mt-2 md:mt-0">
|
|
||||||
<span class="text-gray-400">数据库模型:</span>
|
|
||||||
<span class="font-medium text-gray-700">Device</span>,
|
|
||||||
<span class="font-medium text-gray-700">DeviceSerial</span>,
|
|
||||||
<span class="font-medium text-gray-700">DeviceIP</span>,
|
|
||||||
<span class="font-medium text-gray-700">MaintenanceRecord</span>,
|
|
||||||
<span class="font-medium text-gray-700">DeviceAttachment</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ from rest_framework import viewsets, status, parsers, renderers
|
|||||||
from rest_framework.decorators import action, api_view, parser_classes
|
from rest_framework.decorators import action, api_view, parser_classes
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated
|
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
|
||||||
from drf_yasg import openapi as swagger_openapi
|
|
||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from openpyxl.styles import Font
|
from openpyxl.styles import Font
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -16,7 +14,41 @@ from loguru import logger
|
|||||||
|
|
||||||
|
|
||||||
def home_page(request):
|
def home_page(request):
|
||||||
return render(request, 'device_management/index.html')
|
from collections import OrderedDict
|
||||||
|
from django.db.models import Count, Q
|
||||||
|
|
||||||
|
all_devices = Device.objects.select_related(None).prefetch_related(
|
||||||
|
'serials', 'ips', 'maintenance_records'
|
||||||
|
).order_by('location', 'building', 'cabinet')
|
||||||
|
|
||||||
|
devices_by_location = OrderedDict()
|
||||||
|
for device in all_devices:
|
||||||
|
loc = device.location or '未分类'
|
||||||
|
if loc not in devices_by_location:
|
||||||
|
devices_by_location[loc] = []
|
||||||
|
devices_by_location[loc].append(device)
|
||||||
|
|
||||||
|
status_counts = {
|
||||||
|
'normal': all_devices.filter(status='normal').count(),
|
||||||
|
'warning': all_devices.filter(status='warning').count(),
|
||||||
|
'offline': all_devices.filter(status='offline').count(),
|
||||||
|
'repair': all_devices.filter(status='repair').count(),
|
||||||
|
'scrap': all_devices.filter(status='scrap').count(),
|
||||||
|
}
|
||||||
|
|
||||||
|
warranty_expiring = all_devices.filter(
|
||||||
|
warranty_expire__lte=date.today() + __import__('datetime').timedelta(days=30),
|
||||||
|
warranty_expire__gte=date.today()
|
||||||
|
).count()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'devices_by_location': devices_by_location,
|
||||||
|
'total_count': all_devices.count(),
|
||||||
|
'status_counts': status_counts,
|
||||||
|
'warranty_expiring': warranty_expiring,
|
||||||
|
'location_count': len(devices_by_location),
|
||||||
|
}
|
||||||
|
return render(request, 'device_management/index.html', context)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Device, DeviceSerial, DeviceIP, MaintenanceRecord, DeviceAttachment
|
Device, DeviceSerial, DeviceIP, MaintenanceRecord, DeviceAttachment
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
Django==4.2.11
|
Django==4.2.11
|
||||||
djangorestframework==3.15.1
|
djangorestframework==3.15.1
|
||||||
djangorestframework-simplejwt==5.3.1
|
djangorestframework-simplejwt==5.3.1
|
||||||
drf-yasg==1.21.7
|
Pillow==10.3.0
|
||||||
Pillow==10.3.0
|
mysqlclient==2.2.4
|
||||||
mysqlclient==2.2.4
|
django-filter==24.2
|
||||||
django-filter==24.2
|
openpyxl==3.1.2
|
||||||
openpyxl==3.1.2
|
pandas==2.2.2
|
||||||
pandas==2.2.2
|
numpy==1.26.4
|
||||||
numpy==1.26.4
|
loguru==0.7.2
|
||||||
loguru==0.7.2
|
|
||||||
|
|||||||
Reference in New Issue
Block a user