删除无用文件

This commit is contained in:
zhinianboke 2025-08-14 15:03:11 +08:00
parent 425be77b56
commit f5d4250b05
5 changed files with 7 additions and 2407 deletions

View File

@ -444,51 +444,18 @@ async def admin_page():
return HTMLResponse(f.read())
# 用户管理页面路由
@app.get('/user_management.html', response_class=HTMLResponse)
async def user_management_page():
page_path = os.path.join(static_dir, 'user_management.html')
if os.path.exists(page_path):
with open(page_path, 'r', encoding='utf-8') as f:
return HTMLResponse(f.read())
else:
return HTMLResponse('<h3>User management page not found</h3>')
# 日志管理页面路由
@app.get('/log_management.html', response_class=HTMLResponse)
async def log_management_page():
page_path = os.path.join(static_dir, 'log_management.html')
if os.path.exists(page_path):
with open(page_path, 'r', encoding='utf-8') as f:
return HTMLResponse(f.read())
else:
return HTMLResponse('<h3>Log management page not found</h3>')
# 数据管理页面路由
@app.get('/data_management.html', response_class=HTMLResponse)
async def data_management_page():
page_path = os.path.join(static_dir, 'data_management.html')
if os.path.exists(page_path):
with open(page_path, 'r', encoding='utf-8') as f:
return HTMLResponse(f.read())
else:
return HTMLResponse('<h3>Data management page not found</h3>')
# 商品搜索页面路由
@app.get('/item_search.html', response_class=HTMLResponse)
async def item_search_page():
page_path = os.path.join(static_dir, 'item_search.html')
if os.path.exists(page_path):
with open(page_path, 'r', encoding='utf-8') as f:
return HTMLResponse(f.read())
else:
return HTMLResponse('<h3>Item search page not found</h3>')
# 登录接口

View File

