diff --git a/frontend/src/pages/admin/AdminDashboard.tsx b/frontend/src/pages/admin/AdminDashboard.tsx index 953e884..95ddee2 100644 --- a/frontend/src/pages/admin/AdminDashboard.tsx +++ b/frontend/src/pages/admin/AdminDashboard.tsx @@ -8,8 +8,8 @@ import { useNavigate } from "react-router-dom"; import { Radio, DatePicker, Drawer, Input } from "antd"; import dayjs, { type Dayjs } from "dayjs"; import { - fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, fetchRejectedTasks, fetchUserOperations, - type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, type RejectedTask, type UserOperation, + fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, fetchRejectedTasks, fetchUserOperations, fetchOperationDetail, + type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, type RejectedTask, type UserOperation, type OperationDetail, } from "../../services/dashboardApi"; const { RangePicker } = DatePicker; @@ -276,6 +276,11 @@ export default function AdminDashboard() { const [opLoading, setOpLoading] = useState(false); const [opFilterUsers, setOpFilterUsers] = useState>(new Set()); // 多选人员,空 = 全部 const [opSort, setOpSort] = useState<{ key: string; order: "asc" | "desc" } | null>(null); + // 操作明细下钻 + const [opDetailOpen, setOpDetailOpen] = useState(false); + const [opDetailList, setOpDetailList] = useState([]); + const [opDetailLoading, setOpDetailLoading] = useState(false); + const [opDetailTitle, setOpDetailTitle] = useState(""); const navigate = useNavigate(); @@ -369,6 +374,18 @@ export default function AdminDashboard() { }); }; + // 点击数字下钻查看明细 + const openOpDetail = (user: UserOperation, actionType: string, label: string) => { + setOpDetailOpen(true); + setOpDetailTitle(`${user.user_name} · ${label}`); + setOpDetailLoading(true); + const { since, until } = rangeToParams(dateKey, customRange); + fetchOperationDetail(user.user_id, actionType, since, until) + .then(setOpDetailList) + .catch(() => setOpDetailList([])) + .finally(() => setOpDetailLoading(false)); + }; + const onMsgSearch = (value: string) => { setMsgKeyword(value); loadMessages(value); @@ -776,9 +793,15 @@ export default function AdminDashboard() { {idx + 1} {u.user_name} - {u.receive_count} - {u.transfer_count} - {u.record_count} + + + + + + + + + {u.total} ))} @@ -789,6 +812,39 @@ export default function AdminDashboard() { })() )} + + {/* ═══ 操作明细抽屉 ═══ */} + 📋 {opDetailTitle} {opDetailList.length} 条} + open={opDetailOpen} + onClose={() => setOpDetailOpen(false)} + size="large" + styles={{ body: { padding: 16, background: "#f8fafc" } }} + > + {opDetailLoading ? ( +
+ +
+ ) : opDetailList.length === 0 ? ( +
该时段暂无相关记录
+ ) : ( +
+ {opDetailList.map((d, i) => ( +
+
+ {d.task_name} + {d.time ? dayjs(d.time).format("YYYY-MM-DD HH:mm") : ""} +
+ {d.remark &&

{d.remark}

} +
+ {d.material_name || "未知设备"} + {d.product_sn && 身份证: {d.product_sn}} +
+
+ ))} +
+ )} +
); } diff --git a/frontend/src/services/dashboardApi.ts b/frontend/src/services/dashboardApi.ts index 5ae7154..dc81be3 100644 --- a/frontend/src/services/dashboardApi.ts +++ b/frontend/src/services/dashboardApi.ts @@ -63,6 +63,14 @@ export interface UserOperation { total: number; } +export interface OperationDetail { + task_name: string; + product_sn: string; + material_name: string; + remark: string | null; + time: string; +} + export interface PersonDevice { product_id: string; serial_number: string; @@ -159,6 +167,16 @@ export async function fetchUserOperations(since?: string, until?: string): Promi return data; } +export async function fetchOperationDetail( + userId: string, actionType: string, since?: string, until?: string, +): Promise { + const params: Record = { user_id: userId, action_type: actionType }; + if (since) params.since = since; + if (until) params.until = until; + const { data } = await api.get("/dashboard/user-operations/detail", { params }); + return data; +} + export async function fetchPeopleWorkload(): Promise { const { data } = await api.get("/dashboard/people-workload"); return data;