mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-02 12:37:35 +08:00
541 lines
21 KiB
HTML
541 lines
21 KiB
HTML
<!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="keywords">keywords - 关键字表</option>
|
|
<option value="default_replies">default_replies - 默认回复表</option>
|
|
<option value="ai_reply_settings">ai_reply_settings - AI回复设置表</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="email_verifications">email_verifications - 邮箱验证表</option>
|
|
<option value="captcha_codes">captcha_codes - 验证码表</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账号表',
|
|
'keywords': '关键字表',
|
|
'default_replies': '默认回复表',
|
|
'ai_reply_settings': 'AI回复设置表',
|
|
'message_notifications': '消息通知表',
|
|
'cards': '卡券表',
|
|
'delivery_rules': '发货规则表',
|
|
'notification_channels': '通知渠道表',
|
|
'user_settings': '用户设置表',
|
|
'email_verifications': '邮箱验证表',
|
|
'captcha_codes': '验证码表'
|
|
};
|
|
|
|
// 页面加载完成后初始化
|
|
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>
|