@ -1,556 +0,0 @@
<!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="/static/lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/static/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
<style>
.admin-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.table-container {
max-height: 600px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
}
.table th {
position: sticky;
top: 0;
background: #f8f9fa;
z-index: 10;
}
.btn-delete {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.table-info {
background: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 1rem;
margin-bottom: 1rem;
}
.data-stats {
background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
color: white;
border: none;
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">
<i class="bi bi-robot"></i> 闲鱼自动回复系统
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/">
<i class="bi bi-house"></i> 首页
</a>
<a class="nav-link" href="/user_management.html">
<i class="bi bi-people"></i> 用户管理
</a>
<a class="nav-link active" href="/data_management.html">
<i class="bi bi-database"></i> 数据管理
</a>
<a class="nav-link" href="/log_management.html">
<i class="bi bi-file-text"></i> 日志管理
</a>
<a class="nav-link" href="#" onclick="logout()">
<i class="bi bi-box-arrow-right"></i> 退出
</a>
</div>
</div>
</nav>
<!-- 管理员标题 -->
<div class="admin-header">
<div class="container">
<h1 class="mb-0">
<i class="bi bi-database"></i> 数据管理
</h1>
<p class="mb-0 mt-2">查看和管理数据库中的所有表数据</p>
</div>
</div>
<div class="container">
<!-- 表选择器 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="bi bi-table"></i> 数据表选择
</h5>
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-6">
<label class="form-label">选择数据表</label>
<select class="form-select" id="tableSelect" onchange="loadTableData()">
<option value="">请选择数据表...</option>
<option value="users">users - 用户表</option>
<option value="cookies">cookies - Cookie账号表</option>
<option value="cookie_status">cookie_status - Cookie状态表</option>
<option value="keywords">keywords - 关键字表</option>
<option value="default_replies">default_replies - 默认回复表</option>
<option value="item_replay">item_replay - 指定商品回复表</option>
<option value="default_reply_records">default_reply_records - 默认回复记录表</option>
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</option>
<option value="ai_conversations">ai_conversations - AI对话历史表</option>
<option value="ai_item_cache">ai_item_cache - AI商品信息缓存表</option>
<option value="item_info">item_info - 商品信息表</option>
<option value="message_notifications">message_notifications - 消息通知表</option>
<option value="cards">cards - 卡券表</option>
<option value="delivery_rules">delivery_rules - 发货规则表</option>
<option value="notification_channels">notification_channels - 通知渠道表</option>
<option value="user_settings">user_settings - 用户设置表</option>
<option value="system_settings">system_settings - 系统设置表</option>
<option value="email_verifications">email_verifications - 邮箱验证表</option>
<option value="captcha_codes">captcha_codes - 验证码表</option>
<option value="orders">orders - 订单表</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">数据统计</label>
<div class="card data-stats">
<div class="card-body text-center py-2">
<h5 id="recordCount" class="mb-0">-</h5>
<small>条记录</small>
</div>
</div>
</div>
<div class="col-md-3">
<label class="form-label">&nbsp;</label>
<div class="d-grid">
<button class="btn btn-primary" onclick="refreshTableData()">
<i class="bi bi-arrow-clockwise"></i> 刷新数据
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 表信息 -->
<div id="tableInfo" class="table-info" style="display: none;">
<h6 class="mb-2">
<i class="bi bi-info-circle"></i> 表信息
</h6>
<div class="row">
<div class="col-md-4">
<strong>表名:</strong> <span id="tableName">-</span>
</div>
<div class="col-md-4">
<strong>中文名:</strong> <span id="tableDescription">-</span>
</div>
<div class="col-md-4">
<strong>记录数:</strong> <span id="tableRecordCount">-</span>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-table"></i> 数据内容
</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-warning" onclick="confirmDeleteAll()" style="display: none;" id="deleteAllBtn">
<i class="bi bi-trash"></i> 清空表
</button>
</div>
</div>
<div class="card-body p-0">
<div id="loadingData" class="text-center py-4" style="display: none;">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载数据...</p>
</div>
<div id="noTableSelected" class="text-center py-4">
<i class="bi bi-table" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-2 text-muted">请选择要查看的数据表</p>
</div>
<div id="noData" class="text-center py-4" style="display: none;">
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-2 text-muted">该表暂无数据</p>
</div>
<div id="tableContainer" class="table-container" style="display: none;">
<table class="table table-striped table-hover mb-0">
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 删除确认模态框 -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-exclamation-triangle text-warning"></i> 确认删除
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>您确定要删除这条记录吗?</p>
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
<strong>警告:</strong>此操作不可恢复!
</div>
<div id="deleteRecordInfo" class="bg-light p-2 rounded">
<!-- 记录信息将在这里显示 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">
<i class="bi bi-trash"></i> 确认删除
</button>
</div>
</div>
</div>
</div>
<!-- 清空表确认模态框 -->
<div class="modal fade" id="deleteAllModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-exclamation-triangle text-danger"></i> 确认清空表
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>您确定要清空整个表的所有数据吗?</p>
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i>
<strong>危险操作:</strong>这将删除表中的所有记录,此操作不可恢复!
</div>
<p>表名: <strong id="deleteAllTableName">-</strong></p>
<p>记录数: <strong id="deleteAllRecordCount">-</strong></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-danger" onclick="confirmDeleteAll()">
<i class="bi bi-trash"></i> 确认清空
</button>
</div>
</div>
</div>
</div>
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script>
let currentTable = '';
let currentData = [];
let deleteModal = null;
let deleteAllModal = null;
let currentDeleteId = null;
// 表的中文描述
const tableDescriptions = {
'users': '用户表',
'cookies': 'Cookie账号表',
'cookie_status': 'Cookie状态表',
'keywords': '关键字表',
'item_replay': '指定商品回复表',
'default_replies': '默认回复表',
'default_reply_records': '默认回复记录表',
'ai_reply_settings': 'AI回复设置表',
'ai_conversations': 'AI对话历史表',
'ai_item_cache': 'AI商品信息缓存表',
'item_info': '商品信息表',
'message_notifications': '消息通知表',
'cards': '卡券表',
'delivery_rules': '发货规则表',
'notification_channels': '通知渠道表',
'user_settings': '用户设置表',
'system_settings': '系统设置表',
'email_verifications': '邮箱验证表',
'captcha_codes': '验证码表',
'orders': '订单表'
};
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 检查管理员权限
checkAdminPermission();
// 初始化模态框
deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
deleteAllModal = new bootstrap.Modal(document.getElementById('deleteAllModal'));
});
// 检查管理员权限
function checkAdminPermission() {
const token = localStorage.getItem('auth_token');
if (!token) {
alert('请先登录');
window.location.href = '/login.html';
return;
}
// 验证token并检查是否为管理员
fetch('/verify', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
if (!data.authenticated) {
alert('登录已过期,请重新登录');
window.location.href = '/login.html';
return;
}
// 检查是否为管理员
if (data.username !== 'admin') {
alert('此功能仅限管理员使用');
window.location.href = '/';
return;
}
})
.catch(error => {
console.error('权限验证失败:', error);
alert('权限验证失败');
window.location.href = '/login.html';
});
}
// 加载表数据
function loadTableData() {
const tableSelect = document.getElementById('tableSelect');
const selectedTable = tableSelect.value;
if (!selectedTable) {
showNoTableSelected();
return;
}
currentTable = selectedTable;
showLoading();
const token = localStorage.getItem('auth_token');
fetch(`/admin/data/${selectedTable}`, {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentData = data.data;
displayTableData(data.data, data.columns);
updateTableInfo(selectedTable, data.data.length);
} else {
alert('加载数据失败: ' + data.message);
showNoData();
}
})
.catch(error => {
console.error('加载数据失败:', error);
alert('加载数据失败');
showNoData();
});
}
// 刷新表数据
function refreshTableData() {
if (currentTable) {
loadTableData();
}
}
// 显示表数据
function displayTableData(data, columns) {
const tableHead = document.getElementById('tableHead');
const tableBody = document.getElementById('tableBody');
// 清空表格
tableHead.innerHTML = '';
tableBody.innerHTML = '';
if (!data || data.length === 0) {
showNoData();
return;
}
// 创建表头
const headerRow = document.createElement('tr');
columns.forEach(column => {
const th = document.createElement('th');
th.textContent = column;
headerRow.appendChild(th);
});
// 添加操作列
const actionTh = document.createElement('th');
actionTh.textContent = '操作';
actionTh.style.width = '100px';
headerRow.appendChild(actionTh);
tableHead.appendChild(headerRow);
// 创建表格数据
data.forEach((row, index) => {
const tr = document.createElement('tr');
columns.forEach(column => {
const td = document.createElement('td');
let value = row[column];
// 处理特殊值
if (value === null || value === undefined) {
value = '-';
} else if (typeof value === 'string' && value.length > 50) {
value = value.substring(0, 50) + '...';
}
td.textContent = value;
tr.appendChild(td);
});
// 添加操作按钮
const actionTd = document.createElement('td');
const deleteBtn = document.createElement('button');
deleteBtn.className = 'btn btn-danger btn-delete';
deleteBtn.innerHTML = '<i class="bi bi-trash"></i>';
deleteBtn.onclick = () => deleteRecord(row, index);
actionTd.appendChild(deleteBtn);
tr.appendChild(actionTd);
tableBody.appendChild(tr);
});
showTableContainer();
updateRecordCount(data.length);
}
// 更新表信息
function updateTableInfo(tableName, recordCount) {
document.getElementById('tableName').textContent = tableName;
document.getElementById('tableDescription').textContent = tableDescriptions[tableName] || '未知表';
document.getElementById('tableRecordCount').textContent = recordCount;
document.getElementById('tableInfo').style.display = 'block';
// 显示/隐藏清空表按钮
const deleteAllBtn = document.getElementById('deleteAllBtn');
if (recordCount > 0) {
deleteAllBtn.style.display = 'inline-block';
} else {
deleteAllBtn.style.display = 'none';
}
}
// 更新记录数
function updateRecordCount(count) {
document.getElementById('recordCount').textContent = count;
}
// 显示状态函数
function showLoading() {
document.getElementById('loadingData').style.display = 'block';
document.getElementById('noTableSelected').style.display = 'none';
document.getElementById('noData').style.display = 'none';
document.getElementById('tableContainer').style.display = 'none';
document.getElementById('tableInfo').style.display = 'none';
}
function showNoTableSelected() {
document.getElementById('loadingData').style.display = 'none';
document.getElementById('noTableSelected').style.display = 'block';
document.getElementById('noData').style.display = 'none';
document.getElementById('tableContainer').style.display = 'none';
document.getElementById('tableInfo').style.display = 'none';
updateRecordCount('-');
}
function showNoData() {
document.getElementById('loadingData').style.display = 'none';
document.getElementById('noTableSelected').style.display = 'none';
document.getElementById('noData').style.display = 'block';
document.getElementById('tableContainer').style.display = 'none';
}
function showTableContainer() {
document.getElementById('loadingData').style.display = 'none';
document.getElementById('noTableSelected').style.display = 'none';
document.getElementById('noData').style.display = 'none';
document.getElementById('tableContainer').style.display = 'block';
}
// 删除记录
function deleteRecord(record, index) {
currentDeleteId = record.id || record.user_id || index;
// 显示记录信息
const deleteRecordInfo = document.getElementById('deleteRecordInfo');
deleteRecordInfo.innerHTML = '';
Object.keys(record).forEach(key => {
const div = document.createElement('div');
div.innerHTML = `<strong>${key}:</strong> ${record[key] || '-'}`;
deleteRecordInfo.appendChild(div);
});
deleteModal.show();
}
// 确认删除
function confirmDelete() {
if (!currentDeleteId || !currentTable) return;
const token = localStorage.getItem('auth_token');
fetch(`/admin/data/${currentTable}/${currentDeleteId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
deleteModal.hide();
if (data.success) {
alert('删除成功');
loadTableData(); // 重新加载数据
} else {
alert('删除失败: ' + data.message);
}
})
.catch(error => {
console.error('删除失败:', error);
alert('删除失败');
});
}
// 确认清空表
function confirmDeleteAll() {
if (!currentTable) return;
document.getElementById('deleteAllTableName').textContent = currentTable;
document.getElementById('deleteAllRecordCount').textContent = currentData.length;
deleteAllModal.show();
}
// 退出登录
function logout() {
localStorage.removeItem('auth_token');
window.location.href = '/login.html';
}
</script>
</body>
</html>

View File

@ -1,954 +0,0 @@
<!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="/static/lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/static/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
}
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
border: none;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.search-form {
background: white;
padding: 30px;
border-radius: 15px;
margin-bottom: 30px;
}
.item-card {
transition: transform 0.2s;
}
.item-card:hover {
transform: translateY(-2px);
}
.item-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 10px;
}
.price {
color: #e74c3c;
font-weight: bold;
font-size: 1.2em;
}
.seller-name {
color: #6c757d;
font-size: 0.9em;
}
.tags {
margin-top: 10px;
}
.tag {
background-color: #e9ecef;
color: #495057;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8em;
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
}
.loading {
text-align: center;
padding: 50px;
}
.no-results {
text-align: center;
padding: 50px;
color: #6c757d;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 30px;
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="/index.html">
<i class="bi bi-search me-2"></i>
商品搜索
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/index.html">
<i class="bi bi-house"></i> 返回主页
</a>
</div>
</div>
</nav>
<div class="container mt-4">
<!-- 加载提示 -->
<div id="loadingAuth" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">验证登录状态...</span>
</div>
<p class="mt-3">正在验证登录状态...</p>
</div>
<!-- 主要内容(初始隐藏) -->
<div id="mainContent" style="display: none;">
<!-- 搜索表单 -->
<div class="search-form">
<h4 class="mb-4">
<i class="bi bi-search me-2"></i>
闲鱼商品搜索
</h4>
<div class="alert alert-info mb-4">
<i class="bi bi-info-circle me-2"></i>
<strong>功能说明:</strong>
<ul class="mb-0 mt-2">
<li><strong>查询总页数:</strong>输入要获取的页数1-20页系统会一次性获取所有页面的数据</li>
<li><strong>每页显示:</strong>前端分页显示的每页商品数量</li>
<li><strong>数据来源:</strong>使用真实的闲鱼数据,通过浏览器自动化技术获取</li>
</ul>
</div>
<form id="searchForm">
<div class="row">
<div class="col-md-5">
<label for="keyword" class="form-label">搜索关键词</label>
<input type="text" class="form-control" id="keyword" placeholder="请输入商品关键词" required>
</div>
<div class="col-md-3">
<label for="totalPages" class="form-label">查询总页数</label>
<input type="number" class="form-control" id="totalPages" value="1" min="1" max="20" placeholder="输入页数">
</div>
<div class="col-md-2">
<label for="pageSize" class="form-label">每页显示</label>
<select class="form-select" id="pageSize">
<option value="10">10条</option>
<option value="20" selected>20条</option>
<option value="30">30条</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="d-grid">
<button type="submit" class="btn btn-primary">
<i class="bi bi-search me-2"></i>
搜索
</button>
</div>
</div>
</div>
</form>
</div>
<!-- 搜索结果统计 -->
<div id="searchStats" class="alert alert-info" style="display: none;">
<i class="bi bi-info-circle me-2"></i>
<span id="statsText"></span>
</div>
<!-- 加载状态 -->
<div id="loading" class="loading" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">搜索中...</span>
</div>
<p class="mt-3">正在搜索商品,请稍候...</p>
</div>
<!-- 搜索结果 -->
<div id="searchResults" class="row">
<!-- 商品卡片将在这里动态生成 -->
</div>
<!-- 无结果提示 -->
<div id="noResults" class="no-results" style="display: none;">
<i class="bi bi-search" style="font-size: 3em; color: #dee2e6;"></i>
<h5 class="mt-3">没有找到相关商品</h5>
<p class="text-muted">请尝试使用其他关键词搜索</p>
</div>
<!-- 分页 -->
<div id="paginationContainer" class="pagination-container" style="display: none;">
<nav>
<ul class="pagination" id="pagination">
<!-- 分页按钮将在这里动态生成 -->
</ul>
</nav>
</div>
</div>
<!-- 商品详情模态框 -->
<div class="modal fade" id="itemDetailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-box-seam me-2"></i>
商品详情
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="itemDetailContent">
<!-- 详情内容将在这里动态加载 -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
</div> <!-- 结束 mainContent -->
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script>
// 全局变量
let currentPage = 1;
let currentKeyword = '';
let allItems = []; // 存储所有获取的商品数据
let currentPageSize = 20; // 前端分页每页显示数量
let totalResults = 0;
let authToken = localStorage.getItem('auth_token');
const apiBase = location.origin;
// HTML转义函数全局函数
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 显示商品详情(全局函数)
async function showItemDetail(itemId) {
try {
const modal = new bootstrap.Modal(document.getElementById('itemDetailModal'));
const content = document.getElementById('itemDetailContent');
// 显示加载状态
content.innerHTML = `
<div class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载商品详情...</p>
</div>
`;
modal.show();
// 调用API获取商品详情
const headers = {};
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}
const response = await fetch(`${apiBase}/items/detail/${itemId}`, {
headers: headers
});
if (response.ok) {
const result = await response.json();
if (result.success && result.data) {
content.innerHTML = `
<div class="item-detail">
<h6>商品详情</h6>
<pre style="white-space: pre-wrap; font-family: inherit;">${escapeHtml(result.data)}</pre>
</div>
`;
} else {
content.innerHTML = `
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
${escapeHtml(result.message || '获取商品详情失败')}
</div>
`;
}
} else {
content.innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-x-circle me-2"></i>
获取商品详情失败,请稍后重试
</div>
`;
}
} catch (error) {
console.error('获取商品详情失败:', error);
const content = document.getElementById('itemDetailContent');
content.innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-x-circle me-2"></i>
网络错误,请检查网络连接
</div>
`;
}
}
// 检查用户登录状态
async function checkLoginStatus() {
if (!authToken) {
// 未登录,直接重定向到登录页面
redirectToLogin();
return false;
}
// 验证token是否有效
try {
const response = await fetch(`${apiBase}/verify`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const result = await response.json();
if (result.authenticated) {
return true;
}
}
// token无效清除并重定向
localStorage.removeItem('auth_token');
redirectToLogin();
return false;
} catch (error) {
console.error('Token验证失败:', error);
// 网络错误时也清除token并重定向
localStorage.removeItem('auth_token');
redirectToLogin();
return false;
}
}
// 重定向到登录页面
function redirectToLogin() {
// 保存当前页面URL登录后可以返回
const currentUrl = window.location.href;
localStorage.setItem('redirectAfterLogin', currentUrl);
// 立即重定向到登录页面
window.location.href = 'login.html';
}
// 页面加载时立即检查登录状态
(function() {
// 在页面内容加载前就检查登录状态
if (!authToken) {
redirectToLogin();
return;
}
})();
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', async function() {
// 异步检查登录状态验证token有效性
const isLoggedIn = await checkLoginStatus();
if (!isLoggedIn) {
return;
}
// 隐藏加载提示,显示主要内容
document.getElementById('loadingAuth').style.display = 'none';
document.getElementById('mainContent').style.display = 'block';
// 如果已登录,继续原有的初始化逻辑
initializePage();
});
// 初始化页面
function initializePage() {
// 搜索表单提交
const searchForm = document.getElementById('searchForm');
if (searchForm) {
searchForm.addEventListener('submit', function(e) {
e.preventDefault();
const keyword = document.getElementById('keyword').value.trim();
const totalPages = parseInt(document.getElementById('totalPages').value);
const pageSize = parseInt(document.getElementById('pageSize').value);
if (!keyword) {
alert('请输入搜索关键词');
return;
}
if (isNaN(totalPages) || totalPages < 1 || totalPages > 20) {
alert('请输入有效的页数1-20页');
return;
}
currentKeyword = keyword;
currentPageSize = pageSize;
currentPage = 1; // 重置到第一页
searchAllPages(keyword, totalPages);
});
} else {
console.error('找不到搜索表单元素!');
}
// 监听每页显示数量的变化
const pageSizeSelect = document.getElementById('pageSize');
if (pageSizeSelect) {
pageSizeSelect.addEventListener('change', function() {
const newPageSize = parseInt(this.value);
// 如果已经有搜索结果,立即更新分页显示
if (allItems.length > 0) {
currentPageSize = newPageSize;
currentPage = 1; // 重置到第一页
displayCurrentPage();
updateFrontendPagination();
// 更新统计信息
updateStatsDisplay();
}
});
}
// 检查是否有有效的cookies账户
async function checkValidCookies() {
try {
const headers = {};
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}
const response = await fetch(`${apiBase}/cookies/check`, {
headers: headers
});
if (response.ok) {
const data = await response.json();
return data.hasValidCookies;
} else {
return false;
}
} catch (error) {
console.error('检查cookies失败:', error);
return false;
}
}
// 搜索多页商品数据
async function searchAllPages(keyword, totalPages) {
try {
// 检查是否有有效的cookies账户
const hasValidCookies = await checkValidCookies();
if (!hasValidCookies) {
showErrorMessage('搜索失败系统中不存在有效的账户信息。请先在Cookie管理中添加有效的闲鱼账户。');
return;
}
// 显示加载状态
showLoading(true);
hideResults();
// 构建请求头
const headers = {
'Content-Type': 'application/json'
};
// 如果有token则添加认证头
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}
const requestData = {
keyword: keyword,
total_pages: totalPages // 传递总页数参数
};
console.log('发送多页搜索API请求:', {
url: `${apiBase}/items/search_multiple`,
method: 'POST',
headers: headers,
body: requestData
});
const response = await fetch(`${apiBase}/items/search_multiple`, {
method: 'POST',
headers: headers,
body: JSON.stringify(requestData)
});
console.log('API响应状态:', response.status);
if (response.ok) {
const data = await response.json();
console.log('API返回的完整数据:', data);
if (data.success) {
allItems = data.data || [];
totalResults = allItems.length;
console.log('设置allItems:', allItems);
console.log('allItems长度:', allItems.length);
console.log('totalResults:', totalResults);
// 检查是否有错误信息
if (data.error) {
showErrorMessage(`搜索完成,但遇到问题: ${data.error}`);
}
console.log('调用displayPaginatedResults...');
displayPaginatedResults(data);
console.log('调用updateFrontendPagination...');
updateFrontendPagination();
} else {
console.error('API返回success=false:', data);
throw new Error(data.message || data.error || '搜索失败');
}
} else {
const errorText = await response.text();
throw new Error(`服务器错误 (${response.status}): ${errorText}`);
}
} catch (error) {
console.error('搜索失败:', error);
showErrorMessage('搜索失败: ' + error.message);
} finally {
showLoading(false);
}
}
// 搜索单页商品(保留原有功能)
async function searchItems(page = 1) {
console.log('searchItems函数被调用参数:', { page, currentKeyword, currentPageSize });
try {
// 显示加载状态
showLoading(true);
hideResults();
// 构建请求头
const headers = {
'Content-Type': 'application/json'
};
// 如果有token则添加认证头
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}
const requestData = {
keyword: currentKeyword,
page: page,
page_size: 20 // 固定每页20条数据
};
console.log('发送API请求:', {
url: `${apiBase}/items/search`,
method: 'POST',
headers: headers,
body: requestData
});
const response = await fetch(`${apiBase}/items/search`, {
method: 'POST',
headers: headers,
body: JSON.stringify(requestData)
});
console.log('API响应状态:', response.status);
if (response.ok) {
const data = await response.json();
if (data.success) {
displayResults(data);
currentPage = page;
totalResults = data.total;
updatePagination();
} else {
throw new Error(data.message || '搜索失败');
}
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('搜索失败:', error);
showErrorMessage('搜索失败: ' + error.message);
} finally {
showLoading(false);
}
}
// 显示/隐藏加载状态
function showLoading(show) {
document.getElementById('loading').style.display = show ? 'block' : 'none';
}
// 显示错误消息
function showErrorMessage(message) {
// 创建或更新错误提示
let errorAlert = document.getElementById('errorAlert');
if (!errorAlert) {
errorAlert = document.createElement('div');
errorAlert.id = 'errorAlert';
errorAlert.className = 'alert alert-danger alert-dismissible fade show';
errorAlert.innerHTML = `
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="errorMessage"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 插入到搜索表单后面
const searchForm = document.querySelector('.search-form');
searchForm.parentNode.insertBefore(errorAlert, searchForm.nextSibling);
}
document.getElementById('errorMessage').textContent = message;
errorAlert.style.display = 'block';
// 5秒后自动隐藏
setTimeout(() => {
if (errorAlert) {
errorAlert.style.display = 'none';
}
}, 5000);
}
// 隐藏结果
function hideResults() {
document.getElementById('searchResults').innerHTML = '';
document.getElementById('searchStats').style.display = 'none';
document.getElementById('noResults').style.display = 'none';
document.getElementById('paginationContainer').style.display = 'none';
}
// 显示分页搜索结果
function displayPaginatedResults(data) {
console.log('displayPaginatedResults被调用data:', data);
// 显示统计信息
const statsElement = document.getElementById('searchStats');
const statsText = document.getElementById('statsText');
console.log('statsElement:', statsElement);
console.log('statsText:', statsText);
// 检查数据来源
let dataSource = '';
if (data.is_real_data) {
dataSource = ' [真实数据]';
statsElement.className = 'alert alert-success';
} else {
dataSource = ' [数据获取异常]';
statsElement.className = 'alert alert-danger';
}
statsElement.style.display = 'block';
console.log('统计信息已显示');
// 使用统一的统计信息更新函数
console.log('调用updateStatsDisplay...');
updateStatsDisplay();
// 显示当前页的数据
console.log('调用displayCurrentPage...');
displayCurrentPage();
}
} // 结束 initializePage 函数
// ========================= 全局函数 =========================
// 显示当前页数据(全局函数)
function displayCurrentPage() {
console.log('displayCurrentPage被调用');
console.log('allItems:', allItems);
console.log('allItems.length:', allItems.length);
console.log('currentPage:', currentPage);
console.log('currentPageSize:', currentPageSize);
const resultsContainer = document.getElementById('searchResults');
console.log('resultsContainer:', resultsContainer);
// 计算当前页的数据范围
const startIndex = (currentPage - 1) * currentPageSize;
const endIndex = startIndex + currentPageSize;
const currentItems = allItems.slice(startIndex, endIndex);
console.log('startIndex:', startIndex);
console.log('endIndex:', endIndex);
console.log('currentItems:', currentItems);
console.log('currentItems.length:', currentItems.length);
if (currentItems.length === 0) {
console.log('没有数据,显示无结果提示');
document.getElementById('noResults').style.display = 'block';
resultsContainer.innerHTML = '';
return;
}
document.getElementById('noResults').style.display = 'none';
// 生成商品卡片
console.log('生成商品卡片...');
const cardsHtml = currentItems.map(item => createItemCard(item)).join('');
console.log('生成的HTML长度:', cardsHtml.length);
resultsContainer.innerHTML = cardsHtml;
console.log('商品卡片已设置到容器中');
}
// 显示搜索结果(保留原有功能)
function displayResults(data) {
const resultsContainer = document.getElementById('searchResults');
const items = data.data || [];
// 显示统计信息
const statsElement = document.getElementById('searchStats');
const statsText = document.getElementById('statsText');
// 检查数据来源
let dataSource = '';
if (data.is_real_data) {
dataSource = ' [真实数据]';
statsElement.className = 'alert alert-success';
} else {
dataSource = ' [数据获取异常]';
statsElement.className = 'alert alert-danger';
}
statsText.textContent = `搜索"${data.keyword}",第 ${data.page} 页结果,共显示 ${data.data.length} 个商品${dataSource}`;
statsElement.style.display = 'block';
if (items.length === 0) {
document.getElementById('noResults').style.display = 'block';
return;
}
// 生成商品卡片
resultsContainer.innerHTML = items.map(item => createItemCard(item)).join('');
}
// 更新分页提示(保留原有功能)
function updatePagination() {
const paginationContainer = document.getElementById('paginationContainer');
const pagination = document.getElementById('pagination');
// 显示页码选择提示
paginationContainer.style.display = 'block';
let paginationHtml = `
<li class="page-item disabled">
<span class="page-link">
<i class="bi bi-info-circle me-2"></i>
要查看其他页面,请在上方选择页码后重新搜索
</span>
</li>
`;
pagination.innerHTML = paginationHtml;
} // 结束 initializePage 函数
// ========================= 全局函数 =========================
// 跳转到指定页(全局函数,供分页按钮调用)
function goToPage(page) {
currentPage = page;
displayCurrentPage();
updateFrontendPagination();
// 更新统计信息
updateStatsDisplay();
// 滚动到页面顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// 更新统计信息显示(全局函数)
function updateStatsDisplay() {
const statsText = document.getElementById('statsText');
let dataSource = '';
if (allItems.length > 0) {
dataSource = ' [真实数据]';
}
const totalPages = Math.ceil(allItems.length / currentPageSize);
statsText.textContent = `搜索"${currentKeyword}",共获取 ${allItems.length} 个商品${dataSource},当前显示第 ${currentPage}/${totalPages} 页(每页${currentPageSize}条)`;
}
// 更新前端分页(全局函数)
function updateFrontendPagination() {
const totalPages = Math.ceil(allItems.length / currentPageSize);
const paginationContainer = document.getElementById('paginationContainer');
const pagination = document.getElementById('pagination');
if (totalPages <= 1) {
paginationContainer.style.display = 'none';
return;
}
paginationContainer.style.display = 'block';
let paginationHtml = '';
// 上一页
if (currentPage > 1) {
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(${currentPage - 1}); return false;">上一页</a></li>`;
}
// 页码显示逻辑
if (totalPages <= 7) {
// 如果总页数不超过7页显示所有页码
for (let i = 1; i <= totalPages; i++) {
const active = i === currentPage ? 'active' : '';
paginationHtml += `<li class="page-item ${active}"><a class="page-link" href="#" onclick="goToPage(${i}); return false;">${i}</a></li>`;
}
} else {
// 如果总页数超过7页使用省略号显示
if (currentPage <= 4) {
// 当前页在前面,显示 1 2 3 4 5 ... 最后页
for (let i = 1; i <= 5; i++) {
const active = i === currentPage ? 'active' : '';
paginationHtml += `<li class="page-item ${active}"><a class="page-link" href="#" onclick="goToPage(${i}); return false;">${i}</a></li>`;
}
paginationHtml += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(${totalPages}); return false;">${totalPages}</a></li>`;
} else if (currentPage >= totalPages - 3) {
// 当前页在后面,显示 1 ... 倒数5页
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(1); return false;">1</a></li>`;
paginationHtml += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
for (let i = totalPages - 4; i <= totalPages; i++) {
const active = i === currentPage ? 'active' : '';
paginationHtml += `<li class="page-item ${active}"><a class="page-link" href="#" onclick="goToPage(${i}); return false;">${i}</a></li>`;
}
} else {
// 当前页在中间,显示 1 ... 当前页前后2页 ... 最后页
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(1); return false;">1</a></li>`;
paginationHtml += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
for (let i = currentPage - 2; i <= currentPage + 2; i++) {
const active = i === currentPage ? 'active' : '';
paginationHtml += `<li class="page-item ${active}"><a class="page-link" href="#" onclick="goToPage(${i}); return false;">${i}</a></li>`;
}
paginationHtml += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(${totalPages}); return false;">${totalPages}</a></li>`;
}
}
// 下一页
if (currentPage < totalPages) {
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(${currentPage + 1}); return false;">下一页</a></li>`;
}
pagination.innerHTML = paginationHtml;
}
// 创建商品卡片HTML全局函数
function createItemCard(item) {
console.log('createItemCard被调用item数据:', item);
console.log('item的所有字段:', Object.keys(item));
const tags = item.tags ? item.tags.map(tag => `<span class="tag">${escapeHtml(tag)}</span>`).join('') : '';
const imageUrl = item.main_image || 'https://via.placeholder.com/200x200?text=暂无图片';
const wantCount = item.want_count || 0;
console.log('处理后的数据:', {
title: item.title,
price: item.price,
seller_name: item.seller_name,
imageUrl: imageUrl,
wantCount: wantCount
});
return `
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card item-card h-100">
<img src="${escapeHtml(imageUrl)}" class="item-image" alt="${escapeHtml(item.title)}" onerror="this.src='https://via.placeholder.com/200x200?text=图片加载失败'">
<div class="card-body d-flex flex-column">
<h6 class="card-title" title="${escapeHtml(item.title)}">${escapeHtml(item.title.length > 50 ? item.title.substring(0, 50) + '...' : item.title)}</h6>
<div class="price mb-2">${escapeHtml(item.price)}</div>
<div class="seller-name mb-2">
<i class="bi bi-person me-1"></i>
${escapeHtml(item.seller_name)}
</div>
${wantCount > 0 ? `<div class="want-count mb-2">
<i class="bi bi-heart-fill me-1" style="color: #ff6b6b;"></i>
<span class="badge bg-danger">${wantCount}人想要</span>
</div>` : ''}
${item.publish_time ? `<div class="text-muted small mb-2">
<i class="bi bi-clock me-1"></i>
${escapeHtml(item.publish_time)}
</div>` : ''}
<div class="tags mb-3">${tags}</div>
<div class="mt-auto">
<div class="btn-group w-100" role="group">
<a href="${escapeHtml(item.item_url)}" target="_blank" class="btn btn-outline-primary btn-sm">
<i class="bi bi-link-45deg me-1"></i>
查看商品
</a>
<button class="btn btn-outline-info btn-sm" onclick="showItemDetail('${escapeHtml(item.item_id)}')">
<i class="bi bi-info-circle me-1"></i>
详情
</button>
</div>
</div>
</div>
</div>
</div>
`;
}
// 显示商品详情(全局函数)
function showItemDetail(itemId) {
// 查找对应的商品数据
const item = allItems.find(i => i.item_id === itemId);
if (!item) {
alert('商品信息不存在');
return;
}
// 填充模态框内容
const modalBody = document.querySelector('#itemDetailModal .modal-body');
modalBody.innerHTML = `
<div class="row">
<div class="col-md-6">
<img src="${escapeHtml(item.main_image || 'https://via.placeholder.com/400x400?text=暂无图片')}"
class="img-fluid rounded" alt="${escapeHtml(item.title)}"
onerror="this.src='https://via.placeholder.com/400x400?text=图片加载失败'">
</div>
<div class="col-md-6">
<h5>${escapeHtml(item.title)}</h5>
<p class="text-primary fs-4 fw-bold">${escapeHtml(item.price)}</p>
<p><strong>卖家:</strong>${escapeHtml(item.seller_name)}</p>
${item.want_count ? `<p><strong>想要人数:</strong>${item.want_count}人</p>` : ''}
${item.publish_time ? `<p><strong>发布时间:</strong>${escapeHtml(item.publish_time)}</p>` : ''}
${item.tags && item.tags.length > 0 ? `
<p><strong>标签:</strong></p>
<div class="mb-3">
${item.tags.map(tag => `<span class="badge bg-secondary me-1">${escapeHtml(tag)}</span>`).join('')}
</div>
` : ''}
<p><strong>商品链接:</strong></p>
<a href="${escapeHtml(item.item_url)}" target="_blank" class="btn btn-primary">
<i class="bi bi-link-45deg me-1"></i>
在闲鱼中查看
</a>
</div>
</div>
`;
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('itemDetailModal'));
modal.show();
}
</script>
</body>
</html>

