mirror of
https://github.com/ddCat-main/cursor-auto-register.git
synced 2025-12-24 13:38:01 +08:00
1793 lines
69 KiB
HTML
1793 lines
69 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<title>Cursor账号管理系统</title>
|
||
<!-- Bootstrap 5 CSS -->
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<!-- Font Awesome 图标 -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||
<!-- 自定义样式 -->
|
||
<style>
|
||
body {
|
||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||
color: #212529;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* 组合相似的选择器,减少重复 */
|
||
.card, .table, .alert {
|
||
border-radius: 12px;
|
||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.card {
|
||
border: none;
|
||
transition: all 0.3s ease;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.card-header {
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||
font-weight: 600;
|
||
color: #111827;
|
||
}
|
||
|
||
/* 优化按钮相关样式 */
|
||
.btn {
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.btn:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
/* 优化按钮波纹效果 */
|
||
.btn::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 5px;
|
||
height: 5px;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
opacity: 0;
|
||
border-radius: 100%;
|
||
transform: scale(1) translate(-50%, -50%);
|
||
transform-origin: 0 0;
|
||
}
|
||
|
||
.btn:focus:not(:active)::after {
|
||
animation: ripple 1s ease-out;
|
||
}
|
||
|
||
@keyframes ripple {
|
||
0% { transform: scale(0); opacity: 0.6; }
|
||
100% { transform: scale(25); opacity: 0; }
|
||
}
|
||
|
||
/* 优化表格样式 */
|
||
.table {
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||
width: 100%;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.table td, .table th {
|
||
padding: 0.75rem 1rem;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 简化表格悬停效果 */
|
||
.table tbody tr:hover {
|
||
background-color: rgba(241, 245, 249, 0.7);
|
||
transform: scale(1.01);
|
||
}
|
||
|
||
/* 合并加载遮罩样式 */
|
||
.loading-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: none;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10000;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.loading-overlay.show {
|
||
display: flex;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.spinner-container {
|
||
background-color: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
background: linear-gradient(120deg, #4f46e5, #7c3aed);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 0.9rem;
|
||
color: #1f2937;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.badge {
|
||
font-weight: 500;
|
||
padding: 0.35em 0.6em;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.operation-column .btn {
|
||
margin: 0.15rem;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.email-column {
|
||
max-width: 250px;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.password-cell, .token-cell {
|
||
position: relative;
|
||
}
|
||
|
||
.copy-btn {
|
||
margin-left: 0.25rem !important;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.table th {
|
||
background-color: #f3f4f6;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
border-top: none;
|
||
}
|
||
|
||
.table tbody tr {
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
#fireworks-canvas {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 9999;
|
||
pointer-events: none;
|
||
display: none;
|
||
}
|
||
|
||
#alert-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 10000;
|
||
max-width: 400px;
|
||
}
|
||
|
||
.alert {
|
||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
|
||
border-radius: 10px;
|
||
border-left: 4px solid;
|
||
}
|
||
|
||
.alert-success {
|
||
border-left-color: #10b981;
|
||
}
|
||
|
||
.alert-danger {
|
||
border-left-color: #ef4444;
|
||
}
|
||
|
||
.special-alert {
|
||
background: linear-gradient(135deg, #10b981, #059669);
|
||
color: white;
|
||
font-weight: 500;
|
||
transform-origin: center;
|
||
}
|
||
|
||
.text-muted {
|
||
color: #6c757d !important;
|
||
}
|
||
|
||
/* 调整卡片内部间距,使内容更紧凑 */
|
||
.card-body {
|
||
padding: 1.25rem;
|
||
}
|
||
|
||
/* 调整统计图表容器高度 */
|
||
#usage-chart-container {
|
||
height: 200px; /* 从250px减小到200px */
|
||
}
|
||
|
||
/* 调整任务控制详情部分 */
|
||
#task-details .card {
|
||
margin-bottom: 0.5rem; /* 减小底部边距 */
|
||
}
|
||
|
||
#task-details .card .card-body {
|
||
padding: 0.5rem; /* 减小内部填充 */
|
||
}
|
||
|
||
/* 调整操作按钮大小和间距 */
|
||
.task-control-buttons .btn {
|
||
padding: 0.375rem 0.75rem;
|
||
}
|
||
|
||
/* 调整任务状态信息间距 */
|
||
.task-status-info {
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
/* 调整图表大小 */
|
||
#usage-chart {
|
||
max-height: 160px; /* 从200px减小到160px */
|
||
}
|
||
|
||
/* 使用情况进度条样式更新 */
|
||
.usage-progress-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
/* 使用情况列样式优化 */
|
||
.usage-info {
|
||
min-width: 140px;
|
||
}
|
||
|
||
.usage-numbers {
|
||
font-size: 0.8rem;
|
||
margin-bottom: 3px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.used-count {
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
|
||
.total-count {
|
||
color: #666;
|
||
}
|
||
|
||
.remaining-count {
|
||
font-size: 0.75rem;
|
||
color: #28a745;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
/* 修改进度条背景为灰色 */
|
||
.usage-progress {
|
||
height: 8px;
|
||
background-color: #e9ecef;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 修改进度条填充颜色为浅红色 */
|
||
.usage-progress-bar {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #ff8a8a, #ffb3b3);
|
||
border-radius: 4px;
|
||
transition: width 0.5s ease;
|
||
}
|
||
|
||
/* 调整表格列宽 */
|
||
.usage-column {
|
||
min-width: 180px;
|
||
}
|
||
|
||
.email-column {
|
||
max-width: 220px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 高亮显示当前行 */
|
||
.table tbody tr.active {
|
||
background-color: rgba(79, 70, 229, 0.05);
|
||
}
|
||
|
||
/* 调整使用量查询按钮 */
|
||
.get-usage-btn {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 保留操作列样式,但可以调整 */
|
||
.operation-column {
|
||
min-width: 120px; /* 由于按钮组,宽度需要调整 */
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 标题和面板间距优化 */
|
||
h2, h3, h4, h5 {
|
||
margin-bottom: 1rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 添加鼠标悬停手形光标效果 */
|
||
.copy-btn,
|
||
.toggle-password,
|
||
.toggle-token,
|
||
.toggle-username,
|
||
.btn,
|
||
.status-action,
|
||
.get-usage-btn,
|
||
.delete-account-btn {
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 为复制和显示/隐藏图标添加悬停效果 */
|
||
.copy-btn:hover,
|
||
.toggle-password:hover,
|
||
.toggle-token:hover,
|
||
.toggle-username:hover {
|
||
color: #4f46e5;
|
||
transform: scale(1.1);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
/* 添加额度进度条样式 */
|
||
.battery-progress {
|
||
display: flex;
|
||
width: 100%;
|
||
height: 20px;
|
||
/* background-color: #e9ecef; */
|
||
border-radius: 5px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
/* border: 1px solid #dee2e6; */
|
||
}
|
||
|
||
/* 电池内部格子线 */
|
||
.battery-grid {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 电池格子 */
|
||
.battery-cell {
|
||
flex: 1;
|
||
height: 2px;
|
||
border-right: 1px solid rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.battery-cell:last-child {
|
||
border-right: none;
|
||
}
|
||
|
||
/* 已使用的部分 - 浅红色 */
|
||
.battery-used {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #ff8a8a, #ffb3b3);
|
||
transition: width 0.5s ease;
|
||
}
|
||
|
||
/* 额度信息文本 */
|
||
.usage-text {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: #495057;
|
||
text-shadow: 0 0 2px rgba(255,255,255,0.7);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 使用量显示样式 */
|
||
.usage-display {
|
||
width: 100%;
|
||
}
|
||
|
||
.usage-text {
|
||
font-weight: 500;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.progress {
|
||
height: 8px !important;
|
||
border-radius: 4px;
|
||
background-color: transparent !important;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 自定义颜色 */
|
||
.bg-danger-soft {
|
||
background-color: #ffcccc !important;
|
||
}
|
||
|
||
.bg-success-soft {
|
||
background-color: #d1ffd1 !important;
|
||
}
|
||
|
||
/* 新的额度显示样式 - 参考图片精确复刻 */
|
||
.usage-bar-container {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.usage-bar {
|
||
flex-grow: 1;
|
||
height: 15px;
|
||
background-color: #e8f7e8; /* 浅绿背景 */
|
||
border-radius: 10px;
|
||
margin-right: 15px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.usage-bar-used {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
height: 100%;
|
||
background-color: #ffcccc; /* 浅红色 */
|
||
border-radius: 10px 0 0 10px;
|
||
}
|
||
|
||
.usage-percent {
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: normal;
|
||
white-space: nowrap;
|
||
min-width: 55px;
|
||
text-align: right;
|
||
}
|
||
|
||
/* 使用情况列样式 */
|
||
.usage-info {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.usage-numbers {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.used-count {
|
||
font-weight: 600;
|
||
color: #0d6efd;
|
||
}
|
||
|
||
.separator {
|
||
color: #6c757d;
|
||
}
|
||
|
||
.total-count {
|
||
font-weight: 600;
|
||
color: #212529;
|
||
}
|
||
|
||
.remaining-count {
|
||
color: #28a745;
|
||
font-size: 0.85em;
|
||
margin-left: 5px;
|
||
}
|
||
|
||
/* 电池式进度指示器 - 修改颜色逻辑 */
|
||
.battery-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.battery-bars {
|
||
display: flex;
|
||
gap: 2px;
|
||
align-items: center;
|
||
position: relative;
|
||
padding: 0 2px;
|
||
}
|
||
|
||
.battery-bars::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: -3px;
|
||
height: 8px;
|
||
width: 3px;
|
||
background-color: #adb5bd;
|
||
border-radius: 2px 0 0 2px;
|
||
}
|
||
|
||
.battery-bar {
|
||
width: 6px;
|
||
height: 14px;
|
||
background-color: rgba(40, 167, 69, 0.2); /* 默认未使用为浅绿色 */
|
||
display: inline-block;
|
||
border-radius: 1px;
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
|
||
/* 根据进度点亮电池条 - 反转逻辑,显示已用部分 */
|
||
.battery-progress[data-percent="0"] .battery-bar:nth-child(n) {
|
||
background-color: rgba(40, 167, 69, 0.2); /* 全未用 - 全部浅绿色 */
|
||
}
|
||
|
||
/* 已使用部分为浅红色 */
|
||
.battery-progress[data-percent="10"] .battery-bar:nth-child(1),
|
||
.battery-progress[data-percent="20"] .battery-bar:nth-child(-n+2),
|
||
.battery-progress[data-percent="30"] .battery-bar:nth-child(-n+3),
|
||
.battery-progress[data-percent="40"] .battery-bar:nth-child(-n+4),
|
||
.battery-progress[data-percent="50"] .battery-bar:nth-child(-n+5),
|
||
.battery-progress[data-percent="60"] .battery-bar:nth-child(-n+6),
|
||
.battery-progress[data-percent="70"] .battery-bar:nth-child(-n+7),
|
||
.battery-progress[data-percent="80"] .battery-bar:nth-child(-n+8),
|
||
.battery-progress[data-percent="90"] .battery-bar:nth-child(-n+9),
|
||
.battery-progress[data-percent="100"] .battery-bar:nth-child(-n+10) {
|
||
background-color: rgba(220, 53, 69, 0.2); /* 浅红色表示已使用 */
|
||
}
|
||
|
||
/* 删除旧的根据进度值设置颜色的规则,因为现在使用统一的红绿对比 */
|
||
.battery-percent {
|
||
font-size: 0.75rem;
|
||
color: #6c757d;
|
||
min-width: 40px;
|
||
}
|
||
#registration-status {
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 0.25rem;
|
||
}
|
||
</style>
|
||
<!-- 添加现代字体 -->
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
<!-- 添加动画库 -->
|
||
<link href="https://cdn.jsdelivr.net/npm/animate.css@4.1.1/animate.min.css" rel="stylesheet">
|
||
</head>
|
||
<body>
|
||
<!-- 烟花动画画布 -->
|
||
<canvas id="fireworks-canvas"></canvas>
|
||
|
||
<!-- 加载中遮罩 -->
|
||
<div class="loading-overlay" id="loading-overlay">
|
||
<div class="spinner-container">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">加载中...</span>
|
||
</div>
|
||
<p class="mt-2 mb-0">加载中,请稍候...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 通知容器 -->
|
||
<div id="alert-container"></div>
|
||
|
||
<div class="container py-4">
|
||
<div class="row mb-4">
|
||
<!-- 将左侧任务控制卡片调整为全宽 -->
|
||
<div class="col-12">
|
||
<div class="card h-100">
|
||
<div class="card-header bg-white">
|
||
<h5 class="mb-0">任务控制</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-center mb-3 task-status-info">
|
||
<div>
|
||
<span class="me-2">当前状态:</span>
|
||
<span class="badge status-not-started" id="registration-status">未启动</span>
|
||
</div>
|
||
<div class="task-control-buttons">
|
||
<button id="start-registration" class="btn btn-primary btn-sm me-2">
|
||
<i class="fas fa-play me-1"></i> 启动任务
|
||
</button>
|
||
<button id="stop-registration" class="btn btn-danger btn-sm" disabled>
|
||
<i class="fas fa-stop me-1"></i> 停止任务
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<p class="text-muted" id="status-message">任务未启动</p>
|
||
</div>
|
||
<div id="task-details">
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card bg-light">
|
||
<div class="card-body py-2">
|
||
<div class="d-flex justify-content-between">
|
||
<span>上次运行:</span>
|
||
<span id="last-run">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card bg-light">
|
||
<div class="card-body py-2">
|
||
<div class="d-flex justify-content-between">
|
||
<span>下次运行:</span>
|
||
<span id="next-run">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card bg-light">
|
||
<div class="card-body py-2">
|
||
<div class="d-flex justify-content-between">
|
||
<span>成功次数:</span>
|
||
<span id="successful-runs">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card bg-light">
|
||
<div class="card-body py-2">
|
||
<div class="d-flex justify-content-between">
|
||
<span>失败次数:</span>
|
||
<span id="failed-runs">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card bg-light">
|
||
<div class="card-body py-2">
|
||
<div class="d-flex justify-content-between">
|
||
<span>总运行次数:</span>
|
||
<span id="total-runs">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card bg-light">
|
||
<div class="card-body py-2">
|
||
<div class="d-flex justify-content-between">
|
||
<span>成功率:</span>
|
||
<span id="success-rate">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header bg-white">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<h5 class="mb-0">账号列表</h5>
|
||
<div class="input-group" style="width: 300px;">
|
||
<input type="text" class="form-control" id="search-input" placeholder="搜索账号...">
|
||
<button class="btn btn-outline-secondary" type="button" id="search-btn">
|
||
<i class="fas fa-search"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col" class="d-none d-md-table-cell">#</th>
|
||
<th scope="col">邮箱</th>
|
||
<th scope="col" class="d-none d-lg-table-cell">密码</th>
|
||
<th scope="col">Token</th>
|
||
<th scope="col" class="d-none d-lg-table-cell">额度</th>
|
||
<th scope="col" class="d-none d-lg-table-cell">注册时间</th>
|
||
<th scope="col">用量查询</th>
|
||
<th scope="col">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="accounts-tbody">
|
||
<!-- 账号数据将通过JavaScript动态填充 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div id="pagination" class="d-flex justify-content-center mt-4">
|
||
<!-- 分页控件将通过JavaScript动态填充 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bootstrap JS -->
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||
<!-- jQuery -->
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
<!-- 业务逻辑 -->
|
||
<script>
|
||
// 全局变量
|
||
const REFRESH_INTERVAL = 10000; // 10秒
|
||
let currentPage = 1;
|
||
const itemsPerPage = 10;
|
||
|
||
// 页面加载完成后执行
|
||
$(document).ready(function() {
|
||
// 初始化应用
|
||
initializeApplication();
|
||
});
|
||
|
||
// 应用初始化函数 - 提高代码组织性
|
||
function initializeApplication() {
|
||
// 初始加载数据
|
||
loadAllData();
|
||
|
||
// 设置定时刷新
|
||
setupTaskRefresh();
|
||
|
||
// 绑定所有事件处理函数
|
||
bindEventHandlers();
|
||
}
|
||
|
||
// 事件处理绑定函数 - 将所有事件绑定集中在一起
|
||
function bindEventHandlers() {
|
||
// 按钮事件监听
|
||
$("#refresh-btn").click(function() {
|
||
showLoading();
|
||
loadAllData();
|
||
});
|
||
|
||
$("#start-registration").click(function() {
|
||
startTaskManually();
|
||
});
|
||
|
||
$("#stop-registration").click(function() {
|
||
stopTaskManually();
|
||
});
|
||
|
||
$("#search-btn").click(function() {
|
||
filterAccounts();
|
||
});
|
||
|
||
$("#search-input").keypress(function(e) {
|
||
if (e.which === 13) {
|
||
filterAccounts();
|
||
}
|
||
});
|
||
|
||
// 可以添加更多事件绑定...
|
||
}
|
||
|
||
// 全局变量
|
||
let accounts = [];
|
||
let filteredAccounts = [];
|
||
let refreshTimer;
|
||
|
||
// 显示加载遮罩
|
||
function showLoading() {
|
||
const loadingOverlay = document.getElementById('loading-overlay');
|
||
loadingOverlay.classList.add('show');
|
||
}
|
||
|
||
// 隐藏加载遮罩
|
||
function hideLoading() {
|
||
const loadingOverlay = document.getElementById('loading-overlay');
|
||
loadingOverlay.classList.remove('show');
|
||
}
|
||
|
||
// 加载所有数据
|
||
function loadAllData() {
|
||
showLoading();
|
||
|
||
// 获取账号数据
|
||
fetch('/accounts')
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
console.log('获取到账号数据:', data); // 添加调试日志
|
||
|
||
// 检查并处理返回的数据结构
|
||
if (Array.isArray(data)) {
|
||
accounts = data; // 如果直接是数组
|
||
} else if (data.accounts && Array.isArray(data.accounts)) {
|
||
accounts = data.accounts; // 如果是包装在accounts属性中
|
||
} else if (data.data && Array.isArray(data.data)) {
|
||
accounts = data.data; // 如果是包装在data属性中
|
||
} else {
|
||
console.error('账号数据格式不正确:', data);
|
||
accounts = [];
|
||
}
|
||
|
||
console.log('处理后的账号数据:', accounts);
|
||
filteredAccounts = [...accounts];
|
||
|
||
// 渲染账号表格
|
||
renderAccountsTable();
|
||
|
||
// 然后获取任务状态
|
||
return fetch('/registration/status');
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
console.log('获取到任务状态:', data);
|
||
updateTaskStatusUI(data);
|
||
|
||
hideLoading();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载数据失败:', error);
|
||
hideLoading();
|
||
showAlert('加载数据失败,请刷新页面重试', 'danger');
|
||
});
|
||
}
|
||
|
||
// 启动定时刷新
|
||
function setupTaskRefresh() {
|
||
// 清除可能存在的旧定时器
|
||
if (window.taskRefreshInterval) {
|
||
clearInterval(window.taskRefreshInterval);
|
||
}
|
||
|
||
// 创建新的10秒定时器
|
||
window.taskRefreshInterval = setInterval(function() {
|
||
// 只刷新任务状态,而不是整个页面
|
||
refreshTaskStatus();
|
||
}, 10000); // 10秒
|
||
}
|
||
|
||
// 只刷新任务状态部分的函数
|
||
function refreshTaskStatus() {
|
||
fetch('/registration/status')
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
// 只更新任务状态区域,不重新加载账号列表
|
||
updateTaskStatusUI(data);
|
||
})
|
||
.catch(error => {
|
||
console.error('刷新任务状态时发生错误:', error);
|
||
});
|
||
}
|
||
|
||
// 更新任务状态UI函数 - 适配实际API返回的数据结构
|
||
function updateTaskStatusUI(data) {
|
||
console.log('收到任务状态数据:', data); // 保留调试日志
|
||
|
||
// 数据格式兼容性处理
|
||
const taskStatus = data || {};
|
||
|
||
// 从正确的嵌套位置获取运行状态和任务状态
|
||
const isRunning = taskStatus.task_status === 'running' || taskStatus.task_status === 'monitoring';
|
||
const taskStatusValue = taskStatus.task_status;
|
||
|
||
// 更新状态指示器,添加监控模式的样式
|
||
if ($('#registration-status').length) {
|
||
$('#registration-status').removeClass().addClass('badge');
|
||
|
||
if (taskStatusValue === 'running') {
|
||
$('#registration-status').addClass('bg-success');
|
||
} else if (taskStatusValue === 'monitoring') {
|
||
$('#registration-status').addClass('bg-info');
|
||
} else {
|
||
$('#registration-status').addClass('bg-secondary');
|
||
}
|
||
}
|
||
|
||
// 更新状态文本,添加监控模式文本
|
||
if ($('#registration-status').length) {
|
||
let statusText = '已停止';
|
||
if (taskStatusValue === 'running') {
|
||
statusText = '正在运行';
|
||
} else if (taskStatusValue === 'monitoring') {
|
||
statusText = '监控模式';
|
||
}
|
||
$('#registration-status').text(statusText);
|
||
}
|
||
|
||
// 更新其他状态信息
|
||
if ($('#last-run').length && taskStatus.registration_details?.last_run) {
|
||
$('#last-run').text(new Date(taskStatus.registration_details.last_run).toLocaleString());
|
||
}
|
||
|
||
if ($('#last-status').length && taskStatus.registration_details?.last_status) {
|
||
$('#last-status').text(taskStatus.registration_details.last_status);
|
||
}
|
||
|
||
if ($('#next-run').length && taskStatus.registration_details?.next_run) {
|
||
// next_run是Unix时间戳,需要转换
|
||
$('#next-run').text(new Date(taskStatus.registration_details.next_run * 1000).toLocaleString());
|
||
}
|
||
|
||
// 更新按钮状态 - 监控模式下也禁用启动按钮
|
||
if ($('#start-registration').length && $('#stop-registration').length) {
|
||
if (isRunning) {
|
||
$('#start-registration').addClass('disabled').prop('disabled', true);
|
||
$('#stop-registration').removeClass('disabled').prop('disabled', false);
|
||
} else {
|
||
$('#start-registration').removeClass('disabled').prop('disabled', false);
|
||
$('#stop-registration').addClass('disabled').prop('disabled', true);
|
||
}
|
||
}
|
||
|
||
// 更新统计信息
|
||
const stats = taskStatus.registration_details?.statistics || {};
|
||
|
||
if ($('#total-runs').length) {
|
||
$('#total-runs').text(stats.total_runs || 0);
|
||
}
|
||
|
||
if ($('#successful-runs').length) {
|
||
$('#successful-runs').text(stats.successful_runs || 0);
|
||
}
|
||
|
||
if ($('#failed-runs').length) {
|
||
$('#failed-runs').text(stats.failed_runs || 0);
|
||
}
|
||
|
||
if ($('#success-rate').length) {
|
||
$('#success-rate').text(stats.success_rate || '0%');
|
||
}
|
||
|
||
// 更新剩余槽位信息
|
||
if ($('#remaining-slots').length) {
|
||
$('#remaining-slots').text(taskStatus.remaining_slots || 0);
|
||
}
|
||
|
||
if ($('#current-count').length) {
|
||
$('#current-count').text(taskStatus.active_count || taskStatus.current_count || 0);
|
||
}
|
||
|
||
if ($('#max-accounts').length) {
|
||
$('#max-accounts').text(taskStatus.max_accounts || 0);
|
||
}
|
||
|
||
// 添加状态消息显示
|
||
if ($('#status-message').length) {
|
||
if (taskStatus.status_message) {
|
||
$('#status-message').text(taskStatus.status_message);
|
||
} else {
|
||
// 根据任务状态设置默认消息
|
||
let defaultMessage = '任务未启动';
|
||
if (taskStatusValue === 'running') {
|
||
defaultMessage = '正在注册新账号...';
|
||
} else if (taskStatusValue === 'monitoring') {
|
||
defaultMessage = '已达到最大账号数量,正在监控账号数量变化...';
|
||
} else if (taskStatus.registration_details?.last_status === 'completed') {
|
||
defaultMessage = '任务已完成';
|
||
} else if (taskStatus.registration_details?.last_status === 'error') {
|
||
defaultMessage = '任务执行出错,请检查日志';
|
||
}
|
||
$('#status-message').text(defaultMessage);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 渲染账号表格
|
||
function renderAccountsTable() {
|
||
const accountsBody = $('#accounts-tbody');
|
||
accountsBody.empty();
|
||
|
||
console.log(`准备渲染账号表格,共${filteredAccounts.length}条数据`);
|
||
|
||
if (filteredAccounts.length === 0) {
|
||
// 添加空状态提示
|
||
accountsBody.html(`
|
||
<tr>
|
||
<td colspan="7" class="text-center py-4">
|
||
<div class="py-5">
|
||
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
||
<p class="text-muted">暂无账号数据</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`);
|
||
return;
|
||
}
|
||
|
||
// 计算当前页的数据
|
||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||
const endIndex = Math.min(startIndex + itemsPerPage, filteredAccounts.length);
|
||
const currentPageData = filteredAccounts.slice(startIndex, endIndex);
|
||
|
||
console.log(`当前页数据: ${currentPageData.length}条 (第${currentPage}页)`);
|
||
|
||
// 渲染每行数据
|
||
currentPageData.forEach((account, index) => {
|
||
console.log(`渲染账号: ${account.email}`);
|
||
|
||
// 完整的行模板,包含所有单元格内容
|
||
const row = `
|
||
<tr id="account-row-${account.id}" data-status="${account.status}"
|
||
class="${account.status === 'deleted' ? 'table-danger' : account.status === 'disabled' ? 'table-warning' : ''}">
|
||
<td class="d-none d-md-table-cell">${startIndex + index + 1}</td>
|
||
<td class="email-column">
|
||
${account.email}
|
||
<span class="badge ${account.status === 'active' ? 'bg-success' : account.status === 'disabled' ? 'bg-warning' : 'bg-danger'} ms-2">
|
||
${account.status === 'active' ? '正常' : account.status === 'disabled' ? '停用' : '删除'}
|
||
</span>
|
||
</td>
|
||
<td class="d-none d-lg-table-cell password-cell">
|
||
<span class="password-text">${maskPassword(account.password)}</span>
|
||
<i class="fas fa-eye toggle-password" data-password="${account.password}" title="显示/隐藏密码"></i>
|
||
<i class="fas fa-copy copy-btn ms-1" data-copy="${account.password}" title="复制密码"></i>
|
||
</td>
|
||
${renderTokenColumn(account.token, account.id)}
|
||
${renderUsageProgress(account.usage_limit)}
|
||
<td class="d-none d-lg-table-cell">
|
||
${account.created_at || '未知'}
|
||
</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-outline-primary get-usage-btn" data-email="${account.email}" title="查询使用量">
|
||
<i class="fas fa-chart-pie"></i>
|
||
</button>
|
||
</td>
|
||
<td class="operation-column">
|
||
<div class="d-flex flex-wrap gap-1">
|
||
${account.status !== 'active' ?
|
||
`<button class="btn btn-sm btn-outline-success status-action" data-email="${account.email}" data-id="${account.id}" data-status="active" title="设为正常">
|
||
<i class="fas fa-check-circle"></i>
|
||
</button>` : ''}
|
||
|
||
${account.status !== 'disabled' ?
|
||
`<button class="btn btn-sm btn-outline-warning status-action" data-email="${account.email}" data-id="${account.id}" data-status="disabled" title="停用账号">
|
||
<i class="fas fa-pause-circle"></i>
|
||
</button>` : ''}
|
||
|
||
${account.status !== 'deleted' ?
|
||
`<button class="btn btn-sm btn-outline-danger status-action" data-email="${account.email}" data-id="${account.id}" data-status="deleted" title="标记删除">
|
||
<i class="fas fa-times-circle"></i>
|
||
</button>` : ''}
|
||
|
||
<button class="btn btn-sm btn-danger delete-account-btn" data-email="${account.email}" data-id="${account.id}" title="永久删除">
|
||
<i class="fas fa-trash-alt"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
accountsBody.append(row);
|
||
});
|
||
|
||
// 绑定事件
|
||
bindTableEvents();
|
||
renderPagination();
|
||
}
|
||
|
||
// 渲染分页
|
||
function renderPagination() {
|
||
const totalPages = Math.ceil(filteredAccounts.length / itemsPerPage);
|
||
const pagination = $("#pagination");
|
||
pagination.empty();
|
||
|
||
if (totalPages <= 1) {
|
||
return;
|
||
}
|
||
|
||
const paginationNav = $('<nav aria-label="Page navigation"></nav>');
|
||
const paginationUl = $('<ul class="pagination"></ul>');
|
||
|
||
// 上一页按钮
|
||
paginationUl.append(`
|
||
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
||
<a class="page-link" href="#" aria-label="Previous" ${currentPage !== 1 ? 'onclick="changePage(' + (currentPage - 1) + '); return false;"' : ''}>
|
||
<span aria-hidden="true">«</span>
|
||
</a>
|
||
</li>
|
||
`);
|
||
|
||
// 页码按钮
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
paginationUl.append(`
|
||
<li class="page-item ${currentPage === i ? 'active' : ''}">
|
||
<a class="page-link" href="#" onclick="changePage(${i}); return false;">${i}</a>
|
||
</li>·
|
||
`);
|
||
}
|
||
|
||
// 下一页按钮
|
||
paginationUl.append(`
|
||
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
||
<a class="page-link" href="#" aria-label="Next" ${currentPage !== totalPages ? 'onclick="changePage(' + (currentPage + 1) + '); return false;"' : ''}>
|
||
<span aria-hidden="true">»</span>
|
||
</a>
|
||
</li>
|
||
`);
|
||
|
||
paginationNav.append(paginationUl);
|
||
pagination.append(paginationNav);
|
||
}
|
||
|
||
// 更改页码
|
||
function changePage(page) {
|
||
currentPage = page;
|
||
renderAccountsTable();
|
||
}
|
||
|
||
// 过滤账号
|
||
function filterAccounts() {
|
||
const searchTerm = $("#search-input").val().toLowerCase();
|
||
if (!searchTerm) {
|
||
filteredAccounts = [...accounts];
|
||
} else {
|
||
filteredAccounts = accounts.filter(account =>
|
||
account.email.toLowerCase().includes(searchTerm) ||
|
||
account.user.toLowerCase().includes(searchTerm)
|
||
);
|
||
}
|
||
currentPage = 1;
|
||
renderAccountsTable();
|
||
}
|
||
|
||
// 获取账号用量详情并更新数据库
|
||
function getAccountUsage(email) {
|
||
showLoading();
|
||
fetch(`/account/${encodeURIComponent(email)}/usage`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
hideLoading();
|
||
if (data.success) {
|
||
// 创建并显示用量信息模态框
|
||
const modal = $(`
|
||
<div class="modal fade" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">账号用量信息</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<strong>邮箱:</strong> ${data.email}
|
||
</div>
|
||
<div class="mb-3">
|
||
<strong>剩余额度:</strong> ${data.usage.remaining_balance !== null ? data.usage.remaining_balance : '未知'}
|
||
</div>
|
||
<div class="mb-3">
|
||
<strong>剩余天数:</strong> ${data.usage.remaining_days !== null ? data.usage.remaining_days : '未知'}
|
||
</div>
|
||
<div class="mb-3">
|
||
<strong>状态:</strong>
|
||
<span class="badge ${data.usage.status === 'active' ? 'bg-success' : 'bg-danger'}">
|
||
${data.usage.status === 'active' ? '活跃' : '不活跃'}
|
||
</span>
|
||
</div>
|
||
<div class="mt-3 text-muted small">
|
||
<strong>更新时间:</strong> ${formatDateTime(data.timestamp)}
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`);
|
||
|
||
$('body').append(modal);
|
||
const modalInstance = new bootstrap.Modal(modal[0]);
|
||
modalInstance.show();
|
||
|
||
// 模态框关闭时移除DOM
|
||
modal[0].addEventListener('hidden.bs.modal', function() {
|
||
modal.remove();
|
||
});
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('获取账号用量失败:', error);
|
||
showAlert('获取账号用量失败', 'danger');
|
||
hideLoading();
|
||
});
|
||
}
|
||
|
||
// 更新账号用量到数据库
|
||
function updateAccountUsageLimit(email, usageLimit) {
|
||
fetch(`/account/${encodeURIComponent(email)}/update-usage`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ usage_limit: usageLimit })
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
console.log(`账号 ${email} 用量数据已更新到数据库`);
|
||
} else {
|
||
console.error(`更新账号 ${email} 用量数据失败:`, data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error(`更新账号 ${email} 用量数据时发生错误:`, error);
|
||
});
|
||
}
|
||
|
||
// 修复任务状态更新问题
|
||
function startTaskManually() {
|
||
showLoading();
|
||
fetch('/registration/start', {
|
||
method: 'GET'
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
hideLoading();
|
||
if (data.success) {
|
||
showAlert('定时任务已成功启动', 'success');
|
||
|
||
// 立即更新任务状态 - 添加这段代码
|
||
fetch('/registration/status')
|
||
.then(res => res.json())
|
||
.then(statusData => {
|
||
updateTaskStatusUI(statusData);
|
||
});
|
||
} else {
|
||
showAlert(`启动任务失败: ${data.message || '未知错误'}`, 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('启动任务时发生错误:', error);
|
||
hideLoading();
|
||
showAlert('启动任务失败,请稍后重试', 'danger');
|
||
});
|
||
}
|
||
|
||
// 同样添加到停止任务函数
|
||
function stopTaskManually() {
|
||
showLoading();
|
||
fetch('/registration/stop', {
|
||
method: 'GET'
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
hideLoading();
|
||
if (data.success) {
|
||
showAlert('定时任务已成功停止', 'success');
|
||
|
||
// 立即更新任务状态 - 添加这段代码
|
||
fetch('/registration/status')
|
||
.then(res => res.json())
|
||
.then(statusData => {
|
||
updateTaskStatusUI(statusData);
|
||
});
|
||
} else {
|
||
showAlert(`停止任务失败: ${data.message || '未知错误'}`, 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('停止任务时发生错误:', error);
|
||
hideLoading();
|
||
showAlert('停止任务失败,请稍后重试', 'danger');
|
||
});
|
||
}
|
||
|
||
// 复制到剪贴板
|
||
function copyToClipboard(text) {
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
showAlert('复制成功,Token已复制到剪贴板', 'success');
|
||
}).catch(err => {
|
||
console.error('复制失败:', err);
|
||
showAlert('复制失败', 'danger');
|
||
});
|
||
}
|
||
|
||
// 显示通知
|
||
function showAlert(message, type, isSpecial = false) {
|
||
const alertId = 'alert-' + Date.now();
|
||
const alertClass = isSpecial ?
|
||
`alert-${type} special-alert animate__animated animate__bounceIn` :
|
||
`alert-${type} animate__animated animate__fadeInRight`;
|
||
|
||
const alert = $(`
|
||
<div id="${alertId}" class="alert ${alertClass} alert-dismissible fade show" role="alert">
|
||
${isSpecial ? '<i class="fas fa-star me-2"></i>' : ''}${message}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||
</div>
|
||
`);
|
||
|
||
$("#alert-container").append(alert);
|
||
|
||
// 5秒后自动消失
|
||
setTimeout(() => {
|
||
$(`#${alertId}`).alert('close');
|
||
}, 5000);
|
||
}
|
||
|
||
// 日期时间格式化
|
||
function formatDateTime(dateTimeString) {
|
||
if (!dateTimeString) return '-';
|
||
|
||
try {
|
||
const date = new Date(dateTimeString);
|
||
if (isNaN(date.getTime())) return dateTimeString;
|
||
|
||
const year = date.getFullYear();
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||
const day = date.getDate().toString().padStart(2, '0');
|
||
const hours = date.getHours().toString().padStart(2, '0');
|
||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
const seconds = date.getSeconds().toString().padStart(2, '0');
|
||
|
||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||
} catch (error) {
|
||
return dateTimeString;
|
||
}
|
||
}
|
||
|
||
// 修改掩码函数,增加对用户名的特殊处理
|
||
function maskText(text, showChars = 6, isUsername = false) {
|
||
if (!text) return '';
|
||
|
||
// 用户名特殊处理 - 只显示前1/3
|
||
if (isUsername) {
|
||
const showLength = Math.ceil(text.length / 3);
|
||
if (text.length <= showLength) return text;
|
||
return `${text.substring(0, showLength)}...`;
|
||
}
|
||
|
||
// 其他文本使用标准处理
|
||
if (text.length <= showChars) return text;
|
||
return `${text.substring(0, showChars)}...`;
|
||
}
|
||
|
||
// 隐藏密码
|
||
function maskPassword(password) {
|
||
if (!password) return '';
|
||
return '•'.repeat(password.length);
|
||
}
|
||
|
||
// 页面加载动画
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 修改动画类添加代码,删除对已删除元素的引用
|
||
const elements = [
|
||
{selector: '.card', animation: 'animate__fadeIn', delay: 0.2}
|
||
];
|
||
|
||
elements.forEach(item => {
|
||
const elems = document.querySelectorAll(item.selector);
|
||
elems.forEach((el, index) => {
|
||
el.classList.add('animate__animated', item.animation);
|
||
|
||
if (item.delay) {
|
||
const delay = item.stagger ? item.delay * (index + 1) : item.delay;
|
||
el.style.animationDelay = `${delay}s`;
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// 烟花动画实现
|
||
const Fireworks = {
|
||
canvas: null,
|
||
ctx: null,
|
||
particles: [],
|
||
|
||
init: function() {
|
||
this.canvas = document.getElementById('fireworks-canvas');
|
||
this.ctx = this.canvas.getContext('2d');
|
||
this.resizeCanvas();
|
||
window.addEventListener('resize', () => this.resizeCanvas());
|
||
},
|
||
|
||
resizeCanvas: function() {
|
||
this.canvas.width = window.innerWidth;
|
||
this.canvas.height = window.innerHeight;
|
||
},
|
||
|
||
start: function() {
|
||
this.canvas.style.display = 'block';
|
||
this.particles = [];
|
||
|
||
// 创建5次烟花,间隔300ms
|
||
for (let i = 0; i < 5; i++) {
|
||
setTimeout(() => {
|
||
const x = Math.random() * this.canvas.width;
|
||
const y = Math.random() * this.canvas.height * 0.6;
|
||
this.createParticles(x, y);
|
||
}, i * 300);
|
||
}
|
||
|
||
this.animate();
|
||
|
||
// 5秒后停止动画
|
||
setTimeout(() => {
|
||
this.canvas.style.display = 'none';
|
||
}, 5000);
|
||
},
|
||
|
||
createParticles: function(x, y) {
|
||
const colors = ['#ff595e', '#ffca3a', '#8ac926', '#1982c4', '#6a4c93'];
|
||
|
||
for (let i = 0; i < 80; i++) {
|
||
const particle = {
|
||
x: x,
|
||
y: y,
|
||
size: Math.random() * 4 + 1,
|
||
color: colors[Math.floor(Math.random() * colors.length)],
|
||
velocity: {
|
||
x: (Math.random() - 0.5) * 8,
|
||
y: (Math.random() - 0.5) * 8
|
||
},
|
||
alpha: 1,
|
||
decay: Math.random() * 0.02 + 0.01
|
||
};
|
||
|
||
this.particles.push(particle);
|
||
}
|
||
},
|
||
|
||
animate: function() {
|
||
if (this.particles.length === 0) return;
|
||
|
||
requestAnimationFrame(() => this.animate());
|
||
|
||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||
|
||
for (let i = 0; i < this.particles.length; i++) {
|
||
const p = this.particles[i];
|
||
|
||
// 添加重力
|
||
p.velocity.y += 0.05;
|
||
|
||
// 更新位置
|
||
p.x += p.velocity.x;
|
||
p.y += p.velocity.y;
|
||
|
||
// 减少透明度
|
||
p.alpha -= p.decay;
|
||
|
||
// 绘制粒子
|
||
this.ctx.save();
|
||
this.ctx.globalAlpha = p.alpha;
|
||
this.ctx.fillStyle = p.color;
|
||
this.ctx.beginPath();
|
||
this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
||
this.ctx.fill();
|
||
this.ctx.restore();
|
||
|
||
// 移除消失的粒子
|
||
if (p.alpha <= 0) {
|
||
this.particles.splice(i, 1);
|
||
i--;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// 初始化烟花
|
||
Fireworks.init();
|
||
|
||
// 绑定表格交互事件
|
||
function bindTableEvents() {
|
||
// 显示/隐藏用户名
|
||
$('.toggle-username').off('click').on('click', function() {
|
||
const username = $(this).data('username');
|
||
const usernameText = $(this).prev('.username-text');
|
||
|
||
if (usernameText.text() === username) {
|
||
usernameText.text(maskText(username, 6, true));
|
||
} else {
|
||
usernameText.text(username);
|
||
}
|
||
});
|
||
|
||
// 显示/隐藏密码
|
||
$('.toggle-password').off('click').on('click', function() {
|
||
const password = $(this).data('password');
|
||
const passwordText = $(this).prev('.password-text');
|
||
|
||
if (passwordText.text() === password) {
|
||
passwordText.text(maskPassword(password));
|
||
} else {
|
||
passwordText.text(password);
|
||
}
|
||
});
|
||
|
||
// 显示/隐藏Token
|
||
$('.toggle-token').off('click').on('click', function() {
|
||
const token = $(this).data('token');
|
||
const tokenText = $(this).prev('.token-text');
|
||
|
||
if (tokenText.text() === token) {
|
||
tokenText.text(maskText(token));
|
||
} else {
|
||
tokenText.text(token);
|
||
}
|
||
});
|
||
|
||
// 复制按钮
|
||
$('.copy-btn').off('click').on('click', function() {
|
||
const textToCopy = $('#tokenFullText').val();
|
||
copyToClipboard(textToCopy);
|
||
});
|
||
|
||
// 获取用量按钮
|
||
$('.get-usage-btn').off('click').on('click', function() {
|
||
const email = $(this).data('email');
|
||
getAccountUsage(email);
|
||
});
|
||
|
||
// 删除按钮
|
||
$('.delete-account-btn').off('click').on('click', function() {
|
||
const email = $(this).data('email');
|
||
const id = $(this).data('id');
|
||
$('#deleteEmailConfirm').text(email);
|
||
$('#deleteIdConfirm').text(id || '无');
|
||
|
||
// 重置并重新绑定确认删除按钮事件
|
||
$('#confirmDeleteBtn').off('click').on('click', function() {
|
||
deleteAccount(email, id, true);
|
||
});
|
||
|
||
const deleteModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
|
||
deleteModal.show();
|
||
});
|
||
|
||
// 状态操作按钮
|
||
$('.status-action').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
const email = $(this).data('email');
|
||
const id = $(this).data('id');
|
||
const status = $(this).data('status');
|
||
updateAccountStatus(email, id, status);
|
||
});
|
||
|
||
// 查看Token按钮
|
||
$('.view-token-btn').off('click').on('click', function() {
|
||
const token = $(this).data('token');
|
||
const accountId = $(this).data('account-id');
|
||
$('#tokenFullText').val(token);
|
||
$('#useTokenBtn').data('account-id', accountId);
|
||
new bootstrap.Modal(document.getElementById('tokenViewModal')).show();
|
||
});
|
||
|
||
// 使用Token按钮
|
||
$('#useTokenBtn').off('click').on('click', function() {
|
||
const accountId = $(this).data('account-id');
|
||
if (!accountId) {
|
||
showAlert('账号ID无效', 'danger');
|
||
return;
|
||
}
|
||
|
||
showLoading();
|
||
fetch(`/account/use-token/${accountId}`, {
|
||
method: 'POST'
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
hideLoading();
|
||
if (data.success) {
|
||
showAlert(data.message, 'success');
|
||
$('#tokenViewModal').modal('hide');
|
||
} else {
|
||
showAlert(`使用Token失败: ${data.message || '未知错误'}`, 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('使用Token时发生错误:', error);
|
||
hideLoading();
|
||
showAlert('使用Token失败,请稍后重试', 'danger');
|
||
});
|
||
});
|
||
}
|
||
|
||
// 更新删除确认按钮事件处理
|
||
$('#confirmDeleteBtn').click(function() {
|
||
const email = $(this).data('email');
|
||
const id = $(this).data('id');
|
||
deleteAccount(email, id, true);
|
||
});
|
||
|
||
// 修改updateAccountStatus函数,确保正确发送请求体
|
||
function updateAccountStatus(email, id, status) {
|
||
showLoading();
|
||
// 优先使用ID API,如果ID存在的话
|
||
const apiUrl = id ?
|
||
`/account/id/${id}/status` :
|
||
`/account/${encodeURIComponent(email)}/status`;
|
||
|
||
fetch(apiUrl, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ status: status }) // 确保这里的字段名是status
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
hideLoading();
|
||
if (data.success) {
|
||
let statusText = '';
|
||
if (status === 'active') statusText = '正常';
|
||
else if (status === 'disabled') statusText = '停用';
|
||
else if (status === 'deleted') statusText = '删除';
|
||
|
||
showAlert(`账号${id ? '(ID:'+id+')' : ''} ${email} 已成功设置为${statusText}状态`, 'success');
|
||
loadAllData();
|
||
} else {
|
||
showAlert(`更新账号状态失败: ${data.message || '未知错误'}`, 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('更新账号状态时发生错误:', error);
|
||
hideLoading();
|
||
showAlert('更新账号状态失败,请稍后重试', 'danger');
|
||
});
|
||
}
|
||
|
||
// 修改deleteAccount函数,支持通过ID删除
|
||
function deleteAccount(email, id, hardDelete = true) {
|
||
showLoading();
|
||
// 优先使用ID API,如果ID存在的话
|
||
const apiUrl = id ?
|
||
`/account/id/${id}${hardDelete ? '?hard_delete=true' : ''}` :
|
||
`/account/${encodeURIComponent(email)}${hardDelete ? '?hard_delete=true' : ''}`;
|
||
|
||
fetch(apiUrl, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
hideLoading();
|
||
if (data.success) {
|
||
showAlert(`账号${id ? '(ID:'+id+')' : ''} ${email} 已成功删除`, 'success');
|
||
// 关闭模态框
|
||
$('#deleteConfirmModal').modal('hide');
|
||
// 重新加载账号列表
|
||
loadAllData();
|
||
} else {
|
||
showAlert(`删除账号失败: ${data.message || '未知错误'}`, 'danger');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('删除账号时发生错误:', error);
|
||
hideLoading();
|
||
showAlert('删除账号失败,请稍后重试', 'danger');
|
||
});
|
||
}
|
||
|
||
// 添加强制刷新函数
|
||
function forceRefreshData() {
|
||
window.forceRefresh = true;
|
||
loadAllData();
|
||
}
|
||
|
||
// 完全重构额度显示函数,精确匹配参考代码
|
||
function renderUsageProgress(usageLimit) {
|
||
// 计算使用进度
|
||
const premiumUsed = 150 - usageLimit;
|
||
const premiumTotal = 150;
|
||
const premiumRemaining = premiumTotal - premiumUsed;
|
||
const premiumPercent = Math.round((premiumUsed / premiumTotal) * 100);
|
||
|
||
return `
|
||
<td class="usage-info">
|
||
<div class="usage-numbers">
|
||
<span class="used-count">${premiumUsed}</span>
|
||
<span class="separator">/</span>
|
||
<span class="total-count">${premiumTotal}</span>
|
||
<span class="remaining-count">(剩余: ${premiumRemaining})</span>
|
||
</div>
|
||
<div class="battery-progress" data-percent="${Math.round(premiumPercent / 10) * 10}">
|
||
<div class="battery-bars">
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
<span class="battery-bar"></span>
|
||
</div>
|
||
<span class="battery-percent">${premiumPercent}%</span>
|
||
</div>
|
||
</td>
|
||
`;
|
||
}
|
||
|
||
// 修改Token列的渲染方式
|
||
function renderTokenColumn(token, accountId) {
|
||
return `
|
||
<td class="token-column">
|
||
<button class="btn btn-sm btn-outline-info view-token-btn" data-token="${token}" data-account-id="${accountId}">
|
||
<i class="fas fa-eye"></i> 查看Token
|
||
</button>
|
||
</td>
|
||
`;
|
||
}
|
||
</script>
|
||
|
||
<!-- 添加删除确认对话框 -->
|
||
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="deleteConfirmModalLabel">确认删除</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>确定要删除此账号吗?此操作不可恢复。</p>
|
||
<p class="text-danger fw-bold">邮箱: <span id="deleteEmailConfirm"></span></p>
|
||
<p class="text-muted">ID: <span id="deleteIdConfirm"></span></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" id="confirmDeleteBtn">删除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 在页面底部添加自动刷新指示器 -->
|
||
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 5">
|
||
<div class="d-flex align-items-center small text-muted">
|
||
<span class="me-2">自动刷新(10秒/次)</span>
|
||
<div class="spinner-border spinner-border-sm text-secondary" role="status" style="width: 0.8rem; height: 0.8rem;">
|
||
<span class="visually-hidden">Loading...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加Token查看模态框 -->
|
||
<div class="modal fade" id="tokenViewModal" tabindex="-1" aria-labelledby="tokenViewModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="tokenViewModalLabel">查看Token</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label for="tokenFullText" class="form-label">完整Token:</label>
|
||
<div class="input-group">
|
||
<textarea id="tokenFullText" class="form-control" rows="5" readonly></textarea>
|
||
<button class="btn btn-outline-primary copy-btn" type="button" id="copyTokenBtn" title="复制Token">
|
||
<i class="fas fa-copy"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="text-muted small">此Token用于Cursor客户端身份验证,请妥善保管</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-primary" id="useTokenBtn" data-account-id="">
|
||
<i class="fas fa-plug"></i> 使用此Token
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html> |