This commit is contained in:
DengDai
2025-11-24 10:10:00 +08:00
commit aa516a8d71
37 changed files with 2426 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block title %}申诉管理{% endblock %}
{% block content %}
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">申诉管理</h2>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>#ID</th>
<th>申诉人</th>
<th>关联用户名</th>
<th>状态</th>
<th>最后更新</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for appeal in appeals.items %}
<tr>
<td>{{ appeal.id }}</td>
<td>{{ appeal.appealer.username if appeal.appealer else '未知用户' }}</td>
<td>{{ appeal.blacklist_entry.uid }}</td>
<td>
<span class="badge
{% if 'closed' in appeal.status %} bg-secondary
{% elif 'user' in appeal.status %} bg-warning text-dark
{% else %} bg-info text-dark {% endif %}">
{{ appeal.status }}
</span>
</td>
<td>{{ appeal.updated_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<a href="{{ url_for('main.appeal_detail', appeal_id=appeal.id) }}" class="btn btn-sm btn-outline-primary">查看详情</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center text-muted">暂无申诉记录。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<!-- 分页逻辑 -->
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,71 @@
{% extends "base.html" %}
{% block title %}合作站点管理{% endblock %}
{% block content %}
<h2 class="mb-4">合作站点管理</h2>
<div class="row">
<!-- 添加新站点表单 -->
<div class="col-md-4">
<div class="card">
<div class="card-header">添加新站点</div>
<div class="card-body">
<form method="POST" action="{{ url_for('main.manage_sites') }}" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.name.label(class="form-label") }}
{{ form.name(class="form-control") }}
{% for error in form.name.errors %}
<div class="text-danger small">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.url.label(class="form-label") }}
{{ form.url(class="form-control", placeholder="https://example.com") }}
{% for error in form.url.errors %}
<div class="text-danger small">{{ error }}</div>
{% endfor %}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
</div>
</div>
</div>
<!-- 站点列表 -->
<div class="col-md-8">
<div class="card">
<div class="card-header">站点列表</div>
<div class="list-group list-group-flush">
{% if sites %}
{% for site in sites %}
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ site.name }}</strong>
{% if not site.is_active %}<span class="badge bg-secondary">已禁用</span>{% endif %}
<br>
<small class="text-muted">{{ site.url or '未提供 URL' }}</small>
</div>
<div class="btn-group">
<!-- 切换启用/禁用状态的按钮 -->
<form action="{{ url_for('main.toggle_site_active', site_id=site.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-sm btn-{{ 'warning' if site.is_active else 'success' }}">
{{ '禁用' if site.is_active else '启用' }}
</button>
</form>
<!-- 删除按钮 -->
<form action="{{ url_for('main.delete_site', site_id=site.id) }}" method="POST" class="d-inline ms-2">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除站点 {{ site.name }} 吗?此操作不可逆!');">删除</button>
</form>
</div>
</div>
{% endfor %}
{% else %}
<div class="list-group-item text-center text-muted">暂无合作站点。</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,72 @@
{% extends "base.html" %}
{% block title %}管理后台 - 用户管理{% endblock %}
{% block content %}
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">用户管理</h2>
<p class="text-muted mb-0">在这里您可以管理所有已注册用户的角色和状态。</p>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>用户名 / 邮箱</th>
<th>注册站点 / UID</th>
<th>注册时间</th>
<th style="min-width: 200px;">角色与状态</th>
<th style="min-width: 120px;">操作</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>
<div><strong>{{ user.username }}</strong></div>
<small class="text-muted">{{ user.email }}</small>
</td>
<td>{{ user.pt_site }} / {{ user.uid }}</td>
<td>{{ user.created_at.strftime('%Y-%m-%d') }}</td>
{% if user.id == 1 %}
<td>
<div>角色: <span class="badge bg-danger">{{ user.role }}</span></div>
<div>状态: <span class="badge bg-success">{{ user.status }}</span></div>
</td>
<td>
<button class="btn btn-sm btn-secondary" disabled>不可修改</button>
</td>
{% else %}
<form action="{{ url_for('main.update_user', user_id=user.id) }}" method="POST">
{% set form = forms[user.id] %}
{{ form.hidden_tag() }}
<td>
<div class="input-group input-group-sm mb-2">
<span class="input-group-text">角色</span>
{{ form.role(class="form-select") }}
</div>
<div class="input-group input-group-sm">
<span class="input-group-text">状态</span>
{{ form.status(class="form-select") }}
</div>
</td>
<td>
{{ form.submit(class="btn btn-sm btn-primary") }}
</td>
</form>
{% endif %}
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center text-muted">没有已注册的用户。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block title %}管理后台 - 用户审核{% endblock %}
{% block content %}
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">待审核用户列表</h2>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>用户名</th>
<th>邮箱</th>
<th>所在站点</th>
<th>UID</th>
<th>申请时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.pt_site }}</td>
<td>{{ user.uid }}</td>
<td>{{ user.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<form action="{{ url_for('main.approve_user', user_id=user.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-sm btn-success">批准</button>
</form>
<form action="{{ url_for('main.reject_user', user_id=user.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要拒绝并删除该用户吗?');">拒绝</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center text-muted">目前没有待审核的用户。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,125 @@
{% extends "base.html" %}
{% block title %}举报详情 - #{{ report.id }}{% endblock %}
{% block content %}
<div class="row">
<!-- 左侧信息栏 -->
<div class="col-lg-5">
<div class="card shadow-sm mb-4">
<div class="card-header"><h4 class="mb-0">举报详情 #{{ report.id }}</h4></div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>被举报邮箱:</strong> {{ report.reported_email }}</li>
<li class="list-group-item"><strong>被举报用户名:</strong> {{ report.reported_uid or 'N/A' }}</li>
<li class="list-group-item"><strong>所属站点:</strong> {{ report.reported_pt_site }}</li>
<li class="list-group-item"><strong>举报理由:</strong> {{ report.reason_category }}</li>
<li class="list-group-item"><strong>举报人:</strong> {{ report.reporter.username }}</li>
<li class="list-group-item"><strong>状态:</strong> <strong class="text-capitalize">{{ report.status }}</strong></li>
<li class="list-group-item"><strong>详细描述:</strong><br><span style="white-space: pre-wrap;">{{ report.description }}</span></li>
</ul>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header"><h5 class="mb-0">证据链接</h5></div>
<div class="list-group list-group-flush">
{% for evidence in report.evidences %}
<a href="{{ evidence.file_url }}" target="_blank" class="list-group-item list-group-item-action">
{{ evidence.file_url }}
</a>
{% else %}
<div class="list-group-item text-muted">未提供证据链接。</div>
{% endfor %}
</div>
</div>
{% if current_user.role == 'admin' %}
<div class="card shadow-sm mb-4">
<div class="card-header"><h5 class="mb-0">管理员操作</h5></div>
<div class="card-body d-grid gap-2">
{% if report.status == 'pending' or report.status == 'in_review' %}
<form action="{{ url_for('main.process_report', report_id=report.id, action='confirm') }}" method="POST" class="d-grid">
<button type="submit" class="btn btn-success">确认违规 (加入黑名单)</button>
</form>
<form action="{{ url_for('main.process_report', report_id=report.id, action='invalidate') }}" method="POST" class="d-grid">
<button type="submit" class="btn btn-warning">举报无效</button>
</form>
{% elif report.status == 'approved' %}
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#revokeModal">
撤销批准并移出黑名单
</button>
{% else %}
<p class="text-muted mb-0">该举报已处理完毕,无更多操作。</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
<!-- 右侧评论区 -->
<div class="col-lg-7">
<div class="card shadow-sm">
<div class="card-header"><h5 class="mb-0">审核与讨论</h5></div>
<div class="card-body" style="max-height: 500px; overflow-y: auto;">
{% for comment in comments %}
<div class="d-flex mb-3">
<div class="flex-shrink-0"><span class="badge rounded-pill bg-secondary">{{ comment.author.username }}</span></div>
<div class="flex-grow-1 ms-3">
<div class="p-2 bg-light rounded">
<p class="small mb-0">{{ comment.body | safe }}</p>
</div>
<small class="text-muted">{{ comment.timestamp.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
</div>
{% else %}
<p class="text-muted">暂无审核建议。</p>
{% endfor %}
</div>
{% if report.status == 'pending' or report.status == 'in_review' %}
<div class="card-footer">
<form method="POST">
{{ form.hidden_tag() }}
<div class="input-group">
{{ form.body(class="form-control", placeholder="提交审核建议...") }}
{{ form.submit(class="btn btn-outline-secondary") }}
</div>
</form>
</div>
{% else %}
<div class="card-footer text-center bg-light">
<p class="mb-0 text-muted">举报已处理,评论已关闭。</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- 撤销操作的 Modal -->
<div class="modal fade" id="revokeModal" tabindex="-1" aria-labelledby="revokeModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="revokeModalLabel">撤销举报批准</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url_for('main.revoke_report', report_id=report.id) }}" method="POST">
<div class="modal-body">
{{ revoke_form.hidden_tag() }}
<p>你确定要撤销对举报 #{{ report.id }} 的批准吗?这将把相关用户从黑名单中移除。</p>
<div class="mb-3">
{{ revoke_form.reason.label(class="form-label") }}
{{ revoke_form.reason(class="form-control", rows=3) }}
{% for error in revoke_form.reason.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
{{ revoke_form.submit(class="btn btn-danger") }}
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}管理后台 - 举报列表{% endblock %}
{% block content %}
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">举报列表</h2>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>#ID</th>
<th>被举报邮箱 / 站点</th>
<th>举报人</th>
<th>状态</th>
<th>提交时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for report in reports.items %}
<tr>
<td>{{ report.id }}</td>
<td>
<div><strong>{{ report.reported_email }}</strong></div>
<small class="text-muted">{{ report.reported_pt_site }}</small>
</td>
<td>{{ report.reporter.username }}</td>
<td><span class="badge bg-info text-dark">{{ report.status }}</span></td>
<td>{{ report.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td><a href="{{ url_for('main.report_detail', report_id=report.id) }}" class="btn btn-sm btn-outline-primary">查看详情</a></td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center text-muted">目前没有举报记录。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card-footer">
{# 分页导航 #}
<div class="pagination">
{% if reports.has_prev %}
<a href="{{ url_for('main.report_list', page=reports.prev_num) }}" class="btn btn-sm btn-outline-secondary">&laquo; 上一页</a>
{% endif %}
<span class="mx-2">Page {{ reports.page }} of {{ reports.pages }}.</span>
{% if reports.has_next %}
<a href="{{ url_for('main.report_list', page=reports.next_num) }}" class="btn btn-sm btn-outline-secondary">下一页 &raquo;</a>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,101 @@
{% extends "base.html" %}
{% block title %}申诉详情 - #{{ appeal.id }}{% endblock %}
{% block content %}
<div class="row">
<!-- 左侧信息栏 -->
<div class="col-lg-4">
<div class="card shadow-sm mb-4">
<div class="card-header"><h5 class="mb-0">申诉信息</h5></div>
<div class="card-body">
<p><strong>申诉ID:</strong> #{{ appeal.id }}</p>
<p><strong>申诉人:</strong> {{ appeal.appealer.username }}</p>
<p><strong>状态:</strong>
<span class="badge
{% if 'closed' in appeal.status %} bg-secondary
{% elif 'user' in appeal.status %} bg-warning text-dark
{% else %} bg-info text-dark {% endif %}">
{{ appeal.status }}
</span>
</p>
<hr>
<h6>针对黑名单记录</h6>
<p class="mb-0"><strong>站点:</strong> {{ appeal.blacklist_entry.pt_site }}</p>
<p class="mb-0"><strong>UID:</strong> {{ appeal.blacklist_entry.uid }}</p>
</div>
</div>
{% if current_user.role=='admin' and appeal.status not in ['closed_approved', 'closed_rejected'] %}
<div class="card shadow-sm mb-4">
<div class="card-header"><h5 class="mb-0">管理员操作</h5></div>
<div class="card-body text-center d-grid gap-2">
<form action="{{ url_for('main.decide_appeal', appeal_id=appeal.id) }}" method="POST" class="d-grid">
<input type="hidden" name="action" value="approve">
<button type="submit" class="btn btn-success" onclick="return confirm('确定批准申诉并将该用户移出黑名单吗?')">批准申诉</button>
</form>
<form action="{{ url_for('main.decide_appeal', appeal_id=appeal.id) }}" method="POST" class="d-grid">
<input type="hidden" name="action" value="reject">
<button type="submit" class="btn btn-danger" onclick="return confirm('确定驳回此申诉吗?')">驳回申诉</button>
</form>
</div>
</div>
{% endif %}
</div>
<!-- 右侧对话框 -->
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-header"><h5 class="mb-0">对话历史</h5></div>
<div class="card-body chat-box" style="height: 500px; overflow-y: auto;">
<!-- 初始申诉理由 -->
<div class="message user-message mb-3">
<div class="message-header">
<strong>{{ appeal.appealer.username }}</strong>
<small>{{ appeal.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
<div class="message-body">
<p class="fw-bold">[初始申诉理由]</p>
<p class="mb-0" style="white-space: pre-wrap;">{{ appeal.reason }}</p>
</div>
</div>
<!-- 后续对话 -->
{% for message in messages %}
<div class="message mb-3 {% if message.author_id == appeal.appealer_id %}user-message{% else %}admin-message{% endif %}">
<div class="message-header">
{% if message.author_id.role == 'admin' %}
<strong>{{ message.author_id.username }} (管理员)</strong>
<small>{{ message.timestamp.strftime('%Y-%m-%d %H:%M') }}</small>
{% else %}
<strong>{{ message.author_id.username }}</strong>
<small>{{ message.timestamp.strftime('%Y-%m-%d %H:%M') }}</small>
{% endif %}
</div>
<div class="message-body">
<p class="mb-0" style="white-space: pre-wrap;">{{ message.body }}</p>
</div>
</div>
{% endfor %}
</div>
{% if appeal.status not in ['closed_approved', 'closed_rejected'] %}
<div class="card-footer">
<form method="POST">
{{ form.hidden_tag() }}
<div class="input-group">
{{ form.body(class="form-control", placeholder="输入回复内容...", rows="2") }}
{{ form.submit(class="btn btn-primary") }}
</div>
{% for error in form.body.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</form>
</div>
{% else %}
<div class="card-footer text-center bg-light">
<p class="mb-0 text-muted">该申诉已关闭,无法回复。</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}登录{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card shadow-sm">
<div class="card-body p-4">
<h2 class="card-title text-center mb-4">用户登录</h2>
<form method="POST" action="{{ url_for('auth.login') }}" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% for error in form.password.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3 form-check">
{{ form.remember_me(class="form-check-input") }}
{{ form.remember_me.label(class="form-check-label") }}
</div>
<div class="d-grid">
{{ form.submit(class="btn btn-primary btn-lg") }}
</div>
</form>
<hr>
<p class="text-center mb-0">新用户? <a href="{{ url_for('auth.register') }}">点此注册</a></p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,57 @@
{% extends "base.html" %}
{% block title %}注册{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-sm">
<div class="card-body p-4">
<h2 class="card-title text-center mb-4">创建新账户</h2>
<form method="POST" action="{{ url_for('auth.register') }}" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control") }}
{% for error in form.username.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.pt_site.label(class="form-label") }}
{{ form.pt_site(class="form-control") }}
{% for error in form.pt_site.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="col-md-6 mb-3">
{{ form.uid.label(class="form-label") }}
{{ form.uid(class="form-control") }}
{% for error in form.uid.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% for error in form.password.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="col-md-6 mb-3">
{{ form.password2.label(class="form-label") }}
{{ form.password2(class="form-control") }}
{% for error in form.password2.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
</div>
<div class="d-grid mt-2">
{{ form.submit(class="btn btn-primary btn-lg") }}
</div>
</form>
<hr>
<p class="text-center mb-0">已有账户? <a href="{{ url_for('auth.login') }}">点此登录</a></p>
</div>
</div>
</div>
</div>
{% endblock %}

104
app/templates/base.html Normal file
View File

@@ -0,0 +1,104 @@
<!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 %}PT 黑名单中心{% endblock %}</title>
<link rel="icon" href="{{ url_for('static', filename='images/logo.svg') }}" />
<script src="{{ url_for('static', filename='js/jquery-3.5.1.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<!-- 自定义 CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">
{% block page_css %}{% endblock %}
</head>
<body>
<!-- 响应式导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('main.index') }}">PT 黑名单中心</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">首页查询</a>
</li>
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.create_report') }}">提交举报</a>
</li>
{% if current_user.role != 'user' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.report_list') }}">举报管理</a>
</li>
{% endif %}
{% if current_user.role == 'admin' %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="adminDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
管理员面板
</a>
<ul class="dropdown-menu" aria-labelledby="adminDropdown">
<li><a class="dropdown-item" href="{{ url_for('main.manage_sites') }}">合作站点</a></li>
<li><a class="dropdown-item" href="{{ url_for('main.appeal_list') }}">申诉管理</a></li>
<li><a class="dropdown-item" href="{{ url_for('main.pending_users') }}">用户审核</a></li>
<li><a class="dropdown-item" href="{{ url_for('main.manage_users') }}">用户管理</a></li>
</ul>
</li>
{% endif %}
{% endif %}
</ul>
<!-- 右侧用户菜单 -->
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
你好, {{ current_user.username }}
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<!-- <li><a class="dropdown-item" href="#">我的资料</a></li>
<li><hr class="dropdown-divider"></li> -->
<li><a class="dropdown-item" href="{{ url_for('auth.logout') }}">退出登录</a></li>
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="btn btn-outline-light" href="{{ url_for('auth.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<!-- 主体内容 -->
<main class="container py-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category or 'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer class="container text-center text-muted py-4">
<hr>
<p>&copy; 2025 - now PT Blacklist Center. All Rights Reserved.</p>
</footer>
{% block page_js %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}发起申诉{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">为黑名单记录 #{{ entry.id }} 发起申诉</h2>
</div>
<div class="card-body">
<ul class="list-group list-group-flush mb-4">
<li class="list-group-item"><strong>UID:</strong> {{ entry.uid }}</li>
<li class="list-group-item"><strong>邮箱:</strong> {{ entry.email }}</li>
<li class="list-group-item"><strong>站点:</strong> {{ entry.pt_site }}</li>
{% if entry.report %}
<li class="list-group-item"><strong>违规原因:</strong> {{ entry.report.reason_category }}</li>
{% endif %}
</ul>
<form method="POST" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.reason.label(class="form-label") }}
{{ form.reason(class="form-control", rows=8, placeholder="请详细说明您申诉的理由,并提供任何可以证明您清白的证据或说明。") }}
{% for error in form.reason.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="text-end">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}提交新举报{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-header">
<h2 class="mb-0">提交新举报</h2>
</div>
<div class="card-body">
<p class="text-muted">请填写真实的举报信息,所有提交都将被记录。滥用举报系统可能会导致您的账户被封禁。</p>
<hr>
<form method="POST" action="{{ url_for('main.create_report') }}" novalidate>
{{ form.hidden_tag() }}
<div class="row">
<div class="col-md-6 mb-3">
{{ form.reported_pt_site.label(class="form-label") }}
{{ form.reported_pt_site(class="form-control") }}
{% for error in form.reported_pt_site.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="col-md-6 mb-3">
{{ form.reported_uid.label(class="form-label") }}
{{ form.reported_uid(class="form-control") }}
{% for error in form.reported_uid.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
</div>
<div class="mb-3">
{{ form.reported_email.label(class="form-label") }}
{{ form.reported_email(class="form-control") }}
{% for error in form.reported_email.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="mb-3">
{{ form.reason_category.label(class="form-label") }}
{{ form.reason_category(class="form-select") }}
{% for error in form.reason_category.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="mb-3">
{{ form.description.label(class="form-label") }}
{{ form.description(class="form-control", rows=5) }}
{% for error in form.description.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="mb-3">
{{ form.evidences.label(class="form-label") }}
{{ form.evidences(class="form-control", rows=3, placeholder="每行一个证据链接(请使用图床!)") }}
<div class="form-text">请提供所有相关证据的URL每行一个。</div>
{% for error in form.evidences.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
</div>
<div class="text-end">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

98
app/templates/index.html Normal file
View File

@@ -0,0 +1,98 @@
{% extends "base.html" %}
{% block title %}首页 - 黑名单查询{% endblock %}
{% block content %}
<div class="text-center">
<h1 class="display-5">PT 圈黑名单查询系统</h1>
<p class="lead text-muted">请输入邮箱地址或用户名进行查询。本系统旨在帮助 PTer 发放邀请前识别有不良记录的用户。</p>
</div>
{% if current_user.is_authenticated %}
{# 如果用户已登录,显示查询表单和结果区域 #}
<div class="row justify-content-center mt-4">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-body">
<form method="POST" action="{{ url_for('main.index') }}" novalidate>
{{ form.hidden_tag() }}
<div class="input-group mb-2">
{{ form.search_term(class="form-control form-control-lg", placeholder="例如: user@example.com 或 BadUser123") }}
{{ form.submit(class="btn btn-primary btn-lg") }}
</div>
{% for error in form.search_term.errors %}
<div class="text-danger small">{{ error }}</div>
{% endfor %}
</form>
</div>
</div>
</div>
</div>
{% if searched %}
<hr class="my-4">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">查询结果</h5>
</div>
<div class="card-body">
{% if search_result %}
<div class="alert alert-danger">
<strong>状态: </strong> 发现相关不良记录
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>违规站点:</strong> {{ search_result.pt_site }}</li>
{% if search_result.report %}
<li class="list-group-item"><strong>违规原因:</strong> {{ search_result.report.reason_category }}</li>
{% endif %}
<li class="list-group-item"><strong>记录时间:</strong> {{ search_result.created_at.strftime('%Y-%m-%d') }}</li>
</ul>
<p class="text-muted small mt-3">为保护隐私,仅展示必要的脱敏信息。具体违规描述不对外公开。</p>
{% if current_user.is_authenticated and (current_user.uid == search_result.uid or current_user.email == search_result.email) %}
<hr>
<h6>申诉通道</h6>
{% set active_appeal = search_result.appeals.filter(Appeal.status.in_(['awaiting_admin_reply', 'awaiting_user_reply'])).first() %}
{% if active_appeal %}
<p>您已对该记录发起了申诉,请点击下方按钮查看进展。</p>
<a href="{{ url_for('main.appeal_detail', appeal_id=active_appeal.id) }}" class="btn btn-warning">查看进行中的申诉</a>
{% else %}
<p>如果您认为该记录有误,可以发起申诉,管理员将会与您沟通。</p>
<a href="{{ url_for('main.create_appeal', blacklist_id=search_result.id) }}" class="btn btn-primary">对该记录发起申诉</a>
{% endif %}
{% endif %}
{% else %}
<div class="alert alert-success">
<strong>状态: </strong> 未查询到相关不良记录
</div>
<p class="text-muted">请注意,未查询到记录不代表用户完全清白,也可能是其违规行为尚未被举报。</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
{% else %}
{# 如果用户未登录,显示一个提示框 #}
<div class="row justify-content-center mt-5">
<div class="col-lg-8">
<div class="alert alert-warning text-center" role="alert">
<h4 class="alert-heading">功能受限</h4>
<p>查询功能仅对注册并登录的用户开放。请登录以使用本系统的全部功能。</p>
<hr>
<p class="mb-0">
<a href="{{ url_for('auth.login') }}" class="btn btn-primary">前往登录</a>
<a href="{{ url_for('auth.register') }}" class="btn btn-outline-secondary ms-2">立即注册</a>
</p>
</div>
</div>
</div>
{% endif %}
{% endblock %}