diff --git a/frontend/src/pages/admin/AdminProductsPage.tsx b/frontend/src/pages/admin/AdminProductsPage.tsx index 9ffef73..de77229 100644 --- a/frontend/src/pages/admin/AdminProductsPage.tsx +++ b/frontend/src/pages/admin/AdminProductsPage.tsx @@ -1,6 +1,5 @@ -import { useEffect, useState, useRef, useCallback } from "react"; -import { Printer, RefreshCw, Loader2, QrCode, Plus, Settings, X, Loader } from "lucide-react"; -import { useVirtualizer } from "@tanstack/react-virtual"; +import { useEffect, useState } from "react"; +import { Printer, RefreshCw, Loader2, QrCode, Plus, Settings, X, Package, Hash, Tag, MapPin, Clock } from "lucide-react"; import api from "../../services/api"; import type { ProductResponse } from "../../types/admin"; import CreateProductDialog from "./CreateProductDialog"; @@ -10,6 +9,7 @@ import { type LabelPreviewRequest, } from "../../services/printApi"; import { useToast } from "../../components/ui/Toast"; +import { getStatusConfig } from "../../constants/task"; const QR_BASE = "/api/v1/products/qrcode"; @@ -27,35 +27,6 @@ export default function AdminProductsPage() { const [printCopies, setPrintCopies] = useState(1); const [printing, setPrinting] = useState(false); - // 🚀 虚拟滚动:响应式列数 + 行虚拟化 - const scrollRef = useRef(null); - const [columns, setColumns] = useState(5); - - useEffect(() => { - const el = scrollRef.current; - if (!el) return; - const calc = () => { - const w = el.clientWidth; - if (w >= 1280) setColumns(6); - else if (w >= 1024) setColumns(5); - else if (w >= 768) setColumns(4); - else if (w >= 640) setColumns(3); - else setColumns(2); - }; - calc(); - const ro = new ResizeObserver(calc); - ro.observe(el); - return () => ro.disconnect(); - }, [products.length > 0]); - - const rowCount = Math.ceil(products.length / columns); - const rowVirtualizer = useVirtualizer({ - count: rowCount, - getScrollElement: () => scrollRef.current, - estimateSize: () => 320, - overscan: 2, - }); - async function loadProducts() { setLoading(true); setError(null); @@ -118,35 +89,6 @@ export default function AdminProductsPage() { } } - // ---- 打印二维码(兼容旧功能) ---- - - function handlePrintQr(serialNumber: string) { - const qrUrl = `${QR_BASE}/${serialNumber}`; - const w = window.open("", "_blank", "width=400,height=500"); - if (!w) return; - w.document.write(` - - - 打印标签 - ${serialNumber} - - - QR-${serialNumber} -

${serialNumber}

-

扫描二维码查询生产进度

- - - - `); - w.document.close(); - } - return (
{/* 标题栏 */} @@ -196,7 +138,7 @@ export default function AdminProductsPage() {
)} - {/* 产品列表 */} + {/* 空状态 */} {!loading && products.length === 0 && !error && (
@@ -205,75 +147,71 @@ export default function AdminProductsPage() {
)} + {/* 🚀 响应式网格 — 稳定不拉伸 */} {!loading && products.length > 0 && ( -
-
- {rowVirtualizer.getVirtualItems().map((virtualRow) => { - const start = virtualRow.index * columns; - const rowProducts = products.slice(start, start + columns); - return ( -
- {/* 动态 inline grid 列数(Tailwind 不支持动态 columns) */} -
- {rowProducts.map((p) => ( -
- {/* 二维码 */} - {`QR-${p.serial_number}`} - - {/* 产品身份证 */} -

- {p.serial_number} -

-

- {p.order_no ?? "—"} -

- - {/* 打印按钮 */} - -
- ))} -
+
+ {products.map((p) => { + const statusCfg = getStatusConfig(p.status); + return ( +
+ {/* 顶部:二维码 + 产品ID */} +
+ {`QR-${p.serial_number}`} +

+ {p.serial_number} +

- ); - })} -
+ + {/* 主体:关键信息键值对 */} +
+ + + + + + {p.overall_status && ( +
+ 宏观状态 + + {p.overall_status} + +
+ )} +
+ + {/* 底部:打印按钮 */} +
+ +
+
+ ); + })}
)} @@ -283,9 +221,7 @@ export default function AdminProductsPage() { onCreated={loadProducts} /> - {/* ============================================================ */} {/* 打印预览弹窗 */} - {/* ============================================================ */} {printTarget && (
-

- 标签打印预览 -

+

标签打印预览

- {/* 预览图 */}
{printLoading || !previewUrl ? ( -
+
) : ( @@ -325,7 +258,6 @@ export default function AdminProductsPage() { {printTarget.serial_number}

- {/* 份数选择 */}
打印份数
@@ -335,9 +267,7 @@ export default function AdminProductsPage() { > − - - {printCopies} - + {printCopies}
- {/* 按钮 */}
); } + +/** 信息行:图标 + 标签 + 值 */ +function InfoRow({ + icon: Icon, + label, + value, +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + value: string; +}) { + return ( +
+ + {label} + {value} +
+ ); +}