Merge client_id+token into credential popup, add refund button to actions

- Merge client_id and token columns into single "credential" button
- Click to view in modal with copy buttons for each
- Add refund toggle button in actions row with confirm dialog
- Remove separate refund_received column, cleaner layout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-06 01:52:41 +08:00
parent d8d651240a
commit 1638d2c057
3 changed files with 119 additions and 35 deletions

View File

@@ -92,11 +92,9 @@
<th class="col-num">#</th>
<th>邮箱</th>
<th>密码</th>
<th class="col-hide-mobile">客户ID</th>
<th>令牌</th>
<th>凭证</th>
<th>支付状态</th>
<th>退款状态</th>
<th>退款到账</th>
<th>支付时间</th>
<th>退款时间</th>
<th>封号时间</th>
@@ -107,7 +105,7 @@
</thead>
<tbody id="accountTableBody">
<tr>
<td colspan="14">
<td colspan="12">
<div class="no-data">
<i class="bi bi-inbox"></i>
<div>暂无邮箱数据,点击"粘贴导入"添加</div>
@@ -367,6 +365,34 @@
</div>
</div>
<!-- 凭证查看弹窗 -->
<div class="paste-modal" id="credentialModal">
<div class="paste-modal-content" style="max-width:500px;">
<div class="paste-modal-header">
<div class="paste-modal-icon"><i class="bi bi-key"></i></div>
<div>
<h3 class="paste-modal-title">账户凭证</h3>
<p class="paste-modal-subtitle" id="credModalEmail"></p>
</div>
</div>
<div class="note-fields">
<div class="note-field-group">
<label class="input-label">Client ID</label>
<div class="cred-value-box" id="credClientId"></div>
</div>
<div class="note-field-group">
<label class="input-label">Refresh Token</label>
<div class="cred-value-box" id="credToken" style="word-break:break-all;max-height:120px;overflow-y:auto;"></div>
</div>
</div>
<div class="paste-modal-buttons">
<button class="btn btn-primary" id="copyCredClientIdBtn"><i class="bi bi-clipboard"></i> 复制ID</button>
<button class="btn btn-primary" id="copyCredTokenBtn"><i class="bi bi-clipboard"></i> 复制Token</button>
<button class="btn btn-cancel" id="closeCredentialBtn">关闭</button>
</div>
</div>
</div>
<!-- Toast 提示 -->
<div class="toast-container" id="toastContainer"></div>

View File

