+
+
+
+
- 任务控制
+空闲中
-
- 任务未启动
-
-
-
-
-
-
- 上次运行:
- -
+
+
+
+
-
+
+
+ 系统空闲中,可以开始新任务
+
+
+
+
+
+ 0
+ /
+ 10
+ 剩余: 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0%
-
-
-
- 下次运行:
- -
+
+
+
+
+
+
+
+
+
+
-
-
+
-
+
+
+
+
+ 上次运行:
+ 从未运行
+
+
+ 下次运行:
+ 未排程
+
+
+
+ 总运行次数:
+ 0
+
+
+ 成功率:
+ N/A
+
+ (0 成功 /
+ 0 失败)
+
+
-
-
-
-
-
-
- 成功次数:
- -
-
-
-
-
-
-
-
-
- 失败次数:
- -
-
-
-
-
-
-
-
-
- 总运行次数:
- -
-
-
-
-
-
-
+
+
- 成功率:
- -
-
+
+
+
@@ -772,1168 +166,147 @@
+
+
+ 注册进度: 0%
+
正在初始化注册流程...
-
-
-
账号列表
-
-
+
+
+
+
-
+
+
+
-
-
-
+
+
+
+
+
+
+ `);
+ 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 = `
+
+ ${startIndex + index + 1}
+
+ ${account.email}
+
+ ${account.status === 'active' ? '正常' : account.status === 'disabled' ? '停用' : '删除'}
+
+
+
+ ${maskPassword(account.password)}
+
+
+
+ ${renderTokenColumn(account.token, account.id)}
+ ${renderUsageProgress(account.usage_limit)}
+
+ ${account.created_at || '未知'}
+
+
+
+
+
+
+
+
+
+ `;
+ 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 = $('');
+ const paginationUl = $('
+
+ «
+
+
+ `);
+
+ // 页码按钮
+ for (let i = 1; i <= totalPages; i++) {
+ paginationUl.append(`
+
+ ${i}
+ ·
+ `);
+ }
+
+ // 下一页按钮
+ paginationUl.append(`
+
+
+ »
+
+
+ `);
+
+ 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 = $(`
+
+
+ `;
+}
+
+// 修改Token列的渲染方式
+function renderTokenColumn(token, accountId) {
+ return `
+
+
+ 查看Token
+
+
+ `;
+}
+
+// 加载配置函数
+function loadConfig() {
+ showLoading();
+ fetch('/config')
+ .then(res => res.json())
+ .then(data => {
+ hideLoading();
+ if (data.success) {
+ const config = data.data;
+ $("#browser-headless").val(config.BROWSER_HEADLESS.toString());
+ $("#dynamic-useragent").prop('checked', config.DYNAMIC_USERAGENT || false);
+
+ // 触发动态UA的change事件
+ $("#dynamic-useragent").trigger('change');
+
+ $("#browser-useragent").val(config.BROWSER_USER_AGENT);
+ $("#accounts-limit").val(config.MAX_ACCOUNTS);
+ $("#email-domains").val(config.EMAIL_DOMAINS);
+ $("#email-username").val(config.EMAIL_USERNAME);
+ $("#email-pin").val(config.EMAIL_PIN);
+ $("#browser-path").val(config.BROWSER_PATH || '');
+ $("#cursor-path").val(config.CURSOR_PATH || '');
+ } else {
+ showAlert(`加载配置失败: ${data.message || '未知错误'}`, 'danger');
+ }
+ })
+ .catch(error => {
+ console.error('加载配置时发生错误:', error);
+ hideLoading();
+ showAlert('加载配置失败,请稍后重试', 'danger');
+ });
+}
+
+// 保存配置函数
+function saveConfig() {
+ showLoading();
+ const isDynamicUA = $("#dynamic-useragent").prop('checked');
+
+ const config = {
+ BROWSER_HEADLESS: $("#browser-headless").val() === 'true',
+ DYNAMIC_USERAGENT: isDynamicUA,
+ BROWSER_USER_AGENT: isDynamicUA ? "" : $("#browser-useragent").val(),
+ MAX_ACCOUNTS: parseInt($("#accounts-limit").val()),
+ EMAIL_DOMAINS: $("#email-domains").val(),
+ EMAIL_USERNAME: $("#email-username").val(),
+ EMAIL_PIN: $("#email-pin").val(),
+ BROWSER_PATH: $("#browser-path").val(),
+ CURSOR_PATH: $("#cursor-path").val()
+ };
+
+ fetch('/config', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(config)
+ })
+ .then(res => res.json())
+ .then(data => {
+ hideLoading();
+ if (data.success) {
+ showAlert('配置已成功保存', 'success');
+ enableConfigForm(false); // 禁用编辑状态
+ } else {
+ showAlert(`保存配置失败: ${data.message || '未知错误'}`, 'danger');
+ }
+ })
+ .catch(error => {
+ console.error('保存配置时发生错误:', error);
+ hideLoading();
+ showAlert('保存配置失败,请稍后重试', 'danger');
+ });
+}
+
+// 启用/禁用配置表单
+function enableConfigForm(enable) {
+ const inputs = $('#config-form select, #config-form input');
+ if (enable) {
+ inputs.prop('disabled', false);
+ // 如果动态UA已启用,保持UA输入框禁用
+ if ($("#dynamic-useragent").prop('checked')) {
+ $("#browser-useragent").prop('disabled', true);
+ }
+ // 显示按钮容器而不是单个按钮
+ $('#config-actions').show();
+ $('#edit-config-btn').hide();
+ } else {
+ inputs.prop('disabled', true);
+ // 隐藏按钮容器
+ $('#config-actions').hide();
+ $('#edit-config-btn').show();
+ }
+}
+
+// 动态User-Agent切换逻辑
+$("#dynamic-useragent").change(function() {
+ const isDynamicUA = $(this).prop('checked');
+ if (isDynamicUA) {
+ $("#browser-useragent").prop('disabled', true);
+ $("#useragent-input-container").addClass('text-muted');
+ } else {
+ // 只有在编辑模式下才启用输入框
+ const isEditMode = !$("#edit-config-btn").is(":visible");
+ $("#browser-useragent").prop('disabled', !isEditMode);
+ $("#useragent-input-container").removeClass('text-muted');
+ }
+});
+
+// 修改任务状态显示函数,删除与暂停/恢复相关逻辑
+function updateTaskStatusDisplay(statusData) {
+ // 更新状态徽章
+ const statusBadge = $("#registration-status");
+ const taskStatusText = $("#task-status-text");
+ const taskIcon = $("#task-status i");
+
+ // 更新账号使用情况
+ $("#current-count").text(statusData.current_count);
+ $("#max-accounts").text(statusData.max_accounts);
+ $("#remaining-slots").text(`剩余: ${statusData.remaining_slots}`);
+
+ // 计算使用百分比并更新电池进度条
+ const usagePercent = Math.floor((statusData.current_count / statusData.max_accounts) * 100);
+ $(".battery-progress").attr("data-percent", usagePercent);
+ $(".battery-percent").text(`${usagePercent}%`);
+
+ // 更新任务详情
+ if (statusData.registration_details) {
+ const details = statusData.registration_details;
+
+ // 更新统计信息
+ if (details.statistics) {
+ $("#total-runs").text(details.statistics.total_runs);
+ $("#successful-runs").text(details.statistics.successful_runs);
+ $("#failed-runs").text(details.statistics.failed_runs);
+ $("#success-rate").text(details.statistics.success_rate);
+ }
+ }
+
+ console.log('task_status=>', statusData.task_status)
+ // 根据任务状态更新UI,删除暂停状态处理
+ switch(statusData.task_status) {
+ case "running":
+ statusBadge.removeClass("bg-success bg-warning bg-danger").addClass("bg-primary");
+ statusBadge.text("运行中");
+ taskStatusText.text("任务正在运行中");
+ taskIcon.removeClass("fa-check-circle fa-pause-circle fa-times-circle").addClass("fa-spinner fa-spin");
+ taskIcon.removeClass("text-success text-warning text-danger").addClass("text-primary");
+
+ // 只保留隐藏开始按钮和显示停止按钮的逻辑
+ $("#start-registration").hide();
+ $("#stop-registration").show();
+ break;
+
+ case "stopped":
+ default:
+ statusBadge.removeClass("bg-primary bg-warning bg-danger").addClass("bg-success");
+ statusBadge.text("空闲中");
+ taskStatusText.text(statusData.status_message || "系统空闲中,可以开始新任务");
+ taskIcon.removeClass("fa-spinner fa-spin fa-pause-circle fa-times-circle").addClass("fa-check-circle");
+ taskIcon.removeClass("text-primary text-warning text-danger").addClass("text-success");
+
+ // 只保留显示开始按钮和隐藏停止按钮的逻辑
+ $("#start-registration").show();
+ $("#stop-registration").hide();
+ break;
+ }
+
+ // 处理显示/隐藏注册详情面板
+ if (statusData.task_status === "running") {
+ $("#registration-details").show();
+ } else {
+ $("#registration-details").hide();
+ }
+}
diff --git a/static/js/menu.js b/static/js/menu.js
new file mode 100644
index 0000000..26e1568
--- /dev/null
+++ b/static/js/menu.js
@@ -0,0 +1,69 @@
+// 菜单项切换功能
+$(document).ready(function() {
+ // 初始化页面导航
+ initNavigation();
+
+ // 菜单项点击事件
+ $('.nav-link').click(function(e) {
+ e.preventDefault();
+
+ // 获取目标页面
+ const targetPage = $(this).data('page');
+
+ // 导航到该页面
+ navigateToPage(targetPage);
+ });
+
+ // 响应式处理 - 移动设备上点击菜单后自动收起
+ if ($(window).width() <= 768) {
+ $('.nav-link').click(function() {
+ $('body').removeClass('sidebar-open');
+ });
+ }
+
+ // 添加移动设备菜单切换按钮
+ $(' ')
+ .appendTo('body')
+ .click(function() {
+ $('body').toggleClass('sidebar-open');
+ });
+});
+
+// 初始化导航函数
+function initNavigation() {
+ // 检查URL中是否有哈希值
+ let targetPage = window.location.hash.substring(1); // 移除#符号
+
+ // 如果哈希值为空或无效,默认显示账号管理页面
+ if (!targetPage || !$('#' + targetPage).length) {
+ targetPage = 'tasks-accounts';
+ }
+
+ // 导航到目标页面
+ navigateToPage(targetPage);
+}
+
+// 导航到指定页面
+function navigateToPage(pageId) {
+ // 切换活动菜单
+ $('.nav-link').removeClass('active');
+ $(`.nav-link[data-page="${pageId}"]`).addClass('active');
+
+ // 切换显示页面
+ $('.page-content').removeClass('active');
+ $('#' + pageId).addClass('active');
+
+ // 更新URL哈希值,但不触发页面滚动
+ if (history.pushState) {
+ history.pushState(null, null, '#' + pageId);
+ } else {
+ window.location.hash = pageId;
+ }
+}
+
+// 窗口大小变化时的响应式处理
+$(window).resize(function() {
+ if ($(window).width() > 576) {
+ $('body').removeClass('sidebar-open');
+ }
+});
\ No newline at end of file
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
| # | -邮箱 | -密码 | -Token | -额度 | -注册时间 | -用量查询 | -操作 | -
|---|
+
+
+
+
+
-
+
+
+ 账号列表
+
+
+
+
+
+
+
+ | ID | +邮箱 | +创建时间 | +状态 | +操作 | +
|---|
-
+
+
+
+
+
+
+
+ 系统配置
+
+
+
+
+
+
+
+
@@ -1954,17 +327,7 @@
-
-
-
+
- 自动刷新(10秒/次)
-
-
- Loading...
-
-
@@ -1985,7 +348,7 @@
此Token用于Cursor客户端身份验证,请妥善保管
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/css/styles.css b/static/css/styles.css
new file mode 100644
index 0000000..9474da8
--- /dev/null
+++ b/static/css/styles.css
@@ -0,0 +1,780 @@
+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;
+}
+
+/* 添加侧边栏菜单样式 */
+.app-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+#sidebar {
+ width: 250px;
+ background: #f8f9fa;
+ border-right: 1px solid #dee2e6;
+ height: 100vh;
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 1000;
+ box-shadow: 0 0 10px rgba(0,0,0,0.05);
+}
+
+.sidebar-header {
+ padding: 15px;
+ text-align: center;
+ border-bottom: 1px solid #dee2e6;
+}
+
+.sidebar-header h4 {
+ margin: 0;
+ font-size: 1.2rem;
+ color: #343a40;
+}
+
+.nav-menu {
+ list-style: none;
+ padding: 15px 0;
+ margin: 0;
+}
+
+.nav-item {
+ margin: 5px 0;
+}
+
+.nav-link {
+ display: flex;
+ align-items: center;
+ padding: 10px 15px;
+ color: #495057;
+ text-decoration: none;
+ border-left: 3px solid transparent;
+ transition: all 0.2s;
+}
+
+.nav-link:hover, .nav-link.active {
+ background: #e9ecef;
+ color: #0d6efd;
+ border-left-color: #0d6efd;
+}
+
+.nav-link i {
+ margin-right: 10px;
+ width: 20px;
+ text-align: center;
+}
+
+#content {
+ flex: 1;
+ margin-left: 250px;
+ padding: 20px;
+}
+
+.page-content {
+ display: none;
+}
+
+.page-content.active {
+ display: block;
+}
+
+/* 响应式样式 */
+@media (max-width: 768px) {
+ #sidebar {
+ width: 70px;
+ }
+
+ #content {
+ margin-left: 70px;
+ }
+
+ .sidebar-header h4, .nav-link span {
+ display: none;
+ }
+
+ .nav-link {
+ justify-content: center;
+ padding: 15px;
+ }
+
+ .nav-link i {
+ margin-right: 0;
+ }
+}
+
+@media (max-width: 576px) {
+ #sidebar {
+ width: 0;
+ overflow: hidden;
+ }
+
+ #content {
+ margin-left: 0;
+ }
+}
+
+/* 侧边栏底部文案样式 */
+.sidebar-footer {
+ padding: 15px;
+ text-align: center;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ border-top: 1px solid #dee2e6;
+ background-color: #f8f9fa;
+ font-size: 0.8rem;
+ color: #6c757d;
+}
+
+/* 调整侧边栏内容区域,为底部文案留出空间 */
+.nav-menu {
+ padding-bottom: 60px; /* 确保内容不会被底部文案遮挡 */
+}
+
+/* 在响应式布局中隐藏/显示footer文本 */
+@media (max-width: 768px) {
+ .sidebar-footer {
+ padding: 10px 5px;
+ font-size: 0.7rem;
+ }
+
+ .sidebar-footer span {
+ display: none;
+ }
+}
+
+@media (min-width: 769px) {
+ .sidebar-footer span {
+ display: inline-block;
+ }
+}
+
+/* 更新编辑配置按钮样式 - 占满一行 */
+#edit-config-btn {
+ display: block;
+ width: 100%;
+ margin-top: 1.5rem;
+ padding: 0.75rem 1rem;
+ font-weight: 500;
+ font-size: 1.05rem;
+ text-align: center;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+ transition: all 0.3s ease;
+ border: none;
+}
+
+#edit-config-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
+}
+
+#edit-config-btn:active {
+ transform: translateY(1px);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+}
+
+/* 调整徽章位置,使其更加合理 */
+#edit-config-btn .position-absolute {
+ top: -8px !important;
+ right: -8px !important;
+ left: auto !important;
+ transform: none !important;
+}
+
+/* 卡片标题与按钮的对齐 */
+.card-header.bg-white.d-flex {
+ padding: 1rem 1.25rem;
+}
+
+/* 配置按钮样式 */
+.config-actions button {
+ padding: 0.75rem 1rem;
+ font-weight: 500;
+ transition: all 0.3s ease;
+}
+
+#cancel-config-btn {
+ background-color: #6c757d;
+ border: none;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12);
+}
+
+#cancel-config-btn:hover {
+ background-color: #5a6268;
+ transform: translateY(-2px);
+ box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
+}
+
+#save-config-btn {
+ background-color: #198754;
+ border: none;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12);
+}
+
+#save-config-btn:hover {
+ background-color: #157347;
+ transform: translateY(-2px);
+ box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
+}
diff --git a/static/js/app.js b/static/js/app.js
new file mode 100644
index 0000000..da39c8b
--- /dev/null
+++ b/static/js/app.js
@@ -0,0 +1,1183 @@
+// 全局变量
+const REFRESH_INTERVAL = 10000; // 10秒
+let currentPage = 1;
+const itemsPerPage = 10;
+
+// 页面加载完成后执行
+$(document).ready(function() {
+ // 初始化应用
+ initializeApplication();
+});
+
+// 应用初始化函数 - 提高代码组织性
+function initializeApplication() {
+ // 加载配置
+ loadConfig();
+
+ // 初始加载数据
+ 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();
+ }
+ });
+
+ // 可以添加更多事件绑定...
+
+ // 在bindEventHandlers函数中添加配置相关事件
+ $("#edit-config-btn").click(function() {
+ enableConfigForm(true);
+ });
+
+ $("#cancel-config-btn").click(function() {
+ enableConfigForm(false);
+ loadConfig(); // 重新加载配置
+ });
+
+ $("#config-form").submit(function(e) {
+ e.preventDefault();
+ saveConfig();
+ });
+}
+
+// 全局变量
+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);
+ }
+
+ updateTaskStatusDisplay(data);
+
+ // 添加状态消息显示
+ 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(`
+
+ 自动刷新(10秒/次)
+
+
+ Loading...
+
+
+
+
+ 暂无账号数据
+
+ ${account.status !== 'active' ?
+ `
+
+ ` : ''}
+
+ ${account.status !== 'disabled' ?
+ `
+
+ ` : ''}
+
+ ${account.status !== 'deleted' ?
+ `
+
+ ` : ''}
+
+
+
+
+
+
+
+ `);
+
+ $('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 = $(`
+
+
+
+
+
+
+
+ 账号用量信息
+
+
+
+ 邮箱: ${data.email}
+
+
+ 剩余额度: ${data.usage.remaining_balance !== null ? data.usage.remaining_balance : '未知'}
+
+
+ 剩余天数: ${data.usage.remaining_days !== null ? data.usage.remaining_days : '未知'}
+
+
+ 状态:
+
+ ${data.usage.status === 'active' ? '活跃' : '不活跃'}
+
+
+
+ 更新时间: ${formatDateTime(data.timestamp)}
+
+
+ 关闭
+
+
+ ${isSpecial ? '' : ''}${message}
+
+
+ `);
+
+ $("#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 `
+
+ ${premiumUsed}
+ /
+ ${premiumTotal}
+ (剩余: ${premiumRemaining})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${premiumPercent}%
+