feat(操作统计): 抽屉内增加独立时间筛选
- 抽屉内提供 今天/近7天/近30天/自定义 时间选择,默认同步顶部当前值 - 切换时间自动刷新统计与明细,无需关闭看板调整顶部时间
This commit is contained in:
@ -277,6 +277,9 @@ export default function AdminDashboard() {
|
|||||||
const [opFilterUsers, setOpFilterUsers] = useState<Set<string>>(new Set()); // 多选人员
|
const [opFilterUsers, setOpFilterUsers] = useState<Set<string>>(new Set()); // 多选人员
|
||||||
const [opFilterTouched, setOpFilterTouched] = useState(false); // 用户是否主动操作过筛选
|
const [opFilterTouched, setOpFilterTouched] = useState(false); // 用户是否主动操作过筛选
|
||||||
const [opSort, setOpSort] = useState<{ key: string; order: "asc" | "desc" } | null>(null);
|
const [opSort, setOpSort] = useState<{ key: string; order: "asc" | "desc" } | null>(null);
|
||||||
|
// 抽屉内独立时间筛选(不依赖顶部)
|
||||||
|
const [opDateKey, setOpDateKey] = useState<DateRangeKey>("today");
|
||||||
|
const [opCustomRange, setOpCustomRange] = useState<[Dayjs, Dayjs] | null>(null);
|
||||||
// 操作明细下钻
|
// 操作明细下钻
|
||||||
const [opDetailOpen, setOpDetailOpen] = useState(false);
|
const [opDetailOpen, setOpDetailOpen] = useState(false);
|
||||||
const [opDetailList, setOpDetailList] = useState<OperationDetail[]>([]);
|
const [opDetailList, setOpDetailList] = useState<OperationDetail[]>([]);
|
||||||
@ -337,22 +340,28 @@ export default function AdminDashboard() {
|
|||||||
.finally(() => setRejectedLoading(false));
|
.finally(() => setRejectedLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 人员操作统计 — 跟随顶部时间筛选(打开抽屉时加载)
|
// 人员操作统计 — 用抽屉内独立时间筛选
|
||||||
const loadUserOperations = useCallback(() => {
|
const loadUserOperations = useCallback(() => {
|
||||||
setOpLoading(true);
|
setOpLoading(true);
|
||||||
const { since, until } = rangeToParams(dateKey, customRange);
|
const { since, until } = rangeToParams(opDateKey, opCustomRange);
|
||||||
fetchUserOperations(since, until)
|
fetchUserOperations(since, until)
|
||||||
.then(setOpStats)
|
.then(setOpStats)
|
||||||
.catch(() => setOpStats([]))
|
.catch(() => setOpStats([]))
|
||||||
.finally(() => setOpLoading(false));
|
.finally(() => setOpLoading(false));
|
||||||
}, [dateKey, customRange]);
|
}, [opDateKey, opCustomRange]);
|
||||||
|
|
||||||
|
// 抽屉打开或内部时间变化时加载
|
||||||
|
useEffect(() => {
|
||||||
|
if (opsDrawerOpen) loadUserOperations();
|
||||||
|
}, [opsDrawerOpen, loadUserOperations]);
|
||||||
|
|
||||||
const openOpsDrawer = () => {
|
const openOpsDrawer = () => {
|
||||||
setOpsDrawerOpen(true);
|
setOpsDrawerOpen(true);
|
||||||
|
setOpDateKey(dateKey); // 默认同步顶部当前时间
|
||||||
|
setOpCustomRange(customRange);
|
||||||
setOpFilterUsers(new Set());
|
setOpFilterUsers(new Set());
|
||||||
setOpFilterTouched(false);
|
setOpFilterTouched(false);
|
||||||
setOpSort(null);
|
setOpSort(null);
|
||||||
loadUserOperations();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 首次加载完成后默认全选(显示全部);用户操作过筛选后不再自动覆盖
|
// 首次加载完成后默认全选(显示全部);用户操作过筛选后不再自动覆盖
|
||||||
@ -390,12 +399,12 @@ export default function AdminDashboard() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 点击数字下钻查看明细
|
// 点击数字下钻查看明细(跟随抽屉内时间)
|
||||||
const openOpDetail = (user: UserOperation, actionType: string, label: string) => {
|
const openOpDetail = (user: UserOperation, actionType: string, label: string) => {
|
||||||
setOpDetailOpen(true);
|
setOpDetailOpen(true);
|
||||||
setOpDetailTitle(`${user.user_name} · ${label}`);
|
setOpDetailTitle(`${user.user_name} · ${label}`);
|
||||||
setOpDetailLoading(true);
|
setOpDetailLoading(true);
|
||||||
const { since, until } = rangeToParams(dateKey, customRange);
|
const { since, until } = rangeToParams(opDateKey, opCustomRange);
|
||||||
fetchOperationDetail(user.user_id, actionType, since, until)
|
fetchOperationDetail(user.user_id, actionType, since, until)
|
||||||
.then(setOpDetailList)
|
.then(setOpDetailList)
|
||||||
.catch(() => setOpDetailList([]))
|
.catch(() => setOpDetailList([]))
|
||||||
@ -740,6 +749,31 @@ export default function AdminDashboard() {
|
|||||||
size="large"
|
size="large"
|
||||||
styles={{ body: { padding: 16, background: "#f8fafc" } }}
|
styles={{ body: { padding: 16, background: "#f8fafc" } }}
|
||||||
>
|
>
|
||||||
|
{/* 抽屉内时间筛选 */}
|
||||||
|
<div className="mb-4 flex flex-wrap items-center gap-2">
|
||||||
|
<Radio.Group
|
||||||
|
value={opDateKey}
|
||||||
|
onChange={e => { setOpDateKey(e.target.value); setOpCustomRange(null); }}
|
||||||
|
size="small"
|
||||||
|
optionType="button"
|
||||||
|
buttonStyle="solid"
|
||||||
|
>
|
||||||
|
<Radio.Button value="today">今天</Radio.Button>
|
||||||
|
<Radio.Button value="7d">近7天</Radio.Button>
|
||||||
|
<Radio.Button value="30d">近30天</Radio.Button>
|
||||||
|
<Radio.Button value="custom">自定义</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
{opDateKey === "custom" && (
|
||||||
|
<RangePicker
|
||||||
|
size="small"
|
||||||
|
value={opCustomRange as any}
|
||||||
|
onChange={dates => setOpCustomRange(dates as [Dayjs, Dayjs] | null)}
|
||||||
|
style={{ width: 240 }}
|
||||||
|
placeholder={["开始", "结束"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 人员选择 — 人名宫格多选 */}
|
{/* 人员选择 — 人名宫格多选 */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
|||||||
Reference in New Issue
Block a user