@@ -131,6 +131,29 @@ class MailManager {
}
});
// 凭证弹窗事件
document.getElementById('closeCredentialBtn').addEventListener('click', () => {
document.getElementById('credentialModal').classList.remove('show');
});
document.getElementById('credentialModal').addEventListener('click', (e) => {
if (e.target === e.currentTarget) e.currentTarget.classList.remove('show');
});
document.getElementById('copyCredClientIdBtn').addEventListener('click', () => {
this.copyToClipboard(document.getElementById('credClientId').textContent);
});
document.getElementById('copyCredTokenBtn').addEventListener('click', () => {
this.copyToClipboard(document.getElementById('credToken').textContent);
});
// 凭证查看按钮 — 事件委托
document.getElementById('accountTableBody').addEventListener('click', (e) => {
const btn = e.target.closest('.credential-btn');
if (!btn) return;
e.preventDefault();
e.stopPropagation();
this.showCredentialModal(btn.dataset.credEmail);
});
// 退款到账按钮 — 事件委托
document.getElementById('accountTableBody').addEventListener('click', (e) => {
const btn = e.target.closest('.refund-received-btn');
@@ -257,7 +280,7 @@ class MailManager {
async loadAccounts() {
const tbody = document.getElementById('accountTableBody');
tbody.innerHTML = `<tr><td colspan="14"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`;
tbody.innerHTML = `<tr><td colspan="12"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`;
try {
// 有筛选时加载全部数据,否则分页加载
@@ -277,11 +300,11 @@ class MailManager {
await this.loadClaudePaymentStatuses();
this.applyFilterAndRender();
} else {
tbody.innerHTML = `<tr><td colspan="14"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`;
tbody.innerHTML = `<tr><td colspan="12"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`;
}
} catch (err) {
console.error('加载账号失败:', err);
tbody.innerHTML = `<tr><td colspan="14"><div class="no-data"><i class="bi bi-wifi-off"></i><div>网络错误</div></div></td></tr>`;
tbody.innerHTML = `<tr><td colspan="12"><div class="no-data"><i class="bi bi-wifi-off"></i><div>网络错误</div></div></td></tr>`;
}
}
@@ -316,7 +339,7 @@ class MailManager {
renderAccounts() {
const tbody = document.getElementById('accountTableBody');
if (!this.accounts.length) {
tbody.innerHTML = `<tr><td colspan="14"><div class="no-data"><i class="bi bi-inbox"></i><div>暂无邮箱数据</div></div></td></tr>`;
tbody.innerHTML = `<tr><td colspan="12"><div class="no-data"><i class="bi bi-inbox"></i><div>暂无邮箱数据</div></div></td></tr>`;
return;
}
@@ -347,17 +370,8 @@ class MailManager {
${pwd ? `<button class="copy-icon-btn" data-copy-field="pwd" data-copy-key="${this.escapeHtml(email)}" title="复制"><i class="bi bi-clipboard"></i></button>` : ''}
</div>
</td>
<td class="col-hide-mobile">
<div class="value-container">
<span class="value-text value-secret" title="${this.escapeHtml(cid)}">${this.truncate(cid, 16)}</span>
${cid ? `<button class="copy-icon-btn" data-copy-field="cid" data-copy-key="${this.escapeHtml(email)}" title="复制"><i class="bi bi-clipboard"></i></button>` : ''}
</div>
</td>
<td>
<div class="value-container">
<span class="value-text value-secret" title="点击复制">${token ? this.truncate(token, 20) : '-'}</span>
${token ? `<button class="copy-icon-btn" data-copy-field="token" data-copy-key="${this.escapeHtml(email)}" title="复制"><i class="bi bi-clipboard"></i></button>` : ''}
</div>
<button class="note-cell-btn credential-btn" data-cred-email="${this.escapeHtml(email)}"><i class="bi bi-key"></i> 查看</button>
</td>
${this.renderClaudeColumns(email)}
<td>
@@ -365,6 +379,7 @@ class MailManager {
<button class="view" onclick="app.openMailbox('${this.escapeHtml(email)}', 'INBOX')">收件箱</button>
<button class="view" onclick="app.openMailbox('${this.escapeHtml(email)}', 'Junk')">垃圾箱</button>
<button class="view claude-check-btn" onclick="app.checkSingleClaudePayment('${this.escapeHtml(email)}', this)">Claude</button>
${this.renderRefundBtn(email)}
<button class="delete" onclick="app.deleteAccount('${this.escapeHtml(email)}')">删除</button>
</div>
</td>
@@ -705,6 +720,39 @@ class MailManager {
// Claude 支付检测
// ====================================================================
renderRefundBtn(email) {
const info = this.claudePaymentStatuses[email];
const isReceived = info && info.refund_received === '1';
if (isReceived) {
return `<button class="view" style="background:rgba(16,185,129,0.1);color:#059669;border-color:rgba(16,185,129,0.15);" onclick="app.confirmRefundToggle('${this.escapeHtml(email)}')">已到账</button>`;
}
return `<button class="view" onclick="app.confirmRefundToggle('${this.escapeHtml(email)}')">退款</button>`;
}
confirmRefundToggle(email) {
const info = this.claudePaymentStatuses[email];
const isReceived = info && info.refund_received === '1';
const msg = isReceived
? `确定将 ${email} 的退款状态改为【未到账】吗?`
: `确定将 ${email} 标记为【退款已到账】吗?`;
if (confirm(msg)) {
this.toggleRefundReceived(email);
}
}
showCredentialModal(email) {
const data = this._accountDataMap[email];
if (!data) return;
const cid = data.cid || '-';
const token = data.token || '-';
// 复用 paste-modal 样式
const modal = document.getElementById('credentialModal');
document.getElementById('credModalEmail').textContent = email;
document.getElementById('credClientId').textContent = cid;
document.getElementById('credToken').textContent = token;
modal.classList.add('show');
}
async toggleRefundReceived(email) {
try {
const resp = await fetch(`/api/tools/refund-received/${encodeURIComponent(email)}`, { method: 'POST' });
@@ -739,7 +787,6 @@ class MailManager {
if (!info) {
return `<td><span class="claude-badge claude-unknown">未检测</span></td>
<td><span class="claude-badge claude-unknown">未检测</span></td>
<td>-</td>
<td class="claude-time">-</td>
<td class="claude-time">-</td>
<td class="claude-time">-</td>
@@ -777,12 +824,8 @@ class MailManager {
? `<span class="claude-badge claude-suspended">${fmtTime(info.suspended_time)}</span>`
: '-';
const isRefundReceived = info.refund_received === '1';
const refundReceivedBtn = `<button class="claude-badge ${isRefundReceived ? 'claude-paid' : 'claude-unknown'} refund-received-btn" data-email="${this.escapeHtml(email)}" style="cursor:pointer;border:none;" title="点击切换">${isRefundReceived ? '已到账' : '未到账'}</button>`;
return `<td>${paidBadge}</td>
<td>${refundBadge}</td>
<td>${refundReceivedBtn}</td>
<td class="claude-time">${fmtTime(info.payment_time)}</td>
<td class="claude-time">${fmtTime(info.refund_time)}</td>
<td>${suspendedHtml}</td>

View File

@@ -437,19 +437,17 @@ body {
font-weight: 600;
}
.data-table th:nth-child(2) { width: 14%; min-width: 150px; } /* 邮箱 */
.data-table th:nth-child(2) { width: 16%; min-width: 160px; } /* 邮箱 */
.data-table th:nth-child(3) { width: 6%; min-width: 70px; } /* 密码 */
.data-table th:nth-child(4) { width: 8%; min-width: 90px; } /* 客户ID */
.data-table th:nth-child(5) { width: 10%; min-width: 110px; } /* 令牌 */
.data-table th:nth-child(6) { width: 65px; } /* 支付状态 */
.data-table th:nth-child(7) { width: 65px; } /* 退款状态 */
.data-table th:nth-child(8) { width: 65px; } /* 退款到账 */
.data-table th:nth-child(9) { width: 85px; } /* 支付时间 */
.data-table th:nth-child(10) { width: 85px; } /* 退款时间 */
.data-table th:nth-child(11) { width: 85px; } /* 封号时间 */
.data-table th:nth-child(12) { width: 120px; } /* 备注/卡号 */
.data-table th:nth-child(13) { width: 150px; } /* 代理 */
.data-table th:nth-child(14) { width: 190px; } /* 操作 */
.data-table th:nth-child(4) { width: 55px; } /* 凭证 */
.data-table th:nth-child(5) { width: 65px; } /* 支付状态 */
.data-table th:nth-child(6) { width: 65px; } /* 退款状态 */
.data-table th:nth-child(7) { width: 85px; } /* 支付时间 */
.data-table th:nth-child(8) { width: 85px; } /* 退款时间 */
.data-table th:nth-child(9) { width: 85px; } /* 封号时间 */
.data-table th:nth-child(10) { width: 120px; } /* 备注/卡号 */
.data-table th:nth-child(11) { width: 150px; } /* 代理 */
.data-table th:nth-child(12) { width: 220px; } /* 操作 */
/* 单元格内容 */
.value-container {
@@ -1318,6 +1316,23 @@ body {
}
.note-card i { font-size: 10px; }
.cred-value-box {
padding: 10px 14px;
background: rgba(248, 250, 252, 0.8);
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 12px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
color: #334155;
word-break: break-all;
line-height: 1.5;
user-select: all;
}
.credential-btn {
font-size: 11px !important;
}
.note-fields {
display: flex;
flex-direction: column;