diff --git a/src/app/(main)/config/HomeMarqueeSeeds.tsx b/src/app/(main)/config/HomeMarqueeSeeds.tsx index d138052..9f54e0c 100644 --- a/src/app/(main)/config/HomeMarqueeSeeds.tsx +++ b/src/app/(main)/config/HomeMarqueeSeeds.tsx @@ -42,13 +42,15 @@ interface BulkVals { enabled: boolean; } -interface PreviewItem { +interface RealRecord { masked_user: string; saved_amount_cents: number; - time: string; + created_at: string; + user_id: number; } const yuan = (cents: number) => (cents / 100).toFixed(2); +const MODE_LABEL: Record = { mixed: '混播', real: '只真实', seed: '只种子' }; /** 首页轮播「种子」管理(真实记录不足时的兜底假数据,现为「生成规则」)。「系统配置 / 首页」里的一个区块。 */ export default function HomeMarqueeSeeds() { @@ -66,9 +68,12 @@ export default function HomeMarqueeSeeds() { const [bulkOpen, setBulkOpen] = useState(false); const [bulkForm] = Form.useForm(); - const [previewOpen, setPreviewOpen] = useState(false); - const [previewItems, setPreviewItems] = useState([]); - const [previewLoading, setPreviewLoading] = useState(false); + // 「可展示的真实记录」分页浏览(替代原随机预览):翻遍所有 success+省>0 的真实记录 + const REC_PAGE_SIZE = 8; + const [recItems, setRecItems] = useState([]); + const [recTotal, setRecTotal] = useState(0); + const [recPage, setRecPage] = useState(0); // 0-based + const [recLoading, setRecLoading] = useState(false); const load = async () => { setLoading(true); @@ -94,6 +99,7 @@ export default function HomeMarqueeSeeds() { const changeMode = async (m: string) => { const prev = mode; setMode(m); + setRecPage(0); // 切模式回第 1 页(不同模式条数不同,避免停在越界页) setModeSaving(true); try { await api.patch('/admin/api/marquee-seeds/mode', { mode: m }); @@ -168,22 +174,29 @@ export default function HomeMarqueeSeeds() { } }; - // ===== 预览实际 feed ===== - const fetchPreview = async () => { - setPreviewLoading(true); + // ===== 可展示的记录:按「当前模式」分页浏览全部(不去重)===== + // 只真实=真实记录;只种子=各启用种子按生成逻辑各出一行;混播=真实+种子。与 app 同口径洗牌+去连簇, + // 后端固定种子 → 翻页稳定、能翻遍全部。 + const fetchRecords = async (page: number, m: string) => { + setRecLoading(true); try { - const { data } = await api.get<{ items: PreviewItem[] }>('/admin/api/marquee-seeds/preview?limit=8'); - setPreviewItems(data.items); + const { data } = await api.get<{ items: RealRecord[]; total: number }>( + '/admin/api/marquee-seeds/real-records', + { params: { offset: page * REC_PAGE_SIZE, limit: REC_PAGE_SIZE, mode: m } }, + ); + setRecItems(data.items); + setRecTotal(data.total); } catch (e) { message.error(errMsg(e)); } finally { - setPreviewLoading(false); + setRecLoading(false); } }; - const openPreview = () => { - setPreviewOpen(true); - fetchPreview(); - }; + // 页码或模式变化都重拉(切模式时 changeMode 会把页码重置到 0)。 + useEffect(() => { + fetchRecords(recPage, mode); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [recPage, mode]); const toggle = async (s: Seed, enabled: boolean) => { try { @@ -300,6 +313,84 @@ export default function HomeMarqueeSeeds() { 混播=真实优先+种子补位;只真实=不掺假数据(真实不足则少显示);只种子=只用下方种子/合成(演示用) + + {/* 可展示的真实记录:分页浏览全部(success+省>0,不去重、稳定倒序);app 轮播即从这些记录 + 随机打乱、同一用户去连簇后展示。翻页可看全所有真实用户的展示信息。 */} +
+
+ + 可展示的记录 · {MODE_LABEL[mode] ?? mode} · 共 {recTotal} 条 + + 当前模式下 app 轮播能露出的全部记录(不去重):只真实=真实、只种子=各种子生成、混播=真实+种子; + 已按 app 口径洗牌+去连簇,顺序固定便于翻页看全 + + + + + + 第 {recTotal === 0 ? 0 : recPage + 1}/{Math.max(1, Math.ceil(recTotal / REC_PAGE_SIZE))} 页 + + + +
+ +
+ {recItems.length === 0 && !recLoading ? ( +
+ 当前模式暂无可展示的记录 +
+ ) : ( + recItems.map((it, i) => ( +
+ + {it.masked_user} 比价后节省{' '} + {yuan(it.saved_amount_cents)} 元 + + {it.user_id > 0 ? `u${it.user_id}` : '种子'} + + + {it.created_at} +
+ )) + )} +
+
+
+ - - - - } - > -

- 客户端实际会看到的混播结果:真实记录会插队,种子随机抽取、金额/用户名每次都不同。点「换一批」可重新随机。 -

- -
- {previewItems.map((it, i) => ( -
- - {it.masked_user} 比价后节省{' '} - {yuan(it.saved_amount_cents)} 元 - - {it.time} -
- ))} -
-
- ); } diff --git a/src/app/(main)/withdraws/page.tsx b/src/app/(main)/withdraws/page.tsx index 478bd54..66b46e9 100644 --- a/src/app/(main)/withdraws/page.tsx +++ b/src/app/(main)/withdraws/page.tsx @@ -624,12 +624,27 @@ export default function WithdrawsPage() { const rejectAmount = rejectTargets.reduce((sum, item) => sum + item.amount_cents, 0); const ledgerIssues = ledger ? [ - ledger.balance_diff_cents ? `余额差额 ${yuan(ledger.balance_diff_cents)}` : '', - ledger.missing_withdraw_txn_count ? `缺扣款流水 ${ledger.missing_withdraw_txn_count} 笔` : '', - ledger.missing_refund_txn_count ? `缺退款流水 ${ledger.missing_refund_txn_count} 笔` : '', - ledger.duplicate_refund_txn_count ? `重复退款流水 ${ledger.duplicate_refund_txn_count} 类` : '', + // 普通现金账(coin_cash) + ledger.balance_diff_cents ? `现金余额差额 ${yuan(ledger.balance_diff_cents)}` : '', + ledger.missing_withdraw_txn_count ? `现金缺扣款流水 ${ledger.missing_withdraw_txn_count} 笔` : '', + ledger.missing_refund_txn_count ? `现金缺退款流水 ${ledger.missing_refund_txn_count} 笔` : '', + ledger.duplicate_refund_txn_count ? `现金重复退款流水 ${ledger.duplicate_refund_txn_count} 类` : '', ledger.refund_txn_on_non_terminal_count - ? `非退款终态存在退款流水 ${ledger.refund_txn_on_non_terminal_count} 笔` + ? `现金非退款终态存在退款流水 ${ledger.refund_txn_on_non_terminal_count} 笔` + : '', + // 邀请奖励金账(invite_cash) + ledger.invite_balance_diff_cents ? `邀请金余额差额 ${yuan(ledger.invite_balance_diff_cents)}` : '', + ledger.invite_missing_withdraw_txn_count + ? `邀请金缺扣款流水 ${ledger.invite_missing_withdraw_txn_count} 笔` + : '', + ledger.invite_missing_refund_txn_count + ? `邀请金缺退款流水 ${ledger.invite_missing_refund_txn_count} 笔` + : '', + ledger.invite_duplicate_refund_txn_count + ? `邀请金重复退款流水 ${ledger.invite_duplicate_refund_txn_count} 类` + : '', + ledger.invite_refund_txn_on_non_terminal_count + ? `邀请金非退款终态存在退款流水 ${ledger.invite_refund_txn_on_non_terminal_count} 笔` : '', ].filter(Boolean) : []; @@ -669,7 +684,7 @@ export default function WithdrawsPage() { message={ledger.ok ? '现金账本校验正常' : '现金账本存在异常'} description={ ledger.ok - ? `账户余额合计 ${yuan(ledger.cash_balance_total_cents)},现金流水净额 ${yuan(ledger.cash_transaction_total_cents)}` + ? `现金 余额 ${yuan(ledger.cash_balance_total_cents)} / 流水 ${yuan(ledger.cash_transaction_total_cents)};邀请金 余额 ${yuan(ledger.invite_cash_balance_total_cents)} / 流水 ${yuan(ledger.invite_cash_transaction_total_cents)}` : ledgerIssues.join(';') } action={ diff --git a/src/lib/types.ts b/src/lib/types.ts index 7f5ec3b..2e34983 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -233,6 +233,7 @@ export interface WithdrawBulkResult { export interface WithdrawLedgerCheck { ok: boolean; + // 普通现金账(coin_cash) cash_balance_total_cents: number; cash_transaction_total_cents: number; balance_diff_cents: number; @@ -240,6 +241,14 @@ export interface WithdrawLedgerCheck { missing_refund_txn_count: number; duplicate_refund_txn_count: number; refund_txn_on_non_terminal_count: number; + // 邀请奖励金账(invite_cash:与普通现金物理隔离,各自对账) + invite_cash_balance_total_cents: number; + invite_cash_transaction_total_cents: number; + invite_balance_diff_cents: number; + invite_missing_withdraw_txn_count: number; + invite_missing_refund_txn_count: number; + invite_duplicate_refund_txn_count: number; + invite_refund_txn_on_non_terminal_count: number; } export interface WxpayHealthCheck {