Files
icac/crawler/templates/crawler/dashboard.html
2025-09-26 15:55:48 +08:00

375 lines
18 KiB
HTML

{% extends 'crawler/base.html' %}
{% load custom_filters %}
{% block title %}仪表板 - 网站爬虫系统{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<h1 class="mb-4">
<i class="bi bi-speedometer2"></i> 系统仪表板
</h1>
</div>
</div>
<!-- 统计卡片 -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card stats-card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title">{{ stats.total_websites }}</h4>
<p class="card-text">监控网站</p>
</div>
<div class="align-self-center">
<i class="bi bi-globe fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card stats-card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title">{{ stats.total_tasks }}</h4>
<p class="card-text">爬取任务</p>
</div>
<div class="align-self-center">
<i class="bi bi-list-task fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card stats-card bg-info text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title">{{ stats.total_contents }}</h4>
<p class="card-text">爬取内容</p>
</div>
<div class="align-self-center">
<i class="bi bi-file-text fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card stats-card bg-warning text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title">{{ stats.active_tasks }}</h4>
<p class="card-text">运行中任务</p>
</div>
<div class="align-self-center">
<i class="bi bi-arrow-clockwise fs-1"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 网站筛选和分页控制 -->
<div class="col-12 mb-3">
<div class="card">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-4">
<label for="website" class="form-label">网站筛选</label>
<select name="website" id="website" class="form-select" onchange="this.form.submit()">
<option value="">所有网站</option>
{% for website in stats.websites %}
<option value="{{ website.id }}" {% if website.id == stats.selected_website_id %}selected{% endif %}>
{{ website.name }} ({{ website.region }})
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label for="page_size" class="form-label">每页条数</label>
<select name="page_size" id="page_size" class="form-select" onchange="this.form.submit()">
<option value="10" {% if stats.page_size == 10 %}selected{% endif %}>10条/页</option>
<option value="20" {% if stats.page_size == 20 %}selected{% endif %}>20条/页</option>
<option value="50" {% if stats.page_size == 50 %}selected{% endif %}>50条/页</option>
<option value="100" {% if stats.page_size == 100 %}selected{% endif %}>100条/页</option>
</select>
</div>
<div class="col-md-2">
<label for="start_date" class="form-label">开始日期</label>
<input type="date"
class="form-control"
id="start_date"
name="start_date"
value="{{ stats.start_date }}">
</div>
<div class="col-md-2">
<label for="end_date" class="form-label">结束日期</label>
<input type="date"
class="form-control"
id="end_date"
name="end_date"
value="{{ stats.end_date }}">
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="btn-group" role="group">
<button type="submit" class="btn btn-primary">
<i class="bi bi-funnel"></i> 筛选
</button>
<a href="/" class="btn btn-outline-secondary">
<i class="bi bi-x-circle"></i> 清除
</a>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- 按网站分类显示内容 -->
<div class="col-md-8">
<form id="download-form" method="post" action="{% url 'download_selected_contents' %}">
{% csrf_token %}
<!-- 批量操作按钮 -->
<div class="card mb-3">
<div class="card-body">
<button type="submit" class="btn btn-primary" id="download-selected" disabled>
<i class="bi bi-download"></i> 批量下载选中文章
</button>
<button type="button" class="btn btn-outline-secondary" id="select-all">
<i class="bi bi-check-all"></i> 全选
</button>
<button type="button" class="btn btn-outline-secondary" id="deselect-all">
<i class="bi bi-x-circle"></i> 取消全选
</button>
</div>
</div>
{% for website_name, contents in stats.contents_by_website.items %}
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="bi bi-globe"></i> {{ website_name }}
<span class="badge bg-secondary">{{ contents|length }}</span>
</h5>
</div>
<div class="card-body">
<div class="list-group list-group-flush">
{% for content in contents %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">
<input type="checkbox" name="selected_contents" value="{{ content.id }}" class="me-2 article-checkbox">
{% if content.is_local_saved %}
<a href="{% url 'preview_crawled_content' content.id %}" target="_blank" class="text-decoration-none">
{{ content.title|truncatechars:60 }}
</a>
{% else %}
<a href="{{ content.url }}" target="_blank" class="text-decoration-none">
{{ content.title|truncatechars:60 }}
</a>
{% endif %}
</h6>
<small class="text-muted">{{ content.created_at|date:"m-d H:i" }}</small>
</div>
<p class="mb-1 content-preview">{{ content.content|truncatechars:100 }}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="bi bi-geo-alt"></i> {{ content.website.region }}
{% if content.media_files.count > 0 %}
| <i class="bi bi-image"></i> {{ content.media_files.count }} 个媒体文件
{% endif %}
</small>
<div>
<a href="{% url 'download_crawled_content' content.id %}" class="btn btn-sm btn-outline-primary" title="下载">
<i class="bi bi-download"></i>
</a>
{% for keyword in content.keywords_matched|split:"," %}
<span class="keyword-badge">{{ keyword|strip }}</span>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% empty %}
<div class="card">
<div class="card-body text-center">
<p class="text-muted py-3">暂无爬取内容</p>
</div>
</div>
{% endfor %}
</form>
<!-- 分页信息 -->
{% if stats.page_obj.has_other_pages %}
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
显示第 {{ stats.page_obj.start_index }} 到 {{ stats.page_obj.end_index }} 条,共 {{ stats.page_obj.paginator.count }} 条记录
</div>
<div>
<!-- 分页导航(重复显示,方便用户操作) -->
<nav aria-label="页面导航">
<ul class="pagination mb-0">
{% if stats.page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ stats.page_obj.previous_page_number }}{% if stats.selected_website_id %}&website={{ stats.selected_website_id }}{% endif %}{% if stats.page_size %}&page_size={{ stats.page_size }}{% endif %}{% if stats.start_date %}&start_date={{ stats.start_date }}{% endif %}{% if stats.end_date %}&end_date={{ stats.end_date }}{% endif %}" aria-label="上一页">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% endif %}
{% for num in stats.page_obj.paginator.page_range %}
{% if stats.page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > stats.page_obj.number|add:'-3' and num < stats.page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if stats.selected_website_id %}&website={{ stats.selected_website_id }}{% endif %}{% if stats.page_size %}&page_size={{ stats.page_size }}{% endif %}{% if stats.start_date %}&start_date={{ stats.start_date }}{% endif %}{% if stats.end_date %}&end_date={{ stats.end_date }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if stats.page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ stats.page_obj.next_page_number }}{% if stats.selected_website_id %}&website={{ stats.selected_website_id }}{% endif %}{% if stats.page_size %}&page_size={{ stats.page_size }}{% endif %}{% if stats.start_date %}&start_date={{ stats.start_date }}{% endif %}{% if stats.end_date %}&end_date={{ stats.end_date }}{% endif %}" aria-label="下一页">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<!-- 最近的任务 -->
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="bi bi-list-check"></i> 最近的任务
</h5>
</div>
<div class="card-body">
{% if stats.recent_tasks %}
<div class="list-group list-group-flush">
{% for task in stats.recent_tasks %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">{{ task.name|truncatechars:30 }}</h6>
<span class="badge bg-{% if task.status == 'completed' %}success{% elif task.status == 'failed' %}danger{% elif task.status == 'running' %}warning{% else %}secondary{% endif %}">
{{ task.get_status_display }}
</span>
</div>
<p class="mb-1">
<small class="text-muted">关键字: {{ task.keywords|truncatechars:40 }}</small>
</p>
<small class="text-muted">{{ task.created_at|date:"m-d H:i" }}</small>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted text-center py-3">暂无任务</p>
{% endif %}
</div>
</div>
</div>
</div>
<!-- 快速操作 -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="bi bi-lightning"></i> 快速操作
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 mb-3">
<a href="{% url 'search' %}" class="btn btn-primary w-100">
<i class="bi bi-search"></i> 搜索内容
</a>
</div>
<div class="col-md-4 mb-3">
<a href="/admin/crawler/crawltask/add/" class="btn btn-success w-100">
<i class="bi bi-plus-circle"></i> 创建任务
</a>
</div>
<div class="col-md-4 mb-3">
<a href="/admin/" class="btn btn-outline-secondary w-100">
<i class="bi bi-gear"></i> 管理后台
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 更新选中文章数量显示和批量下载按钮状态
function updateSelectionStatus() {
const selectedCount = document.querySelectorAll('.article-checkbox:checked').length;
const downloadButton = document.getElementById('download-selected');
if (selectedCount > 0) {
downloadButton.disabled = false;
downloadButton.innerHTML = `<i class="bi bi-download"></i> 批量下载 (${selectedCount})`;
} else {
downloadButton.disabled = true;
downloadButton.innerHTML = '<i class="bi bi-download"></i> 批量下载选中文章';
}
}
// 全选功能
document.getElementById('select-all').addEventListener('click', function() {
document.querySelectorAll('.article-checkbox').forEach(checkbox => {
checkbox.checked = true;
});
updateSelectionStatus();
});
// 取消全选功能
document.getElementById('deselect-all').addEventListener('click', function() {
document.querySelectorAll('.article-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
updateSelectionStatus();
});
// 监听复选框变化
document.querySelectorAll('.article-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', updateSelectionStatus);
});
// 初始化状态
updateSelectionStatus();
</script>
{% endblock %}