From 08a3bcb683dbe62671f0686a6a0116d09edbb13d Mon Sep 17 00:00:00 2001 From: linkeyu <798648091@qq.com> Date: Fri, 31 Jul 2026 16:50:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E9=99=90=E5=88=B6=E7=AD=96=E7=95=A5=E9=A1=B5=E9=9D=A2=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一白名单时间口径并接入带乱序保护的分页列表;优化风控页路由跳转、无手机号禁用提示及后端分组计数复用。 --- src/app/(main)/limit-whitelist/page.tsx | 103 ++++++++++++------------ src/app/(main)/risk-monitor/page.tsx | 24 +++--- src/lib/usePagedList.ts | 9 ++- 3 files changed, 71 insertions(+), 65 deletions(-) diff --git a/src/app/(main)/limit-whitelist/page.tsx b/src/app/(main)/limit-whitelist/page.tsx index dc21a67..f720e76 100644 --- a/src/app/(main)/limit-whitelist/page.tsx +++ b/src/app/(main)/limit-whitelist/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Alert, App, @@ -26,6 +26,8 @@ import { EditOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons'; import dayjs, { type Dayjs } from 'dayjs'; import { api, errMsg } from '@/lib/api'; +import { formatUtcTime, utcDayjs } from '@/lib/format'; +import { usePagedList } from '@/lib/usePagedList'; import type { LimitOverride, LimitRule, @@ -71,8 +73,7 @@ interface WhitelistFilters { const asIso = (value?: number | null) => value ? dayjs(value).toISOString() : null; -const showTime = (value: string | null) => - value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'; +const showTime = (value: string | null) => formatUtcTime(value); const normalizeSubjectValue = ( subjectType: LimitSubjectType, value: string, @@ -148,11 +149,6 @@ function RuleSelector({ export default function LimitWhitelistPage() { const { message, modal } = App.useApp(); const [rules, setRules] = useState([]); - const [rows, setRows] = useState([]); - const [total, setTotal] = useState(0); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - const [loading, setLoading] = useState(true); const [keyword, setKeyword] = useState(''); const [subjectType, setSubjectType] = useState(); const [ruleFilter, setRuleFilter] = useState(); @@ -180,37 +176,45 @@ export default function LimitWhitelistPage() { createPreset?.subject_type || 'phone'; - const load = useCallback(async () => { - setLoading(true); - try { - const [ruleResponse, overrideResponse] = await Promise.all([ - api.get('/admin/api/limit-whitelist/rules'), - api.get('/admin/api/limit-whitelist/subjects', { - params: { - keyword: appliedFilters.keyword.trim() || undefined, - subject_type: appliedFilters.subjectType, - rule_code: appliedFilters.ruleCode, - offset: (page - 1) * pageSize, - limit: pageSize, - }, - }), - ]); - setRules(ruleResponse.data); - setRows(overrideResponse.data.items); - setTotal(overrideResponse.data.total); - } catch (error) { - message.error(errMsg(error, '白名单数据加载失败')); - } finally { - setLoading(false); - } - }, [appliedFilters, message, page, pageSize]); + const subjectFilters = useMemo( + () => ({ + keyword: appliedFilters.keyword.trim() || undefined, + subject_type: appliedFilters.subjectType, + rule_code: appliedFilters.ruleCode, + }), + [appliedFilters], + ); + const { + items: rows, + total, + page, + pageSize, + loading, + onChange: onPageChange, + reload: reloadSubjects, + } = usePagedList( + '/admin/api/limit-whitelist/subjects', + subjectFilters, + 10, + 'offset', + ); useEffect(() => { - void load(); - }, [load]); + let active = true; + void api + .get('/admin/api/limit-whitelist/rules') + .then((response) => { + if (active) setRules(response.data); + }) + .catch((error) => { + if (active) message.error(errMsg(error, '限制规则加载失败')); + }); + return () => { + active = false; + }; + }, [message]); const applyFilters = () => { - setPage(1); setAppliedFilters({ keyword, subjectType, @@ -290,8 +294,8 @@ export default function LimitWhitelistPage() { enabled: editing.items.every((item) => item.enabled), valid_range: expiresAt ? [ - startsAt[0] ? dayjs(startsAt[0]).valueOf() : Date.now(), - dayjs(expiresAt).valueOf(), + startsAt[0] ? utcDayjs(startsAt[0]).valueOf() : Date.now(), + utcDayjs(expiresAt).valueOf(), ] : null, reason: reasons.size === 1 ? Array.from(reasons)[0] : '', @@ -417,7 +421,7 @@ export default function LimitWhitelistPage() { if (new URLSearchParams(window.location.search).get('create') === '1') { window.history.replaceState({}, '', window.location.pathname); } - await load(); + await reloadSubjects(); } catch (error) { message.error(errMsg(error)); } finally { @@ -434,7 +438,10 @@ export default function LimitWhitelistPage() { }); message.success('全局限制已更新,业务接口即时读取新值'); setGlobalRule(null); - await load(); + const response = await api.get( + '/admin/api/limit-whitelist/rules', + ); + setRules(response.data); } catch (error) { message.error(errMsg(error)); } finally { @@ -452,10 +459,10 @@ export default function LimitWhitelistPage() { enabled, }); message.success(enabled ? '白名单已启用' : '白名单已停用'); - await load(); + await reloadSubjects(); } catch (error) { message.error(errMsg(error, '白名单启用状态更新失败')); - await load(); + await reloadSubjects(); } finally { setTogglingSubject(null); } @@ -525,12 +532,7 @@ export default function LimitWhitelistPage() { title: '解除限制项', key: 'rule', render: (_, row) => { - const groupCounts = Object.fromEntries( - RULE_GROUPS.map((group) => [ - group, - row.items.filter((item) => item.rule_group === group).length, - ]), - ); + const groupCounts = row.group_counts; return ( `共 ${count} 个主体`, - onChange: (nextPage, nextPageSize) => { - setPage( - nextPageSize !== pageSize ? 1 : nextPage, - ); - setPageSize(nextPageSize); - }, + onChange: onPageChange, }} /> diff --git a/src/app/(main)/risk-monitor/page.tsx b/src/app/(main)/risk-monitor/page.tsx index f0e5b74..272f2fe 100644 --- a/src/app/(main)/risk-monitor/page.tsx +++ b/src/app/(main)/risk-monitor/page.tsx @@ -24,6 +24,7 @@ import { RightOutlined, } from '@ant-design/icons'; import dayjs from 'dayjs'; +import { useRouter } from 'next/navigation'; import { api, errMsg } from '@/lib/api'; import { formatUtcTime, utcDayjs } from '@/lib/format'; import type { @@ -272,6 +273,7 @@ function DetailTable({ } export default function RiskMonitorPage() { + const router = useRouter(); const { message, modal } = App.useApp(); const [summary, setSummary] = useState(null); const [lists, setLists] = useState>>({}); @@ -627,9 +629,9 @@ export default function RiskMonitorPage() { subject_value: subjectValue, rule_code: ruleCode[row.kind], }); - window.location.assign(`/limit-whitelist?${params.toString()}`); + router.push(`/limit-whitelist?${params.toString()}`); }, - [message], + [message, router], ); const columns = useCallback( @@ -642,9 +644,15 @@ export default function RiskMonitorPage() { width: 280, render: (_: unknown, row: RiskIncidentItem) => { const open = expanded.has(row.incident_id); - const canAddToWhitelist = - row.kind === 'compare' || - !row.subject_id.startsWith('legacy-ip:'); + const whitelistUnavailableReason = + row.kind === 'compare' + ? row.phone + ? undefined + : '该风险记录没有关联手机号,暂时无法加入白名单' + : row.subject_id.startsWith('legacy-ip:') + ? '旧客户端未上报真实设备 ID,无法加入设备白名单' + : undefined; + const canAddToWhitelist = !whitelistUnavailableReason; return (