feat(admin-portal): 完整实现 admin-portal 管理端微前端
包含 src 全部实现、Dockerfile、配置文件等
This commit is contained in:
89
apps/admin-portal/src/hooks/use-audit-logs.ts
Normal file
89
apps/admin-portal/src/hooks/use-audit-logs.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 审计日志 Hooks
|
||||
*
|
||||
* contract §2.4:auditLogs
|
||||
* 仲裁 ARB-005:经 teacher-bff GraphQL 消费(不直连 Kafka)
|
||||
*/
|
||||
import { useCallback, useState } from "react";
|
||||
import { useGraphQuery } from "./use-graphql";
|
||||
import { AUDIT_LOGS_QUERY } from "@/lib/graphql-client";
|
||||
import type {
|
||||
AuditLogViewModel,
|
||||
PaginatedResult,
|
||||
ListFilter,
|
||||
} from "@/types/view-models";
|
||||
|
||||
interface AuditLogsResponse {
|
||||
auditLogs: PaginatedResult<AuditLogViewModel>;
|
||||
}
|
||||
|
||||
interface AuditLogFilter extends Partial<ListFilter> {
|
||||
action?: string;
|
||||
actorUserId?: string;
|
||||
startDate?: number;
|
||||
endDate?: number;
|
||||
}
|
||||
|
||||
export function useAuditLogs(filter: AuditLogFilter) {
|
||||
const result = useGraphQuery<AuditLogsResponse>(AUDIT_LOGS_QUERY, { filter });
|
||||
return {
|
||||
...result,
|
||||
data: result.data?.auditLogs ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAuditLogFilter(initial?: Partial<AuditLogFilter>) {
|
||||
const [filter, setFilter] = useState<AuditLogFilter>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
...initial,
|
||||
});
|
||||
const setPage = useCallback(
|
||||
(page: number) => setFilter((f) => ({ ...f, page })),
|
||||
[],
|
||||
);
|
||||
const setSearch = useCallback(
|
||||
(search: string) => setFilter((f) => ({ ...f, search, page: 1 })),
|
||||
[],
|
||||
);
|
||||
const setAction = useCallback(
|
||||
(action: string) =>
|
||||
setFilter((f) => ({ ...f, action: action || undefined, page: 1 })),
|
||||
[],
|
||||
);
|
||||
return { filter, setFilter, setPage, setSearch, setAction };
|
||||
}
|
||||
|
||||
/** 审计日志导出 CSV */
|
||||
export function exportAuditLogsCsv(logs: AuditLogViewModel[]): void {
|
||||
const headers = [
|
||||
"时间",
|
||||
"操作人",
|
||||
"操作",
|
||||
"资源类型",
|
||||
"资源ID",
|
||||
"IP",
|
||||
"追踪ID",
|
||||
];
|
||||
const rows = logs.map((l) => [
|
||||
new Date(l.occurredAt).toLocaleString("zh-CN"),
|
||||
l.actorName,
|
||||
l.action,
|
||||
l.resourceType,
|
||||
l.resourceId,
|
||||
l.ip,
|
||||
l.traceId ?? "",
|
||||
]);
|
||||
const csv = [headers, ...rows]
|
||||
.map((row) =>
|
||||
row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(","),
|
||||
)
|
||||
.join("\n");
|
||||
const blob = new Blob([`\uFEFF${csv}`], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `audit-logs-${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
Reference in New Issue
Block a user