View File

@ -1,457 +0,0 @@
<!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="/static/lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/static/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
<style>
.admin-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.log-container {
background: #1e1e1e;
color: #f8f9fa;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
max-height: 600px;
overflow-y: auto;
border-radius: 0.375rem;
}
.log-line {
padding: 0.25rem 0.5rem;
border-bottom: 1px solid #333;
white-space: pre-wrap;
word-break: break-all;
}
.log-line:hover {
background: #2d2d2d;
}
.log-info { color: #17a2b8; }
.log-warning { color: #ffc107; }
.log-error { color: #dc3545; }
.log-debug { color: #6c757d; }
.filter-badge {
cursor: pointer;
transition: all 0.2s;
}
.filter-badge:hover {
transform: scale(1.05);
}
.filter-badge.active {
box-shadow: 0 0 0 2px rgba(255,255,255,0.5);
}
.auto-refresh-indicator {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">
<i class="bi bi-robot"></i> 闲鱼自动回复系统
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/">
<i class="bi bi-house"></i> 首页
</a>
<a class="nav-link" href="/user_management.html">
<i class="bi bi-people"></i> 用户管理
</a>
<a class="nav-link" href="/data_management.html">
<i class="bi bi-database"></i> 数据管理
</a>
<a class="nav-link active" href="/log_management.html">
<i class="bi bi-file-text"></i> 日志管理
</a>
<a class="nav-link" href="#" onclick="logout()">
<i class="bi bi-box-arrow-right"></i> 退出
</a>
</div>
</div>
</nav>
<!-- 管理员标题 -->
<div class="admin-header">
<div class="container">
<h1 class="mb-0">
<i class="bi bi-file-text"></i> 日志管理
</h1>
<p class="mb-0 mt-2">查看和监控系统运行日志</p>
</div>
</div>
<div class="container">
<!-- 控制面板 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="bi bi-sliders"></i> 日志控制
</h5>
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-3">
<label class="form-label">显示行数</label>
<select class="form-select" id="logLines" onchange="loadLogs()">
<option value="50">50行</option>
<option value="100" selected>100行</option>
<option value="200">200行</option>
<option value="500">500行</option>
<option value="1000">1000行</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">日志级别过滤</label>
<div class="d-flex gap-2 flex-wrap">
<span class="badge bg-secondary filter-badge active" data-level="" onclick="filterByLevel('')">
全部
</span>
<span class="badge bg-info filter-badge log-info" data-level="info" onclick="filterByLevel('info')">
INFO
</span>
<span class="badge bg-warning filter-badge log-warning" data-level="warning" onclick="filterByLevel('warning')">
WARNING
</span>
<span class="badge bg-danger filter-badge log-error" data-level="error" onclick="filterByLevel('error')">
ERROR
</span>
<span class="badge bg-secondary filter-badge log-debug" data-level="debug" onclick="filterByLevel('debug')">
DEBUG
</span>
</div>
</div>
<div class="col-md-3">
<label class="form-label">自动刷新</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()">
<label class="form-check-label" for="autoRefresh">
<span id="autoRefreshLabel">关闭</span>
<i id="autoRefreshIcon" class="bi bi-circle" style="display: none;"></i>
</label>
</div>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="d-grid">
<button class="btn btn-primary" onclick="loadLogs()">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 日志信息 -->
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-info-circle"></i> 日志信息
</h5>
<small class="text-muted" id="lastUpdate">最后更新: -</small>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<strong>日志文件:</strong>
<span id="logFile" class="text-muted">-</span>
</div>
<div class="col-md-4">
<strong>显示行数:</strong>
<span id="displayLines" class="text-muted">-</span>
</div>
<div class="col-md-4">
<strong>当前过滤:</strong>
<span id="currentFilter" class="text-muted">全部</span>
</div>
</div>
</div>
</div>
<!-- 日志内容 -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-terminal"></i> 日志内容
</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-secondary" onclick="scrollToTop()">
<i class="bi bi-arrow-up"></i> 顶部
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="scrollToBottom()">
<i class="bi bi-arrow-down"></i> 底部
</button>
</div>
</div>
<div class="card-body p-0">
<div id="loadingLogs" class="text-center py-4">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载日志...</p>
</div>
<div id="logContainer" class="log-container" style="display: none;"></div>
<div id="noLogs" class="text-center py-4" style="display: none;">
<i class="bi bi-file-text" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-2 text-muted">暂无日志数据</p>
</div>
</div>
</div>
</div>
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script>
let autoRefreshInterval = null;
let currentLevel = '';
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 检查管理员权限
checkAdminPermission();
// 加载日志
loadLogs();
});
// 检查管理员权限
function checkAdminPermission() {
const token = localStorage.getItem('auth_token');
if (!token) {
alert('请先登录');
window.location.href = '/login.html';
return;
}
// 验证token并检查是否为管理员
fetch('/verify', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
if (!data.authenticated) {
alert('登录已过期,请重新登录');
window.location.href = '/login.html';
return;
}
// 检查是否为管理员
if (data.username !== 'admin') {
alert('此功能仅限管理员使用');
window.location.href = '/';
return;
}
})
.catch(error => {
console.error('权限验证失败:', error);
alert('权限验证失败');
window.location.href = '/login.html';
});
}
// 加载日志
function loadLogs() {
const token = localStorage.getItem('auth_token');
const lines = document.getElementById('logLines').value;
const level = currentLevel;
const loadingDiv = document.getElementById('loadingLogs');
const logContainer = document.getElementById('logContainer');
const noLogsDiv = document.getElementById('noLogs');
loadingDiv.style.display = 'block';
logContainer.style.display = 'none';
noLogsDiv.style.display = 'none';
let url = `/admin/logs?lines=${lines}`;
if (level) {
url += `&level=${level}`;
}
fetch(url, {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
console.log('API响应状态:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
console.log('API响应数据:', data);
loadingDiv.style.display = 'none';
if (data.success === false) {
console.error('API返回错误:', data.message);
noLogsDiv.innerHTML = `
<div class="text-center p-4 text-danger">
<i class="bi bi-exclamation-triangle fs-1"></i>
<p class="mt-2">加载日志失败</p>
<p class="small">${data.message || '未知错误'}</p>
</div>
`;
noLogsDiv.style.display = 'block';
return;
}
if (data.logs && data.logs.length > 0) {
console.log(`显示 ${data.logs.length} 条日志`);
displayLogs(data.logs);
updateLogInfo(data);
logContainer.style.display = 'block';
} else {
console.log('没有日志数据');
noLogsDiv.innerHTML = `
<div class="text-center p-4 text-muted">
<i class="bi bi-file-text fs-1"></i>
<p class="mt-2">暂无日志数据</p>
</div>
`;
noLogsDiv.style.display = 'block';
}
// 更新最后更新时间
document.getElementById('lastUpdate').textContent =
'最后更新: ' + new Date().toLocaleTimeString('zh-CN');
})
.catch(error => {
console.error('加载日志失败:', error);
loadingDiv.style.display = 'none';
noLogsDiv.innerHTML = `
<div class="text-center p-4 text-danger">
<i class="bi bi-exclamation-triangle fs-1"></i>
<p class="mt-2">加载日志失败</p>
<p class="small">${error.message}</p>
</div>
`;
noLogsDiv.style.display = 'block';
});
}
// 显示日志
function displayLogs(logs) {
const logContainer = document.getElementById('logContainer');
logContainer.innerHTML = '';
// 反转日志数组,让最新的日志显示在最上面
const reversedLogs = [...logs].reverse();
reversedLogs.forEach(log => {
const logLine = document.createElement('div');
logLine.className = 'log-line';
// 根据日志级别添加颜色类
if (log.includes('| INFO |')) {
logLine.classList.add('log-info');
} else if (log.includes('| WARNING |')) {
logLine.classList.add('log-warning');
} else if (log.includes('| ERROR |')) {
logLine.classList.add('log-error');
} else if (log.includes('| DEBUG |')) {
logLine.classList.add('log-debug');
}
logLine.textContent = log;
logContainer.appendChild(logLine);
});
// 自动滚动到顶部(显示最新日志)
scrollToTop();
}
// 更新日志信息
function updateLogInfo(data) {
document.getElementById('logFile').textContent = data.log_file || '-';
document.getElementById('displayLines').textContent = data.total_lines || '-';
}
// 按级别过滤
function filterByLevel(level) {
currentLevel = level;
// 更新过滤按钮状态
document.querySelectorAll('.filter-badge').forEach(badge => {
badge.classList.remove('active');
});
document.querySelector(`[data-level="${level}"]`).classList.add('active');
// 更新当前过滤显示
const filterText = level ? level.toUpperCase() : '全部';
document.getElementById('currentFilter').textContent = filterText;
// 重新加载日志
loadLogs();
}
// 切换自动刷新
function toggleAutoRefresh() {
const autoRefresh = document.getElementById('autoRefresh');
const label = document.getElementById('autoRefreshLabel');
const icon = document.getElementById('autoRefreshIcon');
if (autoRefresh.checked) {
// 开启自动刷新
autoRefreshInterval = setInterval(loadLogs, 5000); // 每5秒刷新
label.textContent = '开启 (5s)';
icon.style.display = 'inline';
icon.classList.add('auto-refresh-indicator');
} else {
// 关闭自动刷新
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
label.textContent = '关闭';
icon.style.display = 'none';
icon.classList.remove('auto-refresh-indicator');
}
}
// 滚动到顶部
function scrollToTop() {
const logContainer = document.getElementById('logContainer');
logContainer.scrollTop = 0;
}
// 滚动到底部
function scrollToBottom() {
const logContainer = document.getElementById('logContainer');
logContainer.scrollTop = logContainer.scrollHeight;
}
// 退出登录
function logout() {
// 清理自动刷新
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
localStorage.removeItem('auth_token');
window.location.href = '/login.html';
}
// 页面卸载时清理定时器
window.addEventListener('beforeunload', function() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
});
</script>
</body>
</html>

View File

@ -1,400 +0,0 @@
<!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="/static/lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/static/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
<style>
.admin-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
}
.user-card {
transition: transform 0.2s;
border: 1px solid #e0e0e0;
}
.user-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.stats-card {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border: none;
}
.btn-danger-custom {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
border: none;
}
.btn-danger-custom:hover {
background: linear-gradient(135deg, #ee5a52 0%, #ff6b6b 100%);
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">
<i class="bi bi-robot"></i> 闲鱼自动回复系统
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/">
<i class="bi bi-house"></i> 首页
</a>
<a class="nav-link active" href="/user_management.html">
<i class="bi bi-people"></i> 用户管理
</a>
<a class="nav-link" href="/data_management.html">
<i class="bi bi-database"></i> 数据管理
</a>
<a class="nav-link" href="/log_management.html">
<i class="bi bi-file-text"></i> 日志管理
</a>
<a class="nav-link" href="#" onclick="logout()">
<i class="bi bi-box-arrow-right"></i> 退出
</a>
</div>
</div>
</nav>
<!-- 管理员标题 -->
<div class="admin-header">
<div class="container">
<h1 class="mb-0">
<i class="bi bi-people"></i> 用户管理
</h1>
<p class="mb-0 mt-2">管理系统中的所有用户账号</p>
</div>
</div>
<div class="container">
<!-- 统计信息 -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card stats-card">
<div class="card-body text-center">
<h3 id="totalUsers">-</h3>
<p class="mb-0">总用户数</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body text-center">
<h3 id="totalCookies">-</h3>
<p class="mb-0">总Cookie数</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body text-center">
<h3 id="totalCards">-</h3>
<p class="mb-0">总卡券数</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body text-center">
<h3 id="systemUptime">运行中</h3>
<p class="mb-0">系统状态</p>
</div>
</div>
</div>
</div>
<!-- 用户列表 -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-people-fill"></i> 用户列表
</h5>
<button class="btn btn-primary" onclick="refreshUsers()">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
</div>
<div class="card-body">
<div id="loadingUsers" class="text-center py-4">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载用户信息...</p>
</div>
<div id="usersList" class="row" style="display: none;"></div>
<div id="noUsers" class="text-center py-4" style="display: none;">
<i class="bi bi-people" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-2 text-muted">暂无用户</p>
</div>
</div>
</div>
</div>
<!-- 删除确认模态框 -->
<div class="modal fade" id="deleteUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-exclamation-triangle text-warning"></i> 确认删除
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>您确定要删除用户 <strong id="deleteUserName"></strong> 吗?</p>
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
<strong>警告:</strong>此操作将删除该用户的所有数据,包括:
<ul class="mt-2 mb-0">
<li>所有Cookie账号</li>
<li>所有卡券</li>
<li>所有关键字和回复设置</li>
<li>所有个人设置</li>
</ul>
<p class="mt-2 mb-0"><strong>此操作不可恢复!</strong></p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-danger-custom" onclick="confirmDeleteUser()">
<i class="bi bi-trash"></i> 确认删除
</button>
</div>
</div>
</div>
</div>
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script>
let currentDeleteUserId = null;
let deleteUserModal = null;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 检查管理员权限
checkAdminPermission();
// 初始化模态框
deleteUserModal = new bootstrap.Modal(document.getElementById('deleteUserModal'));
// 加载数据
loadSystemStats();
loadUsers();
});
// 检查管理员权限
function checkAdminPermission() {
const token = localStorage.getItem('auth_token');
if (!token) {
alert('请先登录');
window.location.href = '/login.html';
return;
}
// 验证token并检查是否为管理员
fetch('/verify', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
if (!data.authenticated) {
alert('登录已过期,请重新登录');
window.location.href = '/login.html';
return;
}
// 检查是否为管理员
if (data.username !== 'admin') {
alert('此功能仅限管理员使用');
window.location.href = '/';
return;
}
})
.catch(error => {
console.error('权限验证失败:', error);
alert('权限验证失败');
window.location.href = '/login.html';
});
}
// 加载系统统计信息
function loadSystemStats() {
const token = localStorage.getItem('auth_token');
fetch('/admin/stats', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
document.getElementById('totalUsers').textContent = data.users.total;
document.getElementById('totalCookies').textContent = data.cookies.total;
document.getElementById('totalCards').textContent = data.cards.total;
})
.catch(error => {
console.error('加载统计信息失败:', error);
});
}
// 加载用户列表
function loadUsers() {
const token = localStorage.getItem('auth_token');
const loadingDiv = document.getElementById('loadingUsers');
const usersListDiv = document.getElementById('usersList');
const noUsersDiv = document.getElementById('noUsers');
loadingDiv.style.display = 'block';
usersListDiv.style.display = 'none';
noUsersDiv.style.display = 'none';
fetch('/admin/users', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
loadingDiv.style.display = 'none';
if (data.users && data.users.length > 0) {
displayUsers(data.users);
usersListDiv.style.display = 'block';
} else {
noUsersDiv.style.display = 'block';
}
})
.catch(error => {
console.error('加载用户列表失败:', error);
loadingDiv.style.display = 'none';
noUsersDiv.style.display = 'block';
alert('加载用户列表失败');
});
}
// 显示用户列表
function displayUsers(users) {
const usersListDiv = document.getElementById('usersList');
usersListDiv.innerHTML = '';
users.forEach(user => {
const userCard = createUserCard(user);
usersListDiv.appendChild(userCard);
});
}
// 创建用户卡片
function createUserCard(user) {
const col = document.createElement('div');
col.className = 'col-md-6 col-lg-4 mb-3';
const isAdmin = user.username === 'admin';
const badgeClass = isAdmin ? 'bg-danger' : 'bg-primary';
const badgeText = isAdmin ? '管理员' : '普通用户';
col.innerHTML = `
<div class="card user-card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="card-title mb-0">
<i class="bi bi-person-circle"></i> ${user.username}
</h6>
<span class="badge ${badgeClass}">${badgeText}</span>
</div>
<p class="card-text text-muted small mb-2">
<i class="bi bi-envelope"></i> ${user.email}
</p>
<div class="row text-center mb-3">
<div class="col">
<small class="text-muted">Cookie数</small>
<div class="fw-bold">${user.cookie_count || 0}</div>
</div>
<div class="col">
<small class="text-muted">卡券数</small>
<div class="fw-bold">${user.card_count || 0}</div>
</div>
</div>
<div class="text-muted small mb-2">
<i class="bi bi-calendar"></i> 注册时间:${formatDate(user.created_at)}
</div>
${!isAdmin ? `
<div class="d-grid">
<button class="btn btn-danger-custom btn-sm" onclick="deleteUser(${user.id}, '${user.username}')">
<i class="bi bi-trash"></i> 删除用户
</button>
</div>
` : `
<div class="text-center">
<small class="text-muted">
<i class="bi bi-shield-check"></i> 管理员账号不可删除
</small>
</div>
`}
</div>
</div>
`;
return col;
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN');
}
// 删除用户
function deleteUser(userId, username) {
currentDeleteUserId = userId;
document.getElementById('deleteUserName').textContent = username;
deleteUserModal.show();
}
// 确认删除用户
function confirmDeleteUser() {
if (!currentDeleteUserId) return;
const token = localStorage.getItem('auth_token');
fetch(`/admin/users/${currentDeleteUserId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
deleteUserModal.hide();
alert(data.message || '用户删除成功');
// 刷新页面数据
loadSystemStats();
loadUsers();
})
.catch(error => {
console.error('删除用户失败:', error);
alert('删除用户失败');
});
}
// 刷新用户列表
function refreshUsers() {
loadSystemStats();
loadUsers();
}
// 退出登录
function logout() {
localStorage.removeItem('auth_token');
window.location.href = '/login.html';
}
</script>
</body>
</html>