2026-01-13 18:59:26 +08:00
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>MIP广告点击管理系统</title>
|
|
|
|
|
<style>
|
|
|
|
|
* {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
|
|
|
|
background: #f0f2f5;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 布局 */
|
|
|
|
|
.layout {
|
|
|
|
|
display: flex;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 侧边栏 */
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 200px;
|
|
|
|
|
background: #001529;
|
|
|
|
|
color: white;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logo {
|
|
|
|
|
height: 64px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 16px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-item {
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
color: rgba(255, 255, 255, 0.65);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-item:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-item.active {
|
|
|
|
|
background: #1890ff;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-icon {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 主内容区 */
|
|
|
|
|
.main {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 顶部导航栏 */
|
|
|
|
|
.navbar {
|
|
|
|
|
height: 64px;
|
|
|
|
|
background: white;
|
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 0 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breadcrumb {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-indicator {
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: #52c41a;
|
|
|
|
|
animation: pulse 2s infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes pulse {
|
|
|
|
|
0%, 100% { opacity: 1; }
|
|
|
|
|
50% { opacity: 0.5; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 内容区 */
|
|
|
|
|
.content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 卡片 */
|
|
|
|
|
.card {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-body {
|
|
|
|
|
padding: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 统计卡片 */
|
|
|
|
|
.stats-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
gap: 24px;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
background: white;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
|
|
|
border-left: 3px solid #1890ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card:nth-child(2) {
|
|
|
|
|
border-left-color: #52c41a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card:nth-child(3) {
|
|
|
|
|
border-left-color: #faad14;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card:nth-child(4) {
|
|
|
|
|
border-left-color: #f5222d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 30px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 表单 */
|
|
|
|
|
.form-group {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #d9d9d9;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-input:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid #d9d9d9;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
resize: vertical;
|
|
|
|
|
min-height: 100px;
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-textarea:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 按钮 */
|
|
|
|
|
.btn {
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary {
|
|
|
|
|
background: #1890ff;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary:hover {
|
|
|
|
|
background: #40a9ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-success {
|
|
|
|
|
background: #52c41a;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-success:hover {
|
|
|
|
|
background: #73d13d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-danger {
|
|
|
|
|
background: #ff4d4f;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-danger:hover {
|
|
|
|
|
background: #ff7875;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-warning {
|
|
|
|
|
background: #faad14;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-warning:hover {
|
|
|
|
|
background: #ffc53d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 表格 */
|
|
|
|
|
.table-container {
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table th {
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #333;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table td {
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table tr:hover {
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-url {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
max-width: 400px;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-url:hover {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 标签 */
|
|
|
|
|
.tag {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tag-success {
|
|
|
|
|
background: #f6ffed;
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
border: 1px solid #b7eb8f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tag-error {
|
|
|
|
|
background: #fff1f0;
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
border: 1px solid #ffccc7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Toast提示 */
|
|
|
|
|
.toast {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 24px;
|
|
|
|
|
right: 24px;
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
min-width: 300px;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
|
|
animation: slideIn 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toast-success {
|
|
|
|
|
background: #52c41a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toast-error {
|
|
|
|
|
background: #ff4d4f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toast-info {
|
|
|
|
|
background: #1890ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes slideIn {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateX(400px);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 空状态 */
|
|
|
|
|
.empty-state {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 48px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 隐藏内容 */
|
|
|
|
|
.page-content {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-content.active {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="layout">
|
|
|
|
|
<!-- 侧边栏 -->
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
<div class="logo">MIP管理系统</div>
|
|
|
|
|
<div class="menu">
|
|
|
|
|
<div class="menu-item active" onclick="switchPage('dashboard')">
|
|
|
|
|
<span class="menu-icon">📊</span>
|
|
|
|
|
<span>数据概览</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="menu-item" onclick="switchPage('scheduler')">
|
|
|
|
|
<span class="menu-icon">⚙️</span>
|
|
|
|
|
<span>调度器管理</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="menu-item" onclick="switchPage('urls')">
|
|
|
|
|
<span class="menu-icon">🔗</span>
|
|
|
|
|
<span>链接管理</span>
|
|
|
|
|
</div>
|
2026-01-16 22:06:46 +08:00
|
|
|
<div class="menu-item" onclick="switchPage('database')">
|
|
|
|
|
<span class="menu-icon">💾</span>
|
|
|
|
|
<span>数据库管理</span>
|
|
|
|
|
</div>
|
2026-01-13 18:59:26 +08:00
|
|
|
<div class="menu-item" onclick="switchPage('browser')">
|
|
|
|
|
<span class="menu-icon">🌐</span>
|
|
|
|
|
<span>浏览器测试</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 主内容区 -->
|
|
|
|
|
<div class="main">
|
|
|
|
|
<!-- 顶部导航栏 -->
|
|
|
|
|
<div class="navbar">
|
|
|
|
|
<div class="breadcrumb" id="breadcrumb">数据概览</div>
|
|
|
|
|
<div class="user-info">
|
|
|
|
|
<div class="status-indicator" id="statusIndicator"></div>
|
|
|
|
|
<span id="statusText">系统运行中</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 内容区 -->
|
|
|
|
|
<div class="content">
|
|
|
|
|
<!-- 数据概览页面 -->
|
|
|
|
|
<div id="dashboard" class="page-content active">
|
|
|
|
|
<!-- 统计卡片 -->
|
|
|
|
|
<div class="stats-grid">
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">总链接数</div>
|
|
|
|
|
<div class="stat-value" id="totalUrls">0</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">总点击次数</div>
|
|
|
|
|
<div class="stat-value" id="totalClicks">0</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">获得回复</div>
|
|
|
|
|
<div class="stat-value" id="totalReplies">0</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">回复率</div>
|
|
|
|
|
<div class="stat-value" id="replyRate">0%</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 链接列表 -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">链接列表</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table class="table" id="urlTable">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>链接地址</th>
|
|
|
|
|
<th>点击次数</th>
|
|
|
|
|
<th>回复次数</th>
|
|
|
|
|
<th>上次点击时间</th>
|
|
|
|
|
<th>操作</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="urlTableBody">
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="5" class="empty-state">暂无数据</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 调度器管理页面 -->
|
|
|
|
|
<div id="scheduler" class="page-content">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">调度器控制</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="btn-group">
|
|
|
|
|
<button class="btn btn-success" onclick="startScheduler()">启动调度器</button>
|
|
|
|
|
<button class="btn btn-danger" onclick="stopScheduler()">停止调度器</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="padding: 16px; background: #fafafa; border-radius: 2px; margin-top: 16px;">
|
|
|
|
|
<h4 style="margin-bottom: 12px; font-size: 14px;">调度规则说明</h4>
|
|
|
|
|
<ul style="padding-left: 20px; line-height: 2;">
|
|
|
|
|
<li>每30分钟点击一次添加的链接</li>
|
|
|
|
|
<li>仅在09:00-21:00时间段执行</li>
|
|
|
|
|
<li>每个链接随机点击1-10次</li>
|
|
|
|
|
<li>等待最多30秒查看回复</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 链接管理页面 -->
|
|
|
|
|
<div id="urls" class="page-content">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">添加单个链接</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">链接地址</label>
|
|
|
|
|
<input type="text" id="singleUrl" class="form-input" placeholder="请输入MIP页面链接" />
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-primary" onclick="addSingleUrl()">添加链接</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">批量添加链接</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">批量链接(每行一个)</label>
|
|
|
|
|
<textarea id="batchUrls" class="form-textarea" placeholder="每行输入一个链接,支持批量添加"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-primary" onclick="addBatchUrls()">批量添加</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 浏览器测试页面 -->
|
|
|
|
|
<div id="browser" class="page-content">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">AdsPower浏览器测试</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<p style="color: #666; margin-bottom: 16px;">此功能用于测试AdsPower浏览器连接是否正常</p>
|
|
|
|
|
<div class="btn-group">
|
|
|
|
|
<button class="btn btn-primary" onclick="testBrowser(false)">测试浏览器(不使用代理)</button>
|
|
|
|
|
<button class="btn btn-warning" onclick="testBrowser(true)">测试浏览器(使用代理)</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="browserTestResult" style="margin-top: 16px;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-16 22:06:46 +08:00
|
|
|
|
|
|
|
|
<!-- 数据库管理页面 -->
|
|
|
|
|
<div id="database" class="page-content">
|
|
|
|
|
<!-- 统计卡片 -->
|
|
|
|
|
<div class="stats-grid" style="margin-bottom: 24px;">
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">总站点数</div>
|
|
|
|
|
<div class="stat-value" id="dbTotalSites">-</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">总点击数</div>
|
|
|
|
|
<div class="stat-value" id="dbTotalClicks">-</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">总回复数</div>
|
|
|
|
|
<div class="stat-value" id="dbTotalReplies">-</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-card">
|
|
|
|
|
<div class="stat-label">成功互动</div>
|
|
|
|
|
<div class="stat-value" id="dbSuccessful">-</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 数据表切换 -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<div style="display: flex; gap: 12px;">
|
|
|
|
|
<button class="btn btn-primary" id="btnShowSites" onclick="showDatabaseView('sites')">站点列表</button>
|
|
|
|
|
<button class="btn" id="btnShowClicks" onclick="showDatabaseView('clicks')">点击记录</button>
|
|
|
|
|
<button class="btn" id="btnShowInteractions" onclick="showDatabaseView('interactions')">互动记录</button>
|
|
|
|
|
<button class="btn btn-success" onclick="refreshDatabaseData()" style="margin-left: auto;">🔄 刷新</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<!-- 站点列表 -->
|
|
|
|
|
<div id="dbSitesView" class="db-view">
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table class="table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>站点名称</th>
|
|
|
|
|
<th>URL</th>
|
|
|
|
|
<th>状态</th>
|
|
|
|
|
<th>点击数</th>
|
|
|
|
|
<th>回复数</th>
|
|
|
|
|
<th>创建时间</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="dbSitesTableBody">
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="7" class="empty-state">加载中...</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 点击记录 -->
|
|
|
|
|
<div id="dbClicksView" class="db-view" style="display: none;">
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table class="table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>站点ID</th>
|
|
|
|
|
<th>URL</th>
|
|
|
|
|
<th>点击时间</th>
|
|
|
|
|
<th>设备类型</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="dbClicksTableBody">
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="5" class="empty-state">加载中...</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 互动记录 -->
|
|
|
|
|
<div id="dbInteractionsView" class="db-view" style="display: none;">
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
<table class="table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>站点ID</th>
|
|
|
|
|
<th>互动时间</th>
|
|
|
|
|
<th>发送内容</th>
|
|
|
|
|
<th>收到回复</th>
|
|
|
|
|
<th>状态</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="dbInteractionsTableBody">
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="6" class="empty-state">加载中...</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-13 18:59:26 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
const API_BASE = 'http://127.0.0.1:5000';
|
|
|
|
|
|
|
|
|
|
// 页面切换
|
|
|
|
|
function switchPage(page) {
|
|
|
|
|
// 隐藏所有页面
|
|
|
|
|
document.querySelectorAll('.page-content').forEach(p => p.classList.remove('active'));
|
|
|
|
|
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
|
|
|
|
|
|
|
|
|
|
// 显示目标页面
|
|
|
|
|
document.getElementById(page).classList.add('active');
|
|
|
|
|
event.currentTarget.classList.add('active');
|
|
|
|
|
|
|
|
|
|
// 更新面包屑
|
|
|
|
|
const breadcrumbMap = {
|
|
|
|
|
'dashboard': '数据概览',
|
|
|
|
|
'scheduler': '调度器管理',
|
|
|
|
|
'urls': '链接管理',
|
2026-01-16 22:06:46 +08:00
|
|
|
'database': '数据库管理',
|
2026-01-13 18:59:26 +08:00
|
|
|
'browser': '浏览器测试'
|
|
|
|
|
};
|
|
|
|
|
document.getElementById('breadcrumb').textContent = breadcrumbMap[page];
|
2026-01-16 22:06:46 +08:00
|
|
|
|
|
|
|
|
// 如果切换到数据库页面,加载数据
|
|
|
|
|
if (page === 'database') {
|
|
|
|
|
loadDatabaseData();
|
|
|
|
|
}
|
2026-01-13 18:59:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示Toast提示
|
|
|
|
|
function showToast(message, type = 'info') {
|
|
|
|
|
const toast = document.createElement('div');
|
|
|
|
|
toast.className = `toast toast-${type}`;
|
|
|
|
|
toast.textContent = message;
|
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
toast.remove();
|
|
|
|
|
}, 3000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取调度器状态
|
|
|
|
|
async function getSchedulerStatus() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/scheduler/status`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
const isRunning = data.data.status === 'running';
|
|
|
|
|
const indicator = document.getElementById('statusIndicator');
|
|
|
|
|
const text = document.getElementById('statusText');
|
|
|
|
|
|
|
|
|
|
indicator.style.background = isRunning ? '#52c41a' : '#ff4d4f';
|
|
|
|
|
text.textContent = isRunning ? '调度器运行中' : '调度器已停止';
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取调度器状态失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 启动调度器
|
|
|
|
|
async function startScheduler() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/scheduler/start`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
});
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
showToast('调度器已启动', 'success');
|
|
|
|
|
getSchedulerStatus();
|
|
|
|
|
} else {
|
|
|
|
|
showToast(data.message || '启动失败', 'error');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('启动失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 停止调度器
|
|
|
|
|
async function stopScheduler() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/scheduler/stop`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
});
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
showToast('调度器已停止', 'success');
|
|
|
|
|
getSchedulerStatus();
|
|
|
|
|
} else {
|
|
|
|
|
showToast(data.message || '停止失败', 'error');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('停止失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取统计数据
|
|
|
|
|
async function getStatistics() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/statistics`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
const stats = data.data;
|
|
|
|
|
document.getElementById('totalUrls').textContent = stats.total_urls;
|
|
|
|
|
document.getElementById('totalClicks').textContent = stats.total_clicks;
|
|
|
|
|
document.getElementById('totalReplies').textContent = stats.total_replies;
|
|
|
|
|
document.getElementById('replyRate').textContent = stats.reply_rate;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取统计数据失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加单个URL
|
|
|
|
|
async function addSingleUrl() {
|
|
|
|
|
const url = document.getElementById('singleUrl').value.trim();
|
|
|
|
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
showToast('请输入链接', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/urls`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ url })
|
|
|
|
|
});
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
showToast('添加成功', 'success');
|
|
|
|
|
document.getElementById('singleUrl').value = '';
|
|
|
|
|
loadUrlList();
|
|
|
|
|
getStatistics();
|
|
|
|
|
} else {
|
|
|
|
|
showToast(data.message || '添加失败', 'error');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('添加失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 批量添加URL
|
|
|
|
|
async function addBatchUrls() {
|
|
|
|
|
const text = document.getElementById('batchUrls').value.trim();
|
|
|
|
|
|
|
|
|
|
if (!text) {
|
|
|
|
|
showToast('请输入链接', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const urls = text.split('\n').map(u => u.trim()).filter(u => u);
|
|
|
|
|
|
|
|
|
|
if (urls.length === 0) {
|
|
|
|
|
showToast('请输入有效链接', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/urls`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ urls })
|
|
|
|
|
});
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
showToast(`成功添加 ${data.added_count}/${data.total_count} 个链接`, 'success');
|
|
|
|
|
document.getElementById('batchUrls').value = '';
|
|
|
|
|
loadUrlList();
|
|
|
|
|
getStatistics();
|
|
|
|
|
} else {
|
|
|
|
|
showToast(data.message || '添加失败', 'error');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('添加失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载URL列表
|
|
|
|
|
async function loadUrlList() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/urls`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
const tbody = document.getElementById('urlTableBody');
|
|
|
|
|
|
|
|
|
|
if (data.data.length === 0) {
|
|
|
|
|
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">暂无数据</td></tr>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tbody.innerHTML = data.data.map(item => `
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<a href="${item.url}" target="_blank" class="table-url" title="${item.url}">${item.url}</a>
|
|
|
|
|
</td>
|
|
|
|
|
<td>${item.click_count}</td>
|
|
|
|
|
<td>${item.reply_count}</td>
|
|
|
|
|
<td>${item.last_click_time || '未点击'}</td>
|
|
|
|
|
<td>
|
|
|
|
|
<button class="btn btn-warning" style="padding: 4px 12px; font-size: 12px; margin-right: 8px;" onclick="resetUrl('${encodeURIComponent(item.url)}')">重置</button>
|
|
|
|
|
<button class="btn btn-danger" style="padding: 4px 12px; font-size: 12px;" onclick="deleteUrl('${encodeURIComponent(item.url)}')">删除</button>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
`).join('');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载URL列表失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置URL
|
|
|
|
|
async function resetUrl(encodedUrl) {
|
|
|
|
|
if (!confirm('确定要重置该链接吗?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/urls/${encodedUrl}/reset`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
});
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
showToast('重置成功', 'success');
|
|
|
|
|
loadUrlList();
|
|
|
|
|
getStatistics();
|
|
|
|
|
} else {
|
|
|
|
|
showToast(data.message || '重置失败', 'error');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('重置失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 删除URL
|
|
|
|
|
async function deleteUrl(encodedUrl) {
|
|
|
|
|
if (!confirm('确定要删除该链接吗?')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE}/api/urls/${encodedUrl}`, {
|
|
|
|
|
method: 'DELETE'
|
|
|
|
|
});
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
showToast('删除成功', 'success');
|
|
|
|
|
loadUrlList();
|
|
|
|
|
getStatistics();
|
|
|
|
|
} else {
|
|
|
|
|
showToast(data.message || '删除失败', 'error');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('删除失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 浏览器测试
|
|
|
|
|
async function testBrowser(useProxy) {
|
|
|
|
|
const resultDiv = document.getElementById('browserTestResult');
|
|
|
|
|
resultDiv.innerHTML = '<p style="color: #1890ff;">测试中,请稍候...</p>';
|
|
|
|
|
|
|
|
|
|
showToast('浏览器测试功能待开发,请使用 test_adspower_playwright.py 进行测试', 'info');
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
resultDiv.innerHTML = '<p style="color: #666;">请使用命令行运行: python test_adspower_playwright.py</p>';
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 22:06:46 +08:00
|
|
|
// ==================== 数据库管理相关函数 ====================
|
|
|
|
|
|
|
|
|
|
// 切换数据库视图
|
|
|
|
|
function showDatabaseView(view) {
|
|
|
|
|
// 隐藏所有视图
|
|
|
|
|
document.querySelectorAll('.db-view').forEach(v => v.style.display = 'none');
|
|
|
|
|
|
|
|
|
|
// 显示目标视图
|
|
|
|
|
document.getElementById(`db${view.charAt(0).toUpperCase() + view.slice(1)}View`).style.display = 'block';
|
|
|
|
|
|
|
|
|
|
// 更新按钮状态
|
|
|
|
|
document.getElementById('btnShowSites').className = view === 'sites' ? 'btn btn-primary' : 'btn';
|
|
|
|
|
document.getElementById('btnShowClicks').className = view === 'clicks' ? 'btn btn-primary' : 'btn';
|
|
|
|
|
document.getElementById('btnShowInteractions').className = view === 'interactions' ? 'btn btn-primary' : 'btn';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载数据库数据
|
|
|
|
|
async function loadDatabaseData() {
|
|
|
|
|
try {
|
|
|
|
|
// 加载统计数据
|
|
|
|
|
const statsRes = await fetch(`${API_BASE}/api/statistics`);
|
|
|
|
|
const statsData = await statsRes.json();
|
|
|
|
|
|
|
|
|
|
if (statsData.success) {
|
|
|
|
|
const stats = statsData.data;
|
|
|
|
|
document.getElementById('dbTotalSites').textContent = stats.total_sites || 0;
|
|
|
|
|
document.getElementById('dbTotalClicks').textContent = stats.total_clicks || 0;
|
|
|
|
|
document.getElementById('dbTotalReplies').textContent = stats.total_replies || 0;
|
|
|
|
|
document.getElementById('dbSuccessful').textContent = stats.successful_interactions || 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载站点数据
|
|
|
|
|
await loadDatabaseSites();
|
|
|
|
|
await loadDatabaseClicks();
|
|
|
|
|
await loadDatabaseInteractions();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast('加载数据库数据失败: ' + error.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载站点列表
|
|
|
|
|
async function loadDatabaseSites() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${API_BASE}/api/urls`);
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
const tbody = document.getElementById('dbSitesTableBody');
|
|
|
|
|
if (data.success && data.data && data.data.length > 0) {
|
|
|
|
|
tbody.innerHTML = data.data.map(site => `
|
|
|
|
|
<tr>
|
|
|
|
|
<td>${site.id}</td>
|
|
|
|
|
<td>${site.site_name || '-'}</td>
|
|
|
|
|
<td><a href="${site.site_url}" target="_blank" class="table-url" title="${site.site_url}">${site.site_url}</a></td>
|
|
|
|
|
<td><span class="tag ${site.status === 'active' ? 'tag-success' : 'tag-error'}">${site.status === 'active' ? '活跃' : '未激活'}</span></td>
|
|
|
|
|
<td>${site.click_count || 0}</td>
|
|
|
|
|
<td>${site.reply_count || 0}</td>
|
|
|
|
|
<td>${site.created_at || '-'}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
`).join('');
|
|
|
|
|
} else {
|
|
|
|
|
tbody.innerHTML = '<tr><td colspan="7" class="empty-state">暂无站点数据</td></tr>';
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载站点数据失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载点击记录
|
|
|
|
|
async function loadDatabaseClicks() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${API_BASE}/api/clicks?limit=100`);
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
const tbody = document.getElementById('dbClicksTableBody');
|
|
|
|
|
if (data.success && data.data && data.data.length > 0) {
|
|
|
|
|
tbody.innerHTML = data.data.map(click => `
|
|
|
|
|
<tr>
|
|
|
|
|
<td>${click.id}</td>
|
|
|
|
|
<td>${click.site_id || '-'}</td>
|
|
|
|
|
<td><span class="table-url" title="${click.site_url || ''}">${(click.site_url || '-').substring(0, 50)}...</span></td>
|
|
|
|
|
<td>${click.click_time || '-'}</td>
|
|
|
|
|
<td>${click.device_type || '-'}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
`).join('');
|
|
|
|
|
} else {
|
|
|
|
|
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">暂无点击记录</td></tr>';
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载点击记录失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载互动记录
|
|
|
|
|
async function loadDatabaseInteractions() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${API_BASE}/api/interactions?limit=100`);
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
const tbody = document.getElementById('dbInteractionsTableBody');
|
|
|
|
|
if (data.success && data.data && data.data.length > 0) {
|
|
|
|
|
tbody.innerHTML = data.data.map(interaction => `
|
|
|
|
|
<tr>
|
|
|
|
|
<td>${interaction.id}</td>
|
|
|
|
|
<td>${interaction.site_id || '-'}</td>
|
|
|
|
|
<td>${interaction.interaction_time || '-'}</td>
|
|
|
|
|
<td><span class="table-url" title="${interaction.reply_content || ''}">${(interaction.reply_content || '-').substring(0, 30)}...</span></td>
|
|
|
|
|
<td><span class="tag ${interaction.response_received ? 'tag-success' : 'tag-error'}">${interaction.response_received ? '是' : '否'}</span></td>
|
|
|
|
|
<td><span class="tag ${interaction.interaction_status === 'success' ? 'tag-success' : 'tag-error'}">${interaction.interaction_status || '-'}</span></td>
|
|
|
|
|
</tr>
|
|
|
|
|
`).join('');
|
|
|
|
|
} else {
|
|
|
|
|
tbody.innerHTML = '<tr><td colspan="6" class="empty-state">暂无互动记录</td></tr>';
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载互动记录失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 刷新数据库数据
|
|
|
|
|
function refreshDatabaseData() {
|
|
|
|
|
showToast('正在刷新数据...', 'info');
|
|
|
|
|
loadDatabaseData();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 18:59:26 +08:00
|
|
|
// 初始化
|
|
|
|
|
function init() {
|
|
|
|
|
getSchedulerStatus();
|
|
|
|
|
getStatistics();
|
|
|
|
|
loadUrlList();
|
|
|
|
|
|
|
|
|
|
// 定时刷新状态
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
getSchedulerStatus();
|
|
|
|
|
getStatistics();
|
|
|
|
|
loadUrlList();
|
|
|
|
|
}, 5000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|