From 24c124cd304017ae3c30ce4299e0224026cf89bf Mon Sep 17 00:00:00 2001 From: duxingchen Date: Fri, 28 Aug 2026 10:46:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E8=B7=B3=E8=BD=AC=20+=20=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=20+=20=E7=8A=B6=E6=80=81=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E5=A4=9A=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 点击产品卡片跳转 /admin/tasks?sn=身份证,与全局概览看板一致 - 卡片新增「当前人时间」(滞留时长) 与「生产总天数」(自创建) - 状态筛选 tabs 去掉「已入库」(仓库已显示为已完成),改为多选 - 卡片内编辑/删除/打印按钮 stopPropagation 避免误触跳转 - 清理未使用的 import --- .../src/pages/admin/AdminProductsPage.tsx | 71 +++++++++++++------ 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/frontend/src/pages/admin/AdminProductsPage.tsx b/frontend/src/pages/admin/AdminProductsPage.tsx index ee7705d..8f91b0b 100644 --- a/frontend/src/pages/admin/AdminProductsPage.tsx +++ b/frontend/src/pages/admin/AdminProductsPage.tsx @@ -1,32 +1,32 @@ import { useEffect, useState, useMemo } from "react"; +import { useNavigate } from "react-router-dom"; import { Printer, RefreshCw, Loader2, QrCode, Plus, Settings, X, - Package, Hash, Tag, MapPin, Clock, Pencil, Trash2, Save, AlertTriangle, + Package, Hash, Tag, MapPin, Clock, Timer, CalendarDays, + Pencil, Trash2, Save, AlertTriangle, Search, ChevronDown, ChevronRight, Warehouse, Barcode, } from "lucide-react"; import api from "../../services/api"; import type { ProductResponse } from "../../types/admin"; import CreateProductDialog from "./CreateProductDialog"; import { - getLabelPreview, executePrint, type LabelPreviewRequest, + getLabelPreview, executePrint, } from "../../services/printApi"; import { useToast } from "../../components/ui/Toast"; -import { getStatusConfig } from "../../constants/task"; const QR_BASE = "/api/v1/products/qrcode"; const STATUS_TABS = [ - { key: "", label: "全部" }, { key: "PENDING", label: "待接收" }, { key: "WIP", label: "进行中" }, { key: "COMPLETED", label: "已完成" }, - { key: "ARCHIVED", label: "已入库" }, ]; interface ProductGroup { groupKey: string; products: ProductResponse[]; allInWarehouse: boolean; } export default function AdminProductsPage() { const { toast } = useToast(); + const navigate = useNavigate(); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -34,7 +34,7 @@ export default function AdminProductsPage() { // 搜索 & 筛选 & 分组 const [keyword, setKeyword] = useState(""); - const [statusFilter, setStatusFilter] = useState(""); + const [statusFilters, setStatusFilters] = useState>(new Set()); const [groupBy, setGroupBy] = useState<"order" | "device">("device"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); @@ -73,13 +73,13 @@ export default function AdminProductsPage() { function handleSearch(e?: React.FormEvent) { e?.preventDefault(); loadProducts(); } - // ---- 分组 + 状态过滤 ---- + // ---- 分组 + 状态过滤(多选)---- const productGroups = useMemo(() => { const map = new Map(); for (const p of products) { - if (statusFilter) { + if (statusFilters.size > 0) { const s = (p.macro_status || p.status).toUpperCase(); - if (s !== statusFilter) continue; + if (!statusFilters.has(s)) continue; } const key = groupBy === "device" ? (p.material_name || p.material_id || "未命名设备") @@ -91,7 +91,15 @@ export default function AdminProductsPage() { groupKey, products: prods, allInWarehouse: prods.every(p => p.current_location_id === "virtual_warehouse"), })); - }, [products, statusFilter, groupBy]); + }, [products, statusFilters, groupBy]); + + function toggleStatusFilter(key: string) { + setStatusFilters(prev => { + const next = new Set(prev); + if (next.has(key)) next.delete(key); else next.add(key); + return next; + }); + } // 🔧 默认全部展开:productGroups 变化时自动展开所有面板 useEffect(() => { @@ -161,12 +169,19 @@ export default function AdminProductsPage() {
- {STATUS_TABS.map(tab => ( - - ))} + + {STATUS_TABS.map(tab => { + const active = statusFilters.has(tab.key); + return ( + + ); + })}
@@ -210,10 +225,10 @@ export default function AdminProductsPage() {
{group.products.map(p => ( -
+
navigate(`/admin/tasks?sn=${p.serial_number}`)} className="group relative flex cursor-pointer flex-col rounded-xl bg-white shadow-sm ring-1 ring-gray-100 transition-shadow hover:shadow-md">
- - + +
{`QR-${p.serial_number}`} @@ -225,10 +240,12 @@ export default function AdminProductsPage() { + +
-
@@ -294,3 +311,17 @@ export default function AdminProductsPage() { function InfoRow({ icon: Icon, label, value }: { icon: React.ComponentType<{ className?: string }>; label: string; value: string }) { return
{label}{value}
; } + +/** 滞留时长格式化:小时 → 天/小时/分钟 */ +function formatDuration(hours: number | null): string { + if (hours == null) return "—"; + if (hours >= 24) return `${Math.round(hours / 24)}天`; + if (hours >= 1) return `${Math.round(hours)}小时`; + return `${Math.max(1, Math.round(hours * 60))}分钟`; +} + +/** 生产总天数:从产品创建(created_at)到现在,向上取整,最少 1 天 */ +function formatProductionDays(createdAt: string): string { + const days = Math.max(1, Math.ceil((Date.now() - new Date(createdAt).getTime()) / 86400000)); + return `${days} 天`; +}