Files
ai_mip/static/index.html

844 lines
27 KiB
HTML
Raw Normal View History

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>
<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>
</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': '链接管理',
'browser': '浏览器测试'
};
document.getElementById('breadcrumb').textContent = breadcrumbMap[page];
}
// 显示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);
}
// 初始化
function init() {
getSchedulerStatus();
getStatistics();
loadUrlList();
// 定时刷新状态
setInterval(() => {
getSchedulerStatus();
getStatistics();
loadUrlList();
}, 5000);
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>