From d0fc8cdecf10926342c3218d8ae88ffd43a63d26 Mon Sep 17 00:00:00 2001 From: duxingchen Date: Fri, 28 Aug 2026 13:36:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=93=8D=E4=BD=9C=E7=BB=9F=E8=AE=A1):=20?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E6=8E=A5=E6=94=B6/=E8=BD=AC=E4=BA=A4/?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=A4=87=E6=B3=A8=E6=95=B0=E5=AD=97=E4=B8=8B?= =?UTF-8?q?=E9=92=BB=E6=9F=A5=E7=9C=8B=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 表格中接收/转交/上传备注数字改为可点击(蓝色下划线),点击打开明细抽屉 - 明细展示:任务名/备注/设备/身份证/时间,跟随顶部时间筛选 --- frontend/src/pages/admin/AdminDashboard.tsx | 66 +++++++++++++++++++-- frontend/src/services/dashboardApi.ts | 18 ++++++ 2 files changed, 79 insertions(+), 5 deletions(-) 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;