初始提交:希姆计算硬件资产管理系统

功能:
- Django + MySQL + 深色主题
- 资产增删改查(含资产编号、BMC地址、设备位置、备注)
- Excel导入导出(分类自动创建)
- 设备分类管理
- 资产变更记录追踪
- 质保到期提醒
- 用户认证系统
- Docker部署支持
This commit is contained in:
cnbugs
2026-04-25 08:04:51 +08:00
commit a40a0137cf
41 changed files with 2833 additions and 0 deletions
@@ -0,0 +1,32 @@
{% extends "assetapp/base.html" %}
{% block title %}删除确认{% endblock %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header bg-danger">
<i class="bi bi-exclamation-triangle me-2"></i>删除确认
</div>
<div class="card-body text-center py-4">
<i class="bi bi-trash text-danger" style="font-size: 3rem;"></i>
<h5 class="mt-3">确定要删除以下资产吗?</h5>
<div class="my-3 p-3 rounded" style="background: rgba(255,255,255,0.05);">
<p class="mb-1"><strong>资产编号:</strong>{{ asset.asset_number }}</p>
<p class="mb-1"><strong>设备名称:</strong>{{ asset.name }}</p>
<p class="mb-0"><strong>分类:</strong>{{ asset.category.name }}</p>
</div>
<p class="text-danger small">此操作不可撤销,相关变更记录将保留。</p>
<form method="post" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-danger me-2">
<i class="bi bi-trash me-1"></i>确认删除
</button>
</form>
<a href="{% url 'asset_detail' asset.pk %}" class="btn btn-outline-secondary">取消</a>
</div>
</div>
</div>
</div>
{% endblock %}
+150
View File
@@ -0,0 +1,150 @@
{% extends "assetapp/base.html" %}
{% block title %}{{ asset.asset_number }} - 资产详情{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<a href="{% url 'asset_list' %}" class="btn btn-outline-secondary btn-sm me-3">
<i class="bi bi-arrow-left"></i> 返回
</a>
<h4 class="mb-0"><i class="bi bi-hdd me-2"></i>{{ asset.asset_number }}</h4>
<span class="badge ms-3
{% if asset.status == 'in_use' %}bg-success
{% elif asset.status == 'idle' %}bg-warning text-dark
{% elif asset.status == 'maintenance' %}bg-info
{% else %}bg-danger{% endif %}">
{{ asset.get_status_display }}
</span>
</div>
<div>
<a href="{% url 'asset_update' asset.pk %}" class="btn btn-warning btn-sm">
<i class="bi bi-pencil me-1"></i>编辑
</a>
<a href="{% url 'asset_delete' asset.pk %}" class="btn btn-danger btn-sm">
<i class="bi bi-trash me-1"></i>删除
</a>
</div>
</div>
<div class="row g-3">
<!-- 基本信息 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-info-circle me-2"></i>基本信息</div>
<div class="card-body">
<table class="table table-dark table-borderless mb-0 detail-table">
<tr><td class="text-muted" width="120">设备名称</td><td>{{ asset.name }}</td></tr>
<tr><td class="text-muted">资产编号</td><td><code>{{ asset.asset_number }}</code></td></tr>
<tr><td class="text-muted">设备分类</td><td><span class="badge bg-secondary">{{ asset.category.name }}</span></td></tr>
<tr><td class="text-muted">品牌</td><td>{{ asset.brand|default:"-" }}</td></tr>
<tr><td class="text-muted">型号</td><td>{{ asset.model|default:"-" }}</td></tr>
<tr><td class="text-muted">序列号</td><td><code>{{ asset.serial_number|default:"-" }}</code></td></tr>
</table>
</div>
</div>
</div>
<!-- 位置信息 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-geo-alt me-2"></i>位置与网络</div>
<div class="card-body">
<table class="table table-dark table-borderless mb-0 detail-table">
<tr><td class="text-muted" width="120">设备位置</td><td>{{ asset.location|default:"-" }}</td></tr>
<tr><td class="text-muted">机柜</td><td>{{ asset.cabinet|default:"-" }}</td></tr>
<tr><td class="text-muted">机柜位置</td><td>{{ asset.cabinet_position|default:"-" }}</td></tr>
<tr><td class="text-muted">BMC地址</td><td><code>{{ asset.bmc_address|default:"-" }}</code></td></tr>
<tr><td class="text-muted">IP地址</td><td><code>{{ asset.ip_address|default:"-" }}</code></td></tr>
<tr><td class="text-muted">负责人</td><td>{{ asset.responsible_person|default:"-" }}</td></tr>
<tr><td class="text-muted">状态</td>
<td><span class="badge
{% if asset.status == 'in_use' %}bg-success
{% elif asset.status == 'idle' %}bg-warning text-dark
{% elif asset.status == 'maintenance' %}bg-info
{% else %}bg-danger{% endif %}">
{{ asset.get_status_display }}
</span></td>
</tr>
</table>
</div>
</div>
</div>
<!-- 采购与质保 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-receipt me-2"></i>采购与质保</div>
<div class="card-body">
<table class="table table-dark table-borderless mb-0 detail-table">
<tr><td class="text-muted" width="120">采购日期</td><td>{{ asset.purchase_date|default:"-" }}</td></tr>
<tr>
<td class="text-muted">质保到期</td>
<td>
{{ asset.warranty_expire|default:"-" }}
{% if asset.warranty_expire %}
{% if asset.is_expired %}
<span class="badge bg-danger ms-2">已过保</span>
{% elif asset.is_expiring_soon %}
<span class="badge bg-warning text-dark ms-2">即将过保</span>
{% endif %}
{% endif %}
</td>
</tr>
<tr><td class="text-muted">供应商</td><td>{{ asset.supplier|default:"-" }}</td></tr>
</table>
</div>
</div>
</div>
<!-- 备注 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-sticky me-2"></i>备注</div>
<div class="card-body">
<p class="mb-0">{{ asset.remark|default:"暂无备注"|linebreaksbr }}</p>
<hr class="border-secondary">
<small class="text-muted">
创建人:{{ asset.created_by|default:"-" }} |
创建时间:{{ asset.created_at|date:"Y-m-d H:i" }} |
更新时间:{{ asset.updated_at|date:"Y-m-d H:i" }}
</small>
</div>
</div>
</div>
</div>
<!-- 变更记录 -->
<div class="card card-dark mt-3">
<div class="card-header"><i class="bi bi-clock-history me-2"></i>变更记录</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr><th>时间</th><th>操作</th><th>字段</th><th>旧值</th><th>新值</th><th>操作人</th></tr>
</thead>
<tbody>
{% for log in change_logs %}
<tr>
<td>{{ log.created_at|date:"Y-m-d H:i" }}</td>
<td><span class="badge
{% if log.action == 'create' %}bg-success
{% elif log.action == 'update' %}bg-primary
{% elif log.action == 'delete' %}bg-danger
{% elif log.action == 'import' %}bg-info
{% else %}bg-secondary{% endif %}">
{{ log.get_action_display }}</span></td>
<td>{{ log.field_name|default:"-" }}</td>
<td><code>{{ log.old_value|default:"-" }}</code></td>
<td><code>{{ log.new_value|default:"-" }}</code></td>
<td>{{ log.operator|default:"-" }}</td>
</tr>
{% empty %}
<tr><td colspan="6" class="text-center text-light opacity-75 py-3">暂无变更记录</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+140
View File
@@ -0,0 +1,140 @@
{% extends "assetapp/base.html" %}
{% block title %}{% if action == 'create' %}新增资产{% else %}编辑资产{% endif %}{% endblock %}
{% block content %}
<div class="d-flex align-items-center mb-4">
<a href="{% url 'asset_list' %}" class="btn btn-outline-secondary btn-sm me-3">
<i class="bi bi-arrow-left"></i> 返回
</a>
<h4 class="mb-0">
<i class="bi bi-{% if action == 'create' %}plus-circle{% else %}pencil{% endif %} me-2"></i>
{% if action == 'create' %}新增资产{% else %}编辑资产 - {{ asset.asset_number }}{% endif %}
</h4>
</div>
<form method="post" novalidate>
{% csrf_token %}
<div class="row g-3">
<!-- 基本信息 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-info-circle me-2"></i>基本信息</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label text-muted">{{ form.asset_number.label }} <span class="text-danger">*</span></label>
{{ form.asset_number }}
{% if form.asset_number.errors %}<div class="text-danger small mt-1">{{ form.asset_number.errors.0 }}</div>{% endif %}
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.name.label }} <span class="text-danger">*</span></label>
{{ form.name }}
{% if form.name.errors %}<div class="text-danger small mt-1">{{ form.name.errors.0 }}</div>{% endif %}
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.category.label }} <span class="text-danger">*</span></label>
{{ form.category }}
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{{ form.brand.label }}</label>
{{ form.brand }}
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{{ form.model.label }}</label>
{{ form.model }}
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.serial_number.label }}</label>
{{ form.serial_number }}
</div>
</div>
</div>
</div>
<!-- 位置与网络 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-geo-alt me-2"></i>位置与网络</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label text-muted">{{ form.location.label }}</label>
{{ form.location }}
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{{ form.cabinet.label }}</label>
{{ form.cabinet }}
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{{ form.cabinet_position.label }}</label>
{{ form.cabinet_position }}
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.bmc_address.label }}</label>
{{ form.bmc_address }}
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.ip_address.label }}</label>
{{ form.ip_address }}
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.status.label }}</label>
{{ form.status }}
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.responsible_person.label }}</label>
{{ form.responsible_person }}
</div>
</div>
</div>
</div>
<!-- 采购与质保 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-receipt me-2"></i>采购与质保</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{{ form.purchase_date.label }}</label>
{{ form.purchase_date }}
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{{ form.warranty_expire.label }}</label>
{{ form.warranty_expire }}
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">{{ form.supplier.label }}</label>
{{ form.supplier }}
</div>
</div>
</div>
</div>
<!-- 备注 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-sticky me-2"></i>备注</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label text-muted">{{ form.remark.label }}</label>
{{ form.remark }}
</div>
</div>
</div>
</div>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-1"></i>{% if action == 'create' %}创建{% else %}保存{% endif %}
</button>
<a href="{% url 'asset_list' %}" class="btn btn-outline-secondary ms-2">取消</a>
</div>
</form>
{% endblock %}
+71
View File
@@ -0,0 +1,71 @@
{% extends "assetapp/base.html" %}
{% block title %}导入Excel{% endblock %}
{% block content %}
<div class="d-flex align-items-center mb-4">
<a href="{% url 'asset_list' %}" class="btn btn-outline-secondary btn-sm me-3">
<i class="bi bi-arrow-left"></i> 返回
</a>
<h4 class="mb-0"><i class="bi bi-upload me-2"></i>导入Excel</h4>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card card-dark">
<div class="card-header"><i class="bi bi-file-earmark-spreadsheet me-2"></i>上传文件</div>
<div class="card-body">
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
<strong>使用说明:</strong>
<ol class="mb-0 mt-2">
<li><a href="{% url 'download_template' %}" class="text-info">下载导入模板</a></li>
<li>按模板格式填写资产数据</li>
<li>上传填写好的Excel文件(.xlsx)</li>
<li>资产编号重复的记录会被自动跳过</li>
<li>分类不存在时会自动创建</li>
</ol>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">选择Excel文件</label>
<div class="file-upload-wrapper">
<input type="file" name="excel_file" id="id_excel_file" accept=".xlsx,.xls" class="d-none" onchange="updateFileName(this)">
<button type="button" class="btn btn-outline-info" onclick="document.getElementById('id_excel_file').click()">
<i class="bi bi-folder2-open me-1"></i>浏览文件
</button>
<span id="file-name" class="ms-3 file-name-text">未选择文件</span>
</div>
{% if form.excel_file.errors %}
<div class="text-danger small mt-1">{{ form.excel_file.errors.0 }}</div>
{% endif %}
</div>
<div class="d-flex">
<button type="submit" class="btn btn-primary me-2">
<i class="bi bi-upload me-1"></i>开始导入
</button>
<a href="{% url 'download_template' %}" class="btn btn-outline-info">
<i class="bi bi-file-earmark-arrow-down me-1"></i>下载模板
</a>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
function updateFileName(input) {
var nameSpan = document.getElementById('file-name');
if (input.files && input.files.length > 0) {
nameSpan.textContent = input.files[0].name;
nameSpan.style.color = '#4A9EFF';
} else {
nameSpan.textContent = '未选择文件';
nameSpan.style.color = '#8b949e';
}
}
</script>
{% endblock %}
+154
View File
@@ -0,0 +1,154 @@
{% extends "assetapp/base.html" %}
{% block title %}资产列表 - 希姆计算资产管理{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-list-ul me-2"></i>资产列表</h4>
<div>
<a href="{% url 'asset_export' %}" class="btn btn-outline-success btn-sm me-2">
<i class="bi bi-download me-1"></i>导出Excel
</a>
<a href="{% url 'asset_import' %}" class="btn btn-outline-info btn-sm me-2">
<i class="bi bi-upload me-1"></i>导入Excel
</a>
<a href="{% url 'asset_create' %}" class="btn btn-primary btn-sm">
<i class="bi bi-plus-circle me-1"></i>新增资产
</a>
</div>
</div>
<!-- 搜索和筛选 -->
<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">
<label class="form-label text-muted small">搜索</label>
<input type="text" name="search" class="form-control form-control-sm"
placeholder="编号/名称/序列号/IP/品牌/型号/位置/负责人" value="{{ search }}">
</div>
<div class="col-md-2">
<label class="form-label text-muted small">分类</label>
<select name="category" class="form-select form-select-sm">
<option value="">全部分类</option>
{% for cat in categories %}
<option value="{{ cat.id }}" {% if current_category == cat.id|stringformat:"s" %}selected{% endif %}>{{ cat.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label text-muted small">状态</label>
<select name="status" class="form-select form-select-sm">
<option value="">全部状态</option>
{% for key, label in status_map.items %}
<option value="{{ key }}" {% if current_status == key %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</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>
{% 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>
<a href="{% url 'asset_list' %}" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></a>
</div>
</form>
</div>
</div>
<!-- 资产表格 -->
<div class="card card-dark">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover table-striped mb-0">
<thead>
<tr>
<th>资产编号</th>
<th>设备名称</th>
<th>分类</th>
<th>品牌/型号</th>
<th>位置</th>
<th>BMC地址</th>
<th>IP地址</th>
<th>负责人</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for asset in page_obj %}
<tr>
<td><a href="{% url 'asset_detail' asset.pk %}" class="text-info">{{ asset.asset_number }}</a></td>
<td>{{ asset.name }}</td>
<td><span class="badge bg-secondary">{{ asset.category.name }}</span></td>
<td>{{ asset.brand }} {% if asset.model %}{{ asset.model }}{% endif %}</td>
<td>
{{ asset.location }}
{% if asset.cabinet %}<small class="text-muted"> {{ asset.cabinet }}{% if asset.cabinet_position %}/{{ asset.cabinet_position }}{% endif %}</small>{% endif %}
</td>
<td><code>{{ asset.bmc_address|default:"-" }}</code></td>
<td><code>{{ asset.ip_address|default:"-" }}</code></td>
<td>{{ asset.responsible_person|default:"-" }}</td>
<td>
<span class="badge
{% if asset.status == 'in_use' %}bg-success
{% elif asset.status == 'idle' %}bg-warning text-dark
{% elif asset.status == 'maintenance' %}bg-info
{% else %}bg-danger{% endif %}">
{{ asset.get_status_display }}
</span>
</td>
<td>
<a href="{% url 'asset_detail' asset.pk %}" class="btn btn-outline-info btn-xs" title="详情">
<i class="bi bi-eye"></i>
</a>
<a href="{% url 'asset_update' asset.pk %}" class="btn btn-outline-warning btn-xs" title="编辑">
<i class="bi bi-pencil"></i>
</a>
<a href="{% url 'asset_delete' asset.pk %}" class="btn btn-outline-danger btn-xs" title="删除">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr><td colspan="10" class="text-center text-light opacity-75 py-4">暂无资产数据</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- 分页 -->
{% if page_obj.has_other_pages %}
<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 }}&search={{ search }}&category={{ current_category }}&status={{ current_status }}&location={{ current_location }}"><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 }}&search={{ search }}&category={{ current_category }}&status={{ current_status }}&location={{ current_location }}">{{ 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 }}&search={{ search }}&category={{ current_category }}&status={{ current_status }}&location={{ current_location }}"><i class="bi bi-chevron-right"></i></a></li>
{% endif %}
</ul>
</nav>
{% endif %}
<div class="text-light opacity-75 small mt-2">
共 {{ page_obj.paginator.count }} 条记录,第 {{ page_obj.number }}/{{ page_obj.paginator.num_pages }} 页
</div>
{% endblock %}
+89
View File
@@ -0,0 +1,89 @@
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}希姆计算资产管理{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet">
{% block extra_css %}{% endblock %}
</head>
<body>
{% if user.is_authenticated %}
<nav class="navbar navbar-expand-lg navbar-dark bg-navy">
<div class="container-fluid">
<a class="navbar-brand d-flex align-items-center" href="{% url 'dashboard' %}">
<i class="bi bi-hdd-rack me-2"></i>
<strong>希姆计算资产管理</strong>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'dashboard' %}">
<i class="bi bi-speedometer2 me-1"></i>仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'assets' in request.path and 'import' not in request.path %}active{% endif %}" href="{% url 'asset_list' %}">
<i class="bi bi-list-ul me-1"></i>资产列表
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'category_list' %}active{% endif %}" href="{% url 'category_list' %}">
<i class="bi bi-tags me-1"></i>分类管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'change_log_list' %}active{% endif %}" href="{% url 'change_log_list' %}">
<i class="bi bi-clock-history me-1"></i>变更记录
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-file-earmark-spreadsheet me-1"></i>导入导出
</a>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" href="{% url 'asset_import' %}"><i class="bi bi-upload me-2"></i>导入Excel</a></li>
<li><a class="dropdown-item" href="{% url 'asset_export' %}"><i class="bi bi-download me-2"></i>导出Excel</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{% url 'download_template' %}"><i class="bi bi-file-earmark-arrow-down me-2"></i>下载模板</a></li>
</ul>
</li>
</ul>
<div class="d-flex align-items-center">
<span class="text-light me-3">
<i class="bi bi-person-circle me-1"></i>{{ user.username }}
</span>
<a href="{% url 'logout' %}" class="btn btn-outline-light btn-sm">
<i class="bi bi-box-arrow-right me-1"></i>退出
</a>
</div>
</div>
</div>
</nav>
{% endif %}
{% if messages %}
<div class="container-fluid mt-3 px-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
<main class="container-fluid p-4">
{% block content %}{% endblock %}
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
@@ -0,0 +1,26 @@
{% extends "assetapp/base.html" %}
{% block title %}删除分类{% endblock %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header bg-danger">
<i class="bi bi-exclamation-triangle me-2"></i>删除分类
</div>
<div class="card-body text-center py-4">
<h5>确定要删除分类 "{{ category.name }}" 吗?</h5>
<p class="text-muted">{{ category.description }}</p>
<form method="post" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-danger me-2">
<i class="bi bi-trash me-1"></i>确认删除
</button>
</form>
<a href="{% url 'category_list' %}" class="btn btn-outline-secondary">取消</a>
</div>
</div>
</div>
</div>
{% endblock %}
+40
View File
@@ -0,0 +1,40 @@
{% extends "assetapp/base.html" %}
{% block title %}{% if action == 'create' %}新增分类{% else %}编辑分类{% endif %}{% endblock %}
{% block content %}
<div class="d-flex align-items-center mb-4">
<a href="{% url 'category_list' %}" class="btn btn-outline-secondary btn-sm me-3">
<i class="bi bi-arrow-left"></i> 返回
</a>
<h4 class="mb-0">
<i class="bi bi-{% if action == 'create' %}plus-circle{% else %}pencil{% endif %} me-2"></i>
{% if action == 'create' %}新增分类{% else %}编辑分类 - {{ category.name }}{% endif %}
</h4>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card card-dark">
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label class="form-label text-muted">分类名称 <span class="text-danger">*</span></label>
{{ form.name }}
{% if form.name.errors %}<div class="text-danger small mt-1">{{ form.name.errors.0 }}</div>{% endif %}
</div>
<div class="mb-3">
<label class="form-label text-muted">描述</label>
{{ form.description }}
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-1"></i>{% if action == 'create' %}创建{% else %}保存{% endif %}
</button>
<a href="{% url 'category_list' %}" class="btn btn-outline-secondary ms-2">取消</a>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
+52
View File
@@ -0,0 +1,52 @@
{% extends "assetapp/base.html" %}
{% block title %}分类管理{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-tags me-2"></i>分类管理</h4>
<a href="{% url 'category_create' %}" class="btn btn-primary btn-sm">
<i class="bi bi-plus-circle me-1"></i>新增分类
</a>
</div>
<div class="card card-dark">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>ID</th>
<th>分类名称</th>
<th>描述</th>
<th>资产数量</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td>{{ category.id }}</td>
<td><strong>{{ category.name }}</strong></td>
<td>{{ category.description|default:"-" }}</td>
<td><span class="badge bg-primary">{{ category.asset_count }}</span></td>
<td>{{ category.created_at|date:"Y-m-d H:i" }}</td>
<td>
<a href="{% url 'category_update' category.pk %}" class="btn btn-outline-warning btn-xs" title="编辑">
<i class="bi bi-pencil"></i>
</a>
<a href="{% url 'category_delete' category.pk %}" class="btn btn-outline-danger btn-xs" title="删除">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr><td colspan="6" class="text-center text-muted py-4">暂无分类</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+110
View File
@@ -0,0 +1,110 @@
{% extends "assetapp/base.html" %}
{% block title %}变更记录{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-clock-history me-2"></i>变更记录</h4>
</div>
<!-- 筛选 -->
<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">
<label class="form-label text-muted small">资产编号</label>
<input type="text" name="asset_number" class="form-control form-control-sm"
placeholder="输入资产编号搜索" value="{{ asset_number }}">
</div>
<div class="col-md-3">
<label class="form-label text-muted small">操作类型</label>
<select name="action" class="form-select form-select-sm">
<option value="">全部</option>
<option value="create" {% if current_action == 'create' %}selected{% endif %}>创建</option>
<option value="update" {% if current_action == 'update' %}selected{% endif %}>更新</option>
<option value="delete" {% if current_action == 'delete' %}selected{% endif %}>删除</option>
<option value="import" {% if current_action == 'import' %}selected{% endif %}>导入</option>
<option value="export" {% if current_action == 'export' %}selected{% endif %}>导出</option>
<option value="status_change" {% if current_action == 'status_change' %}selected{% endif %}>状态变更</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary btn-sm"><i class="bi bi-search"></i> 搜索</button>
<a href="{% url 'change_log_list' %}" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></a>
</div>
</form>
</div>
</div>
<!-- 记录表 -->
<div class="card card-dark">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>时间</th>
<th>资产编号</th>
<th>操作</th>
<th>变更字段</th>
<th>旧值</th>
<th>新值</th>
<th>描述</th>
<th>操作人</th>
</tr>
</thead>
<tbody>
{% for log in page_obj %}
<tr>
<td>{{ log.created_at|date:"Y-m-d H:i:s" }}</td>
<td>
{% if log.asset %}
<a href="{% url 'asset_detail' log.asset.pk %}" class="text-info">{{ log.asset_number }}</a>
{% else %}
{{ log.asset_number }}
{% endif %}
</td>
<td><span class="badge
{% if log.action == 'create' %}bg-success
{% elif log.action == 'update' %}bg-primary
{% elif log.action == 'delete' %}bg-danger
{% elif log.action == 'import' %}bg-info
{% elif log.action == 'export' %}bg-secondary
{% else %}bg-warning{% endif %}">
{{ log.get_action_display }}</span></td>
<td>{{ log.field_name|default:"-" }}</td>
<td><code class="small">{{ log.old_value|default:"-"|truncatechars:30 }}</code></td>
<td><code class="small">{{ log.new_value|default:"-"|truncatechars:30 }}</code></td>
<td>{{ log.description|default:"-" }}</td>
<td>{{ log.operator|default:"-" }}</td>
</tr>
{% empty %}
<tr><td colspan="8" class="text-center text-muted py-4">暂无变更记录</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- 分页 -->
{% if page_obj.has_other_pages %}
<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 }}&asset_number={{ asset_number }}&action={{ current_action }}"><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 }}&asset_number={{ asset_number }}&action={{ current_action }}">{{ 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 }}&asset_number={{ asset_number }}&action={{ current_action }}"><i class="bi bi-chevron-right"></i></a></li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}
+151
View File
@@ -0,0 +1,151 @@
{% extends "assetapp/base.html" %}
{% load asset_tags %}
{% block title %}仪表盘 - 希姆计算资产管理{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-speedometer2 me-2"></i>仪表盘</h4>
<a href="{% url 'asset_create' %}" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i>新增资产
</a>
</div>
<!-- 统计卡片 -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="stat-card stat-total">
<div class="stat-icon"><i class="bi bi-hdd-rack"></i></div>
<div class="stat-info">
<div class="stat-value">{{ total_assets }}</div>
<div class="stat-label">资产总数</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card stat-in-use">
<div class="stat-icon"><i class="bi bi-cpu"></i></div>
<div class="stat-info">
<div class="stat-value">{{ status_data.in_use|default:0 }}</div>
<div class="stat-label">在用</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card stat-warning">
<div class="stat-icon"><i class="bi bi-exclamation-triangle"></i></div>
<div class="stat-info">
<div class="stat-value">{{ expiring_soon }}</div>
<div class="stat-label">即将过保(30天)</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card stat-danger">
<div class="stat-icon"><i class="bi bi-shield-exclamation"></i></div>
<div class="stat-info">
<div class="stat-value">{{ expired }}</div>
<div class="stat-label">已过保</div>
</div>
</div>
</div>
</div>
<div class="row g-3">
<!-- 分类统计 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header">
<i class="bi bi-pie-chart me-2"></i>分类统计
</div>
<div class="card-body">
{% if category_stats %}
{% for item in category_stats %}
<div class="d-flex justify-content-between align-items-center py-2 border-bottom border-secondary">
<span class="text-light">{{ item.category__name }}</span>
<div class="d-flex align-items-center">
<div class="progress me-2" style="width: 120px; height: 8px;">
{% widthratio item.count total_assets 100 as pct %}
<div class="progress-bar bg-primary" style="width: {{ pct }}%"></div>
</div>
<span class="badge bg-primary">{{ item.count }}</span>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-muted text-center my-4">暂无数据</p>
{% endif %}
</div>
</div>
</div>
<!-- 状态分布 -->
<div class="col-md-6">
<div class="card card-dark">
<div class="card-header">
<i class="bi bi-bar-chart me-2"></i>状态分布
</div>
<div class="card-body">
{% for key, label in status_map.items %}
<div class="d-flex justify-content-between align-items-center py-2 border-bottom border-secondary">
<span class="text-light">{{ label }}</span>
<span class="badge
{% if key == 'in_use' %}bg-success
{% elif key == 'idle' %}bg-warning text-dark
{% elif key == 'maintenance' %}bg-info
{% else %}bg-danger{% endif %}">
{{ status_data|get_item:key|default:0 }}
</span>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- 最近变更 -->
<div class="card card-dark mt-3">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-clock-history me-2"></i>最近变更</span>
<a href="{% url 'change_log_list' %}" class="btn btn-sm btn-outline-light">查看全部</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>时间</th>
<th>资产编号</th>
<th>操作</th>
<th>描述</th>
<th>操作人</th>
</tr>
</thead>
<tbody>
{% for log in recent_changes %}
<tr>
<td>{{ log.created_at|date:"m-d H:i" }}</td>
<td><a href="{% url 'asset_list' %}?search={{ log.asset_number }}" class="text-info">{{ log.asset_number }}</a></td>
<td>
<span class="badge
{% if log.action == 'create' %}bg-success
{% elif log.action == 'update' %}bg-primary
{% elif log.action == 'delete' %}bg-danger
{% elif log.action == 'import' %}bg-info
{% elif log.action == 'export' %}bg-secondary
{% else %}bg-warning{% endif %}">
{{ log.get_action_display }}
</span>
</td>
<td>{{ log.description|default:"-" }}</td>
<td>{{ log.operator|default:"-" }}</td>
</tr>
{% empty %}
<tr><td colspan="5" class="text-center text-light opacity-75 py-3">暂无变更记录</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+54
View File
@@ -0,0 +1,54 @@
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 希姆计算资产管理</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet">
</head>
<body class="login-body">
<div class="login-container">
<div class="login-card">
<div class="login-header">
<i class="bi bi-hdd-rack" style="font-size: 3rem;"></i>
<h3 class="mt-3 mb-1">希姆计算资产管理</h3>
<p class="text-muted mb-0">Hardware Asset Management</p>
</div>
<div class="login-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label class="form-label text-muted">用户名</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-person"></i></span>
<input type="text" name="username" class="form-control" placeholder="请输入用户名" required autofocus>
</div>
</div>
<div class="mb-4">
<label class="form-label text-muted">密码</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-lock"></i></span>
<input type="password" name="password" class="form-control" placeholder="请输入密码" required>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 btn-login">
<i class="bi bi-box-arrow-in-right me-2"></i>登 录
</button>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>