Merge pull request 'feat: 运营后台完善微信提现审核列表和批量处理' (#3) from feature/withdraw-review-ops into main
Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
+1011
-62
File diff suppressed because it is too large
Load Diff
+78
-1
@@ -71,7 +71,8 @@ export interface WithdrawOrder {
|
||||
user_id: number;
|
||||
out_bill_no: string;
|
||||
amount_cents: number;
|
||||
status: string; // pending / success / failed
|
||||
user_name: string | null;
|
||||
status: string; // reviewing / pending / success / failed / rejected
|
||||
wechat_state: string | null;
|
||||
transfer_bill_no: string | null;
|
||||
fail_reason: string | null;
|
||||
@@ -79,6 +80,82 @@ export interface WithdrawOrder {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface WithdrawSummary {
|
||||
reviewing_count: number;
|
||||
reviewing_amount_cents: number;
|
||||
pending_count: number;
|
||||
failed_count: number;
|
||||
today_success_count: number;
|
||||
today_success_amount_cents: number;
|
||||
today_rejected_count: number;
|
||||
}
|
||||
|
||||
export interface WithdrawUserSnapshot {
|
||||
id: number;
|
||||
phone: string;
|
||||
nickname: string | null;
|
||||
status: string;
|
||||
wechat_nickname: string | null;
|
||||
wechat_avatar_url: string | null;
|
||||
created_at: string;
|
||||
last_login_at: string;
|
||||
cash_balance_cents: number;
|
||||
withdraw_total: number;
|
||||
withdraw_success_cents: number;
|
||||
}
|
||||
|
||||
export interface WithdrawDetail {
|
||||
order: WithdrawOrder;
|
||||
user: WithdrawUserSnapshot | null;
|
||||
risk_flags: string[];
|
||||
risk_score: number;
|
||||
recent_withdraws: WithdrawOrder[];
|
||||
recent_cash_transactions: CashTxn[];
|
||||
audit_logs: AuditLog[];
|
||||
}
|
||||
|
||||
export interface WithdrawBulkItemResult {
|
||||
out_bill_no: string;
|
||||
ok: boolean;
|
||||
status: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface WithdrawBulkResult {
|
||||
total: number;
|
||||
success: number;
|
||||
failed: number;
|
||||
items: WithdrawBulkItemResult[];
|
||||
}
|
||||
|
||||
export interface WithdrawLedgerCheck {
|
||||
ok: boolean;
|
||||
cash_balance_total_cents: number;
|
||||
cash_transaction_total_cents: number;
|
||||
balance_diff_cents: number;
|
||||
missing_withdraw_txn_count: number;
|
||||
missing_refund_txn_count: number;
|
||||
duplicate_refund_txn_count: number;
|
||||
refund_txn_on_non_terminal_count: number;
|
||||
}
|
||||
|
||||
export interface WxpayHealthCheck {
|
||||
ok: boolean;
|
||||
wxpay_configured: boolean;
|
||||
wxpay_auth_configured: boolean;
|
||||
private_key_path: string;
|
||||
private_key_exists: boolean;
|
||||
private_key_loadable: boolean;
|
||||
public_key_path: string;
|
||||
public_key_exists: boolean;
|
||||
public_key_loadable: boolean;
|
||||
auth_notify_url_configured: boolean;
|
||||
auto_reconcile_enabled: boolean;
|
||||
auto_reconcile_interval_sec: number;
|
||||
auto_reconcile_older_than_minutes: number;
|
||||
issues: string[];
|
||||
}
|
||||
|
||||
export interface Feedback {
|
||||
id: number;
|
||||
user_id: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { api } from './api';
|
||||
import type { CursorPage } from './types';
|
||||
|
||||
@@ -12,26 +12,32 @@ export function useCursorList<T>(url: string, filters: Record<string, unknown>,
|
||||
const [items, setItems] = useState<T[]>([]);
|
||||
const [nextCursor, setNextCursor] = useState<number | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const requestSeq = useRef(0);
|
||||
|
||||
const filtersKey = JSON.stringify(filters);
|
||||
|
||||
const load = useCallback(
|
||||
async (cursor: number | null) => {
|
||||
const seq = requestSeq.current + 1;
|
||||
requestSeq.current = seq;
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: Record<string, unknown> = { ...JSON.parse(filtersKey), limit };
|
||||
if (cursor != null) params.cursor = cursor;
|
||||
const { data } = await api.get<CursorPage<T>>(url, { params });
|
||||
if (requestSeq.current !== seq) return;
|
||||
setItems((prev) => (cursor == null ? data.items : [...prev, ...data.items]));
|
||||
setNextCursor(data.next_cursor);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (requestSeq.current === seq) setLoading(false);
|
||||
}
|
||||
},
|
||||
[url, filtersKey, limit],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setItems([]);
|
||||
setNextCursor(null);
|
||||
load(null);
|
||||
}, [load]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user