feat: Track 网页端已完成/已入库/已出库状态展示与筛选
- ProductCard/ScanPage 徽标改用整体状态,补全宏观状态中文映射 - 产品管理/任务全景/我的任务/看板/WIP分析 状态筛选与徽标区分 - 转入在库显示在库实际操作人;新增已出库/OUTBOUND 状态色
This commit is contained in:
@ -115,7 +115,10 @@ const SlimCard = memo(function SlimCard({
|
||||
<p className={`${sz.title} font-bold text-gray-800 truncate`}>{task.task_name}</p>
|
||||
<div className={`${sz.metaRow} flex items-center gap-1`}>
|
||||
<span className={`rounded-full ${sz.status} font-medium ${cfg.bg} ${cfg.text}`}>{cfg.label}</span>
|
||||
{/* 在库任务不单独显示负责人,由下方"转入在库"统一展示实际操作人 */}
|
||||
{!isWarehouse && (
|
||||
<span className={`${sz.assignee} text-gray-400 truncate`}>{assigneeName || task.assignee_id || "—"}</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 单行时间 */}
|
||||
<p className={`${sz.time} text-gray-300`}>
|
||||
@ -124,9 +127,9 @@ const SlimCard = memo(function SlimCard({
|
||||
</p>
|
||||
{legacy && <p className={`${sz.sub} text-orange-500`}>源自: {assigneeName || (findParent(task)?.assignee_id) || "历史任务"}</p>}
|
||||
{isNestedSpawn && <p className={`${sz.sub} text-purple-500`}>协助: {findParent(task)?.assignee_id || "—"}</p>}
|
||||
{/* 📥 在库任务:显示谁转入在库 */}
|
||||
{isWarehouse && task.created_by && (
|
||||
<p className={`${sz.sub} text-emerald-600`}>📥 转入在库: {assigneeNames?.[task.created_by] || task.created_by}</p>
|
||||
{/* 📥 在库任务:显示实际执行"转入在库"的操作人(任务负责人) */}
|
||||
{isWarehouse && task.assignee_id && (
|
||||
<p className={`${sz.sub} text-emerald-600`}>📥 转入在库: {assigneeName || task.assignee_id}</p>
|
||||
)}
|
||||
{/* 操作按钮 */}
|
||||
{active && isOwner && (
|
||||
|
||||
@ -8,13 +8,15 @@ interface ProductCardProps {
|
||||
}
|
||||
|
||||
export default function ProductCard({ product }: ProductCardProps) {
|
||||
// 优先显示宏观状态(整体流转),回退到产品状态字段
|
||||
const displayStatus = product.overall_status || product.status;
|
||||
return (
|
||||
<div className="rounded-xl bg-white p-4 shadow-sm">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<Package className="h-5 w-5 text-blue-600" />
|
||||
<h3 className="font-semibold text-gray-800">产品信息</h3>
|
||||
<span className={`ml-auto rounded-full px-2.5 py-0.5 text-xs font-medium ${statusColor(product.status)}`}>
|
||||
{statusLabel(product.status)}
|
||||
<span className={`ml-auto rounded-full px-2.5 py-0.5 text-xs font-medium ${statusColor(displayStatus)}`}>
|
||||
{statusLabel(displayStatus)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
|
||||
@ -42,7 +42,75 @@ export const STATUS_CONFIG: Record<string, StatusStyle> = {
|
||||
bg: "bg-gray-50",
|
||||
text: "text-gray-600",
|
||||
ring: "ring-gray-300",
|
||||
label: "已完成",
|
||||
label: "已入库",
|
||||
},
|
||||
// 产品已发货出库(MOM 出库联动)
|
||||
OUTBOUND: {
|
||||
bg: "bg-indigo-50",
|
||||
text: "text-indigo-600",
|
||||
ring: "ring-indigo-300",
|
||||
label: "已出库",
|
||||
},
|
||||
// 🔧 "待收货"虚拟节点(MOM 尚未扫码实收)— 警告级橙色,区别于绿色的"已完成"
|
||||
IN_PROGRESS: {
|
||||
bg: "bg-orange-50",
|
||||
text: "text-orange-700",
|
||||
ring: "ring-orange-400",
|
||||
label: "待收货",
|
||||
},
|
||||
// ---- 产品宏观状态(overall_status)中文映射 ----
|
||||
// 车间完工已转交仓库、待 MOM 实收
|
||||
待仓库收货: {
|
||||
bg: "bg-orange-50",
|
||||
text: "text-orange-700",
|
||||
ring: "ring-orange-400",
|
||||
label: "待仓库收货",
|
||||
},
|
||||
// MOM 已扫码实收,入库闭环
|
||||
已入库: {
|
||||
bg: "bg-gray-100",
|
||||
text: "text-gray-600",
|
||||
ring: "ring-gray-400",
|
||||
label: "已入库",
|
||||
},
|
||||
// 旧命名"在库"等价于"已入库"(后端 _resolve_macro_status 口径一致)
|
||||
在库: {
|
||||
bg: "bg-gray-100",
|
||||
text: "text-gray-600",
|
||||
ring: "ring-gray-400",
|
||||
label: "已入库",
|
||||
},
|
||||
// MOM 已发货出库
|
||||
已出库: {
|
||||
bg: "bg-indigo-50",
|
||||
text: "text-indigo-600",
|
||||
ring: "ring-indigo-300",
|
||||
label: "已出库",
|
||||
},
|
||||
// 其余流转中状态(扫码详情 ProductCard 徽标会用到)
|
||||
备货: {
|
||||
bg: "bg-blue-50",
|
||||
text: "text-blue-700",
|
||||
ring: "ring-blue-400",
|
||||
label: "备货",
|
||||
},
|
||||
生产: {
|
||||
bg: "bg-blue-50",
|
||||
text: "text-blue-700",
|
||||
ring: "ring-blue-400",
|
||||
label: "生产",
|
||||
},
|
||||
测试: {
|
||||
bg: "bg-blue-50",
|
||||
text: "text-blue-700",
|
||||
ring: "ring-blue-400",
|
||||
label: "测试",
|
||||
},
|
||||
维修: {
|
||||
bg: "bg-blue-50",
|
||||
text: "text-blue-700",
|
||||
ring: "ring-blue-400",
|
||||
label: "维修",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ const TABS = [
|
||||
{ key: "PENDING", label: "待接收" },
|
||||
{ key: "WIP", label: "进行中" },
|
||||
{ key: "COMPLETED", label: "已完成" },
|
||||
{ key: "ARCHIVED", label: "已入库" },
|
||||
] as const;
|
||||
|
||||
type TabKey = (typeof TABS)[number]["key"];
|
||||
|
||||
@ -71,7 +71,7 @@ export default function ScanPage() {
|
||||
<div className="mt-3 px-4">
|
||||
<div className="flex items-center gap-2 rounded-lg bg-white px-4 py-2.5 shadow-sm">
|
||||
<span className="text-xs text-gray-400">宏观状态</span>
|
||||
<span className={`flex-1 text-sm font-bold ${product.overall_status ? "text-blue-600" : "text-red-500"}`}>
|
||||
<span className={`flex-1 text-sm font-bold ${product.overall_status === "已入库" ? "text-gray-500" : product.overall_status === "待仓库收货" ? "text-orange-600" : product.overall_status ? "text-blue-600" : "text-red-500"}`}>
|
||||
{product.overall_status || "未设定"}
|
||||
</span>
|
||||
</div>
|
||||
@ -123,7 +123,7 @@ export default function ScanPage() {
|
||||
<div className="mt-3 px-4">
|
||||
<div className="flex items-center gap-2 rounded-lg bg-white px-4 py-2.5 shadow-sm">
|
||||
<span className="text-xs text-gray-400">宏观状态</span>
|
||||
<span className={`flex-1 text-sm font-bold ${product.overall_status ? "text-blue-600" : "text-red-500"}`}>
|
||||
<span className={`flex-1 text-sm font-bold ${product.overall_status === "已入库" ? "text-gray-500" : product.overall_status === "待仓库收货" ? "text-orange-600" : product.overall_status ? "text-blue-600" : "text-red-500"}`}>
|
||||
{product.overall_status || "未设定"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -492,7 +492,7 @@ export default function AdminDashboard() {
|
||||
<p className="text-3xl font-bold text-gray-800">{stats.products_total}<span className="text-sm font-normal text-gray-400"> 个</span></p>
|
||||
<p className="mb-3 text-[11px] text-gray-400">实时快照(不受时间筛选影响)</p>
|
||||
<ProgressBar a={stats.products_pending} b={stats.products_in_progress} c={stats.products_completed}
|
||||
total={stats.products_total} labels={["待流转", "流转中", "已完成"]} />
|
||||
total={stats.products_total} labels={["待流转", "流转中", "已完结"]} />
|
||||
</div>
|
||||
|
||||
{/* 任务状态 */}
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
getLabelPreview, executePrint,
|
||||
} from "../../services/printApi";
|
||||
import { useToast } from "../../components/ui/Toast";
|
||||
import { getStatusConfig } from "../../constants/task";
|
||||
|
||||
const QR_BASE = "/api/v1/products/qrcode";
|
||||
|
||||
@ -20,6 +21,8 @@ const STATUS_TABS = [
|
||||
{ key: "PENDING", label: "待接收" },
|
||||
{ key: "WIP", label: "进行中" },
|
||||
{ key: "COMPLETED", label: "已完成" },
|
||||
{ key: "ARCHIVED", label: "已入库" },
|
||||
{ key: "OUTBOUND", label: "已出库" },
|
||||
];
|
||||
|
||||
interface ProductGroup { groupKey: string; products: ProductResponse[]; allInWarehouse: boolean; }
|
||||
@ -89,7 +92,8 @@ export default function AdminProductsPage() {
|
||||
}
|
||||
return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([groupKey, prods]) => ({
|
||||
groupKey, products: prods,
|
||||
allInWarehouse: prods.every(p => p.current_location_id === "virtual_warehouse"),
|
||||
// 已入库 = MOM 已扫码实收 (macro_status==ARCHIVED),而非仅"在仓库位置"
|
||||
allInWarehouse: prods.every(p => (p.macro_status || p.status).toUpperCase() === "ARCHIVED"),
|
||||
}));
|
||||
}, [products, statusFilters, groupBy]);
|
||||
|
||||
@ -233,6 +237,12 @@ export default function AdminProductsPage() {
|
||||
<div className="flex flex-col items-center px-4 pt-5 pb-3">
|
||||
<img src={`${QR_BASE}/${p.serial_number}`} alt={`QR-${p.serial_number}`} className="h-32 w-32 rounded-lg border border-gray-100" loading="lazy" />
|
||||
<p className="mt-3 font-mono text-sm font-bold tracking-wider text-gray-800">{p.serial_number}</p>
|
||||
{(() => {
|
||||
const cfg = getStatusConfig(p.macro_status || p.status);
|
||||
return cfg.label ? (
|
||||
<span className={`mt-1 inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${cfg.bg} ${cfg.text}`}>{cfg.label}</span>
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex-1 space-y-2 border-t border-gray-50 px-4 py-3">
|
||||
<InfoRow icon={Package} label="物料名称" value={p.material_name || p.material_id || "—"} />
|
||||
|
||||
@ -24,6 +24,8 @@ const STATUS_TABS = [
|
||||
{ key: "PENDING", label: "待接收" },
|
||||
{ key: "WIP", label: "进行中" },
|
||||
{ key: "COMPLETED", label: "已完成" },
|
||||
{ key: "ARCHIVED", label: "已入库" },
|
||||
{ key: "OUTBOUND", label: "已出库" },
|
||||
];
|
||||
|
||||
type SortOrder = "asc" | "desc";
|
||||
@ -44,6 +46,7 @@ interface OrderGroup {
|
||||
orderNo: string;
|
||||
products: ProductResponse[];
|
||||
allInWarehouse: boolean;
|
||||
allCompleted: boolean;
|
||||
}
|
||||
|
||||
/** 滞留时长格式化:小时 → 天/小时/分钟 */
|
||||
@ -102,18 +105,20 @@ export default function AdminTasksPage() {
|
||||
render: (p) => <div className="text-xs text-gray-500 truncate">{p.spec_model || p.material_name || p.material_id || "—"}</div>,
|
||||
},
|
||||
{
|
||||
key: "overall_status", label: "宏观状态", colSpan: 1,
|
||||
key: "overall_status", label: "当前工序", colSpan: 1,
|
||||
filterType: "enum", getFilterValue: (p) => p.overall_status || "—",
|
||||
enumOptions: ["备货", "生产", "测试", "维修", "在库"].map((v) => ({ value: v, label: v })),
|
||||
enumOptions: ["备货", "生产", "测试", "维修", "在库", "待仓库收货", "已入库", "已出库"].map((v) => ({ value: v, label: v })),
|
||||
render: (p) => <span className="text-xs font-medium text-gray-700">{p.overall_status || "—"}</span>,
|
||||
},
|
||||
{
|
||||
key: "status", label: "任务状态", colSpan: 1,
|
||||
key: "status", label: "产品状态", colSpan: 1,
|
||||
filterType: "enum", getFilterValue: (p) => (p.macro_status || p.status).toUpperCase(),
|
||||
enumOptions: [
|
||||
{ value: "PENDING", label: "待接收" },
|
||||
{ value: "WIP", label: "进行中" },
|
||||
{ value: "COMPLETED", label: "已完成" },
|
||||
{ value: "ARCHIVED", label: "已入库" },
|
||||
{ value: "OUTBOUND", label: "已出库" },
|
||||
],
|
||||
render: (p) => {
|
||||
const statusCfg = getStatusConfig(p.macro_status || p.status);
|
||||
@ -292,8 +297,12 @@ export default function AdminTasksPage() {
|
||||
return {
|
||||
orderNo,
|
||||
products: sorted,
|
||||
// 已入库 = MOM 已扫码实收 (macro_status==ARCHIVED),而非仅"在仓库位置"
|
||||
allInWarehouse: prods.every(
|
||||
(p) => p.current_location_id === "virtual_warehouse"
|
||||
(p) => (p.macro_status || p.status).toUpperCase() === "ARCHIVED"
|
||||
),
|
||||
allCompleted: prods.every(
|
||||
(p) => (p.macro_status || p.status).toUpperCase() === "COMPLETED"
|
||||
),
|
||||
};
|
||||
});
|
||||
@ -683,7 +692,13 @@ export default function AdminTasksPage() {
|
||||
已全部入库
|
||||
</span>
|
||||
)}
|
||||
{!group.allInWarehouse && (
|
||||
{group.allCompleted && !group.allInWarehouse && group.products.length > 0 && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-orange-50 px-2.5 py-0.5 text-xs font-medium text-orange-600">
|
||||
<Warehouse className="h-3 w-3" />
|
||||
已完工待入库
|
||||
</span>
|
||||
)}
|
||||
{!group.allInWarehouse && !group.allCompleted && (
|
||||
<span className="text-xs text-gray-400">流转中</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@ -24,6 +24,8 @@ const STATUS_LABEL: Record<string, string> = {
|
||||
WIP: "进行中",
|
||||
PENDING: "待接收",
|
||||
COMPLETED: "已完成",
|
||||
ARCHIVED: "已入库",
|
||||
OUTBOUND: "已出库",
|
||||
"—": "未涉及",
|
||||
};
|
||||
|
||||
@ -31,6 +33,8 @@ const STATUS_BADGE: Record<string, string> = {
|
||||
WIP: "bg-blue-100 text-blue-700",
|
||||
PENDING: "bg-amber-100 text-amber-700",
|
||||
COMPLETED: "bg-green-100 text-green-700",
|
||||
ARCHIVED: "bg-gray-100 text-gray-600",
|
||||
OUTBOUND: "bg-indigo-100 text-indigo-700",
|
||||
"—": "bg-gray-100 text-gray-500",
|
||||
};
|
||||
|
||||
@ -38,6 +42,8 @@ const STATUS_DOT: Record<string, string> = {
|
||||
WIP: "blue",
|
||||
PENDING: "orange",
|
||||
COMPLETED: "green",
|
||||
ARCHIVED: "gray",
|
||||
OUTBOUND: "indigo",
|
||||
"—": "gray",
|
||||
};
|
||||
|
||||
|
||||
@ -96,6 +96,7 @@ export interface ProductScanResponse {
|
||||
parent_product_id: string | null;
|
||||
current_location_id: string | null;
|
||||
status: string;
|
||||
overall_status: string | null;
|
||||
created_at: string;
|
||||
top_level_tasks: TaskSummary[];
|
||||
/** 完整递归任务树 — 供十字矩阵树状图渲染 */
|
||||
|
||||
Reference in New Issue
Block a user