ca7c3f27f0
## 需求背景 新增统一白名单管理页面,让运营人员能够查看和调整全局限制,并按手机号或设备批量配置临时不限或风险免告警。 ## 主要改动 - 新增白名单导航与完整管理页面 - 全局限制按五类分页展示并支持修改 - 白名单按主体聚合,展示各分类数量及悬浮明细 - 支持手机号与设备搜索、批量多选限制项和统一有效期 - 支持手动筛选、十条分页、主体点击编辑及启用开关 - 风控监控增加快捷加入白名单入口 - 旧引导视频配置页与统一限制入口保持一致 ## 验证 - Next.js 生产构建通过 - TypeScript 类型检查通过 - 共 26 个页面完成静态/动态构建 - 已与后端 PR #207 配套验证 --------- Co-authored-by: linkeyu <798648091@qq.com> Reviewed-on: #99 Co-authored-by: linkeyu <linkeyu@wonderable.ai> Co-committed-by: linkeyu <linkeyu@wonderable.ai>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { api } from './api';
|
|
import type { CursorPage } from './types';
|
|
|
|
/**
|
|
* 页码分页列表 hook(对接后端 offset 分页 + total)。
|
|
* 与 useCursorList(「加载更多」)并存:需要页码/跳页/共 N 条的主列表页用本 hook。
|
|
*
|
|
* 约定:后端 cursor 即 offset,本 hook 按 (page-1)*pageSize 计算;CursorPage.total 为符合
|
|
* 筛选条件的总条数(供 antd Table pagination 渲染)。filters 变化自动回第 1 页重载。
|
|
*/
|
|
export function usePagedList<T>(
|
|
url: string,
|
|
filters: Record<string, unknown>,
|
|
initialPageSize = 20,
|
|
offsetParam: 'cursor' | 'offset' = 'cursor',
|
|
) {
|
|
const [items, setItems] = useState<T[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(1);
|
|
const [pageSize, setPageSize] = useState(initialPageSize);
|
|
const [loading, setLoading] = useState(false);
|
|
const requestSeq = useRef(0);
|
|
|
|
const filtersKey = JSON.stringify(filters);
|
|
|
|
const load = useCallback(
|
|
async (p: number, ps: number) => {
|
|
const seq = requestSeq.current + 1;
|
|
requestSeq.current = seq;
|
|
setLoading(true);
|
|
try {
|
|
const params = {
|
|
...JSON.parse(filtersKey),
|
|
limit: ps,
|
|
[offsetParam]: (p - 1) * ps,
|
|
};
|
|
const { data } = await api.get<CursorPage<T>>(url, { params });
|
|
if (requestSeq.current !== seq) return; // 丢弃过期响应(快速切页/筛选)
|
|
setItems(data.items);
|
|
setTotal(data.total ?? 0);
|
|
} finally {
|
|
if (requestSeq.current === seq) setLoading(false);
|
|
}
|
|
},
|
|
[url, filtersKey, offsetParam],
|
|
);
|
|
|
|
// 筛选变化:回第 1 页重载
|
|
useEffect(() => {
|
|
setPage(1);
|
|
load(1, pageSize);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [load]);
|
|
|
|
// antd Table pagination.onChange(page, pageSize):页变或每页条数变都走这里
|
|
const onChange = (p: number, ps: number) => {
|
|
if (ps !== pageSize) {
|
|
// 每页条数变 → 回第 1 页,避免越界空页
|
|
setPageSize(ps);
|
|
setPage(1);
|
|
load(1, ps);
|
|
} else {
|
|
setPage(p);
|
|
load(p, ps);
|
|
}
|
|
};
|
|
|
|
return {
|
|
items,
|
|
total,
|
|
page,
|
|
pageSize,
|
|
loading,
|
|
onChange,
|
|
reload: () => load(page, pageSize), // 写操作后刷新当前页
|
|
};
|
|
}
|