Add payment status filter dropdown (paid/unpaid/refunded/suspended)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-06 01:20:37 +08:00
parent 3c1715603d
commit 197c969e41
3 changed files with 72 additions and 9 deletions

View File

@@ -55,6 +55,14 @@
<i class="bi bi-credit-card"></i>
<span>Claude检测</span>
</button>
<select class="filter-select" id="paymentFilter" title="支付状态筛选">
<option value="">全部状态</option>
<option value="paid">已支付</option>
<option value="unpaid">未支付</option>
<option value="refunded">已退款</option>
<option value="suspended">已封号</option>
<option value="unchecked">未检测</option>
</select>
<button class="btn btn-icon" id="refreshAccountsBtn" title="刷新">
<i class="bi bi-arrow-clockwise"></i>
</button>

View File

@@ -50,6 +50,7 @@ class MailManager {
this.messages = [];
this.selectedId = null;
this.claudePaymentStatuses = {};
this.paymentFilter = '';
this.init();
}
@@ -104,6 +105,13 @@ class MailManager {
// Claude支付检测
document.getElementById('claudePaymentBtn').addEventListener('click', () => this.startClaudePaymentCheck());
// 支付状态筛选
document.getElementById('paymentFilter').addEventListener('change', (e) => {
this.paymentFilter = e.target.value;
this.page = 1;
this.loadAccounts();
});
// 刷新
document.getElementById('refreshAccountsBtn').addEventListener('click', () => this.loadAccounts());
@@ -242,22 +250,22 @@ class MailManager {
tbody.innerHTML = `<tr><td colspan="13"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`;
try {
const params = new URLSearchParams({
page: this.page,
page_size: this.pageSize
});
// 有筛选时加载全部数据,否则分页加载
const params = new URLSearchParams(
this.paymentFilter
? { page: 1, page_size: 9999 }
: { page: this.page, page_size: this.pageSize }
);
if (this.query) params.set('q', this.query);
const resp = await fetch(`/api/accounts/detailed?${params}`);
const result = await resp.json();
if (result.success && result.data) {
this.accounts = result.data.items || [];
this.total = result.data.total || 0;
this.page = result.data.page || 1;
this._allAccounts = result.data.items || [];
this._allTotal = result.data.total || 0;
await this.loadClaudePaymentStatuses();
this.renderAccounts();
this.renderPager();
this.applyFilterAndRender();
} else {
tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`;
}
@@ -267,6 +275,32 @@ class MailManager {
}
}
_matchPaymentFilter(email) {
const info = this.claudePaymentStatuses[email];
switch (this.paymentFilter) {
case 'paid': return info && !!info.payment_time;
case 'unpaid': return !info || !info.payment_time;
case 'refunded': return info && !!info.refund_time;
case 'suspended': return info && !!info.suspended_time;
case 'unchecked': return !info;
default: return true;
}
}
applyFilterAndRender() {
if (this.paymentFilter) {
const filtered = this._allAccounts.filter(acc => this._matchPaymentFilter(acc.email));
this.total = filtered.length;
const start = (this.page - 1) * this.pageSize;
this.accounts = filtered.slice(start, start + this.pageSize);
} else {
this.accounts = this._allAccounts;
this.total = this._allTotal;
}
this.renderAccounts();
this.renderPager();
}
renderAccounts() {
const tbody = document.getElementById('accountTableBody');
if (!this.accounts.length) {

View File

@@ -219,6 +219,27 @@ body {
height: 38px;
}
/* 筛选下拉 */
.filter-select {
height: 38px;
padding: 0 12px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
color: #475569;
background: rgba(255, 255, 255, 0.8);
outline: none;
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
}
.filter-select:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-box input::placeholder { color: #9ca3af; }
.search-box input:focus {
border-color: #667eea;