diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 16d89a3..7076c49 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -24,6 +24,7 @@ const AdminTasksPage = lazy(() => import("./pages/admin/AdminTasksPage")); const AdminPeoplePage = lazy(() => import("./pages/admin/AdminPeoplePage")); const AdminPrintConfigPage = lazy(() => import("./pages/admin/AdminPrintConfigPage")); const AnalyticsDashboard = lazy(() => import("./pages/admin/AnalyticsDashboard")); +const MatrixBoard = lazy(() => import("./pages/MatrixBoard")); export default function App() { return ( @@ -55,6 +56,7 @@ export default function App() { } /> } /> } /> + } /> diff --git a/frontend/src/components/layout/AdminLayout.tsx b/frontend/src/components/layout/AdminLayout.tsx index fb3ff8d..00416dd 100644 --- a/frontend/src/components/layout/AdminLayout.tsx +++ b/frontend/src/components/layout/AdminLayout.tsx @@ -1,5 +1,5 @@ import { NavLink, Outlet, useLocation, useNavigate, Navigate } from "react-router-dom"; -import { QrCode, Package, ArrowLeft, LayoutDashboard, Smartphone, GitBranch, LogOut, User, Users, BarChart3 } from "lucide-react"; +import { QrCode, Package, ArrowLeft, LayoutDashboard, Smartphone, GitBranch, LogOut, User, Users, BarChart3, Table2 } from "lucide-react"; import { useAuth } from "../../contexts/AuthContext"; const MENU = [ @@ -33,6 +33,12 @@ const MENU = [ icon: BarChart3, description: "人员效能 / 设备流转 ECharts 可视化", }, + { + title: "WIP 分布矩阵", + path: "/admin/matrix", + icon: Table2, + description: "规格型号 × 人员/工序 在制品透视表", + }, ]; export default function AdminLayout() { diff --git a/frontend/src/pages/MatrixBoard.tsx b/frontend/src/pages/MatrixBoard.tsx new file mode 100644 index 0000000..b47d8a3 --- /dev/null +++ b/frontend/src/pages/MatrixBoard.tsx @@ -0,0 +1,92 @@ +/** + * WIP 分布矩阵 — 在制品透视表(规格型号 × 人员/工序,单元格=设备数量) + * Y 轴 = 规格型号,X 轴 = 人员 或 工序,单元格 = 该交叉点的在制品设备数 + */ +import { useEffect, useMemo, useState } from "react"; +import { Radio, Table } from "antd"; +import { fetchWipMatrix, type WipMatrixRow } from "../services/dashboardApi"; + +export default function MatrixBoard() { + const [dimension, setDimension] = useState<"assignee" | "task_name">("assignee"); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setLoading(true); + fetchWipMatrix(dimension) + .then(setData) + .catch(() => setData([])) + .finally(() => setLoading(false)); + }, [dimension]); + + // 🔧 核心:扁平数组 → 动态交叉表(规格型号为行、dimension_key 为列) + const { columns, dataSource } = useMemo(() => { + const keys = Array.from(new Set(data.map((d) => d.dimension_key))); + const rowsMap = new Map(); + for (const item of data) { + if (!rowsMap.has(item.spec_model)) { + rowsMap.set(item.spec_model, { spec_model: item.spec_model, _assignees: {}, row_total: 0 }); + } + const row = rowsMap.get(item.spec_model); + row[item.dimension_key] = (row[item.dimension_key] || 0) + item.count; + row.row_total += item.count; + // 记录每个交叉点的主负责人(中文名,去重) + row._assignees[item.dimension_key] = item.assignees || []; + } + const ds = Array.from(rowsMap.values()); + + const cols: any[] = [ + { title: "规格型号", dataIndex: "spec_model", fixed: "left", width: 190, render: (v: string) => {v} }, + ...keys.map((key) => ({ + title: key, + dataIndex: key, + align: "center" as const, + width: 120, + render: (val: number, record: any) => { + const assignees = record._assignees?.[key] || []; + return ( + + {val || 0} + {assignees.length > 0 && ( + + {assignees.join("、")} + + )} + + ); + }, + })), + { title: "合计", dataIndex: "row_total", fixed: "right" as const, align: "center" as const, width: 90, render: (v: number) => {v} }, + ]; + + return { columns: cols, dataSource: ds }; + }, [data]); + + return ( + + + + 📊 WIP 分布矩阵 + 在制品分布透视表:规格型号 × {dimension === "assignee" ? "人员" : "工序"} · 单元格为设备数量 + + setDimension(e.target.value)} optionType="button" buttonStyle="solid"> + 按人员分布 + 按工序分布 + + + + + + + + ); +} diff --git a/frontend/src/services/dashboardApi.ts b/frontend/src/services/dashboardApi.ts index dc81be3..0a58e70 100644 --- a/frontend/src/services/dashboardApi.ts +++ b/frontend/src/services/dashboardApi.ts @@ -71,6 +71,13 @@ export interface OperationDetail { time: string; } +export interface WipMatrixRow { + spec_model: string; + dimension_key: string; + count: number; + assignees: string[]; +} + export interface PersonDevice { product_id: string; serial_number: string; @@ -177,6 +184,11 @@ export async function fetchOperationDetail( return data; } +export async function fetchWipMatrix(dimension: "assignee" | "task_name"): Promise { + const { data } = await api.get("/dashboard/wip-matrix", { params: { dimension } }); + return data; +} + export async function fetchPeopleWorkload(): Promise { const { data } = await api.get("/dashboard/people-workload"); return data;
在制品分布透视表:规格型号 × {dimension === "assignee" ? "人员" : "工序"} · 单元格为设备数量