From d92fcc4d4a225c51ee3aaa8d49dbed016aa3960a Mon Sep 17 00:00:00 2001 From: duxingchen Date: Fri, 28 Aug 2026 11:19:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=85=A8=E5=B1=80=E6=A6=82=E8=A7=88):=20?= =?UTF-8?q?=E9=A9=B3=E5=9B=9E/=E8=BF=94=E5=B7=A5=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E5=8F=AF=E7=82=B9=E5=87=BB=E4=B8=8B=E9=92=BB=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E6=8A=BD=E5=B1=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 驳回/返工数字改为可点击按钮,打开明细抽屉(仿完成明细) - RejectedRow 展示设备/被驳回工序/驳回人/返工给谁/原因/时间,行点击跳转任务全景 - 跟随顶部时间筛选(since/until) --- frontend/src/pages/admin/AdminDashboard.tsx | 91 +++++++++++++++++++-- frontend/src/services/dashboardApi.ts | 21 +++++ 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/admin/AdminDashboard.tsx b/frontend/src/pages/admin/AdminDashboard.tsx index c7901ee..502a2b4 100644 --- a/frontend/src/pages/admin/AdminDashboard.tsx +++ b/frontend/src/pages/admin/AdminDashboard.tsx @@ -7,8 +7,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, - type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, + fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, fetchRejectedTasks, + type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, type RejectedTask, } from "../../services/dashboardApi"; const { RangePicker } = DatePicker; @@ -188,6 +188,43 @@ function CompletedRow({ t }: { t: CompletedTask }) { ); } +// ─── 被驳回明细项(卡片式) ────────────────────────────── +function RejectedRow({ t }: { t: RejectedTask }) { + const nav = useNavigate(); + const rejectedTime = t.rejected_at ? dayjs(t.rejected_at).format("YYYY-MM-DD HH:mm") : ""; + return ( +
t.product_sn && nav(`/admin/tasks?sn=${t.product_sn}`)} + className="cursor-pointer rounded-lg border border-gray-100 bg-white px-4 py-3 transition-shadow hover:border-red-200 hover:shadow-md" + > +
+ + {t.task_name} + | + 已驳回 + 驳回人: {t.rejected_by} + {rejectedTime} +
+
+ + 🔁 返工给: {t.rework_assignee} + + | + 原因: {t.reject_reason || "—"} +
+
+ {t.material_name || "未知设备"} + | + {t.spec_model || "无规格"} + | + 序列号: {t.external_serial || "未录入"} + | + 身份证: {t.product_sn} +
+
+ ); +} + // ─── 主组件 ─────────────────────────────────────────────── export default function AdminDashboard() { const [stats, setStats] = useState(null); @@ -211,6 +248,11 @@ export default function AdminDashboard() { const [completedTasks, setCompletedTasks] = useState([]); const [completedLoading, setCompletedLoading] = useState(false); + // 驳回/返工明细抽屉 + const [rejectedDrawerOpen, setRejectedDrawerOpen] = useState(false); + const [rejectedTasks, setRejectedTasks] = useState([]); + const [rejectedLoading, setRejectedLoading] = useState(false); + const navigate = useNavigate(); // ── 加载主数据 ── @@ -255,6 +297,16 @@ export default function AdminDashboard() { .finally(() => setCompletedLoading(false)); }; + const openRejectedDrawer = () => { + setRejectedDrawerOpen(true); + setRejectedLoading(true); + const { since, until } = rangeToParams(dateKey, customRange); + fetchRejectedTasks(since, until) + .then(setRejectedTasks) + .catch(() => setRejectedTasks([])) + .finally(() => setRejectedLoading(false)); + }; + const onMsgSearch = (value: string) => { setMsgKeyword(value); loadMessages(value); @@ -358,10 +410,16 @@ export default function AdminDashboard() {

⚠️ 品质与协同

-
+ {/* 驳回/返工 — 可点击打开明细抽屉 */} +
+

+ 驳回/返工 ↗ +

+ {/* 留言 — 可点击打开抽屉 */}
); } diff --git a/frontend/src/services/dashboardApi.ts b/frontend/src/services/dashboardApi.ts index ae197a0..e56934f 100644 --- a/frontend/src/services/dashboardApi.ts +++ b/frontend/src/services/dashboardApi.ts @@ -39,6 +39,19 @@ export interface CompletedTask { completed_at: string; } +export interface RejectedTask { + task_id: string; + task_name: string; + product_sn: string; + external_serial: string | null; + material_name: string; + spec_model: string; + rejected_by: string; + rework_assignee: string; + reject_reason: string | null; + rejected_at: string; +} + export interface PersonDevice { product_id: string; serial_number: string; @@ -119,6 +132,14 @@ export async function fetchCompletedTasks(since?: string, until?: string): Promi return data; } +export async function fetchRejectedTasks(since?: string, until?: string): Promise { + const params: Record = {}; + if (since) params.since = since; + if (until) params.until = until; + const { data } = await api.get("/dashboard/rejected-tasks", { params }); + return data; +} + export async function fetchPeopleWorkload(): Promise { const { data } = await api.get("/dashboard/people-workload"); return data;