mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-30 01:27:35 +08:00
删除无用文件
This commit is contained in:
parent
425be77b56
commit
f5d4250b05
@ -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>')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 登录接口
|
||||
|
@ -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"> </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>
|
@ -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"> </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>
|
@ -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"> </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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user