From 964dfbb94fe70318252829126d33a76722e12ed1 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Wed, 24 Jun 2026 12:41:33 +0800 Subject: [PATCH] Restyle record detail modal as grouped card (match demo design) - Dark header banner: work-order badge, total-row/workshop, delivery date - Conditional missing-parts alert banner - Process-node section with 4 track blocks; base-config section with highlighted key fields and full-width long-text fields; catch-all section - Grey out empty / '-' / blank values - formatCellValue now treats '-' as empty (added test) --- src/app/record-detail-modal.tsx | 332 ++++++++++++++++++++++++++++++-- src/app/record-detail.test.ts | 3 + src/app/record-detail.ts | 5 +- 3 files changed, 320 insertions(+), 20 deletions(-) diff --git a/src/app/record-detail-modal.tsx b/src/app/record-detail-modal.tsx index c0fa880..06f9dfe 100644 --- a/src/app/record-detail-modal.tsx +++ b/src/app/record-detail-modal.tsx @@ -1,14 +1,181 @@ "use client"; -import type { CSSProperties } from "react"; -import { Modal, Descriptions, Empty } from "antd"; +import { Modal } from "antd"; +import { + ClockCircleOutlined, + AppstoreOutlined, + UnorderedListOutlined, + WarningOutlined, + RightOutlined, +} from "@ant-design/icons"; +import type { ReactNode } from "react"; import type { DataRow } from "./table-filters"; import { pickRecordTitle, formatCellValue } from "./record-detail"; -const VALUE_STYLE: CSSProperties = { - whiteSpace: "pre-wrap", - wordBreak: "break-all", -}; +/* ── 字段分组定义(键名对应真实数据列;未列出的列自动落入"其他信息")── */ + +const BANNER_KEYS = ["工令号", "总排号", "车间", "交货日期"]; +const MISSING_KEY = "缺件明细"; + +const TRACK_GROUPS: { title: string; fields: string[] }[] = [ + { + title: "【1】备料与物料准备", + fields: ["执行卡下发日期", "物料类别", "焊接领料日期", "领料单签收日期", "库房发出日期"], + }, + { + title: "【2】前序加工(隔膜 / 喷涂)", + fields: ["隔膜接收", "隔离膜片接收", "车波纹日期", "烘洗", "喷涂发出", "喷涂回来"], + }, + { + title: "【3】焊接与装配", + fields: ["焊接接收日期", "膜片焊", "壳焊接员", "表壳焊接日期"], + }, + { + title: "【4】校验 / 测试 / 入库", + fields: ["超压日期", "退火日期", "氦测日期", "调校人", "调试日期", "检验员", "检验日期", "入库日期"], + }, +]; + +const SPEC_HIGHLIGHT = ["客户名称", "产品型号", "量程", "数量"]; +const SPEC_FIELDS = [ + "客户名称", "产品型号", "量程", "数量", + "隔膜类型", "隔膜大小", "隔膜材质", "膜片尺寸", "膜片材质", + "生产订单号", "订单号", "签订日期", "接单日期", "经办人", "盘号", "位号", "标准", +]; +const SPEC_FULL = ["技术参数", "特殊要求", "备注", "新参数"]; + +const DEFINED_KEYS = new Set([ + ...BANNER_KEYS, + MISSING_KEY, + ...TRACK_GROUPS.flatMap((g) => g.fields), + ...SPEC_FIELDS, + ...SPEC_FULL, +]); + +/* ── 卡片风子组件(对标 demo.tsx 的 Field / Section / TrackBlock)── */ + +function Field({ + label, + value, + highlight = false, + full = false, +}: { + label: string; + value: string | number | null; + highlight?: boolean; + full?: boolean; +}) { + const display = formatCellValue(value); + const isEmpty = display === "—"; + return ( +
+ + {label} + + + {display} + +
+ ); +} + +function Section({ + icon, + title, + gridCols = 3, + children, +}: { + icon: ReactNode; + title: string; + gridCols?: number; + children: ReactNode; +}) { + return ( +
+
+ + {icon} + +

{title}

+
+
+ {children} +
+
+ ); +} + +function TrackBlock({ title, children }: { title: string; children: ReactNode }) { + return ( +
+
+ + {title} +
+
+ {children} +
+
+ ); +} + +/* ── 主组件 ── */ export function RecordDetailModal({ record, @@ -21,24 +188,153 @@ export function RecordDetailModal({ open: boolean; onClose: () => void; }) { + const colsSet = new Set(columns); + const has = (k: string) => colsSet.has(k); + const val = (k: string): string | number | null => + record && has(k) ? (record[k] ?? null) : null; + + const others = columns.filter((c) => !DEFINED_KEYS.has(c)); + + const missingVal = val(MISSING_KEY); + const missingEmpty = formatCellValue(missingVal) === "—"; + + const workOrder = val("工令号"); + const workOrderDisplay = formatCellValue(workOrder); + const showFadedWorkOrder = workOrderDisplay !== "—"; + return ( - {record && columns.length > 0 ? ( - - {columns.map((col) => ( - - {formatCellValue(record[col])} - - ))} - - ) : ( - + {record && ( +
+ {/* 黑底信息头 */} +
+ {showFadedWorkOrder && ( +
+ {workOrderDisplay} +
+ )} +
+
+
工令号
+
+ {workOrderDisplay} +
+
+
+ 总排号 / 车间 +
+ {formatCellValue(val("总排号"))} + | + {formatCellValue(val("车间"))} +
+
+
+
+ 交货期 + + {formatCellValue(val("交货日期"))} + +
+
+ + {/* 缺件预警 */} + {has(MISSING_KEY) && !missingEmpty && ( +
+ + 缺件明细: + + {missingVal === null || missingVal === undefined ? "" : String(missingVal)} + +
+ )} + + {/* 明细主体 */} +
+ {/* 工序节点(提至最前) */} + {TRACK_GROUPS.some((g) => g.fields.some(has)) && ( +
} title="工序节点" gridCols={2}> + {TRACK_GROUPS.filter((g) => g.fields.some(has)).map((g) => ( + + {g.fields.filter(has).map((f) => ( + + ))} + + ))} +
+ )} + + {/* 基础配置与规格 */} + {SPEC_FIELDS.some(has) && ( +
} title="基础配置与规格" gridCols={3}> + {SPEC_FIELDS.filter(has).map((f) => ( + + ))} + {SPEC_FULL.filter(has).map((f) => ( + + ))} +
+ )} + + {/* 其他信息(兜底:未被分组的列) */} + {others.length > 0 && ( +
} title="其他信息" gridCols={3}> + {others.map((f) => ( + + ))} +
+ )} +
+
)}
); diff --git a/src/app/record-detail.test.ts b/src/app/record-detail.test.ts index 3dd8383..56d6562 100644 --- a/src/app/record-detail.test.ts +++ b/src/app/record-detail.test.ts @@ -29,6 +29,9 @@ describe("formatCellValue", () => { expect(formatCellValue("")).toBe("—"); expect(formatCellValue(" ")).toBe("—"); }); + it("hyphen '-' (no-value marker) -> em dash", () => { + expect(formatCellValue("-")).toBe("—"); + }); it("0 is NOT blank", () => { expect(formatCellValue(0)).toBe("0"); }); diff --git a/src/app/record-detail.ts b/src/app/record-detail.ts index e7ccb3a..c42e172 100644 --- a/src/app/record-detail.ts +++ b/src/app/record-detail.ts @@ -16,11 +16,12 @@ export function pickRecordTitle(record: DataRow): string { /** * 格式化单元格值用于详情 Modal 完整展示。 - * null / 空串 / 纯空白 -> "—";其余 String(value)。0 视为非空(与 isBlank 一致)。 + * null / "-" / 空串 / 纯空白 -> "—"(弱化显示);其余 String(value)。 + * 注:生产数据中 "-" 表示"无值",展示时与空白同等弱化(仅展示语义,与筛选用的 isBlank 不同)。 */ export function formatCellValue(value: string | number | null): string { if (value === null || value === undefined) return "—"; const s = String(value); - if (s.trim() === "") return "—"; + if (s === "-" || s.trim() === "") return "—"; return s; }