Compare commits

...

1 Commits

Author SHA1 Message Date
jingchuan
11ccc8922e fix(supply-chain): refine unified table layout 2026-08-16 17:25:33 +08:00

View File

@ -100,7 +100,8 @@ type TimingDetailDefinition = {
type DetailColumnGroup = {
label: string
columnSpan: number
baseColumnSpan: number
timingColumnSpan?: number
toneClass: string
stickyClass?: string
}
@ -229,9 +230,10 @@ const COMPLETION_ESTIMATE_STATUS_OPTIONS: FilterOption[] = [
{ value: 'PENDING_CONFIRMATION', label: '暂无法预估,需每日跟进' },
]
const COLUMN_WIDTHS = {
image: 110,
image: 96,
openingCode: 160,
status: 130,
workflowLink: 140,
status: 110,
createdDate: 120,
approvalStage: 190,
currentNode: 180,
@ -240,7 +242,7 @@ const COLUMN_WIDTHS = {
const STICKY_LEFT = {
image: 0,
openingCode: COLUMN_WIDTHS.image,
status: COLUMN_WIDTHS.image + COLUMN_WIDTHS.openingCode,
workflowLink: COLUMN_WIDTHS.image + COLUMN_WIDTHS.openingCode,
} as const
const TIMING_DETAIL_DEFINITIONS: TimingDetailDefinition[] = [
@ -357,45 +359,112 @@ const TIMING_DETAIL_DEFINITIONS: TimingDetailDefinition[] = [
{ id: 'actual-listing', label: '实际-运营上架', metricKey: 'listing', valueType: 'actual' },
]
const BASE_DETAIL_COLUMN_GROUPS: DetailColumnGroup[] = [
const TIMING_DETAIL_DEFINITION_BY_ID = new Map(
TIMING_DETAIL_DEFINITIONS.map((field) => [field.id, field]),
)
const TIMING_FIELD_IDS_BY_GROUP = {
appearance: ['target-exterior', 'actual-exterior', 'timeout-exterior'],
structure: ['target-structure', 'actual-structure', 'timeout-structure'],
sampling: ['target-sample-prepare', 'actual-sample-prepare', 'timeout-sample-prepare'],
review: ['target-sample-review', 'actual-sample-review', 'timeout-sample-review'],
production: [
'target-production',
'actual-production',
'timeout-production',
'target-material',
'actual-material',
'timeout-material',
'market-analysis',
'target-first-order',
'actual-first-order',
'timeout-first-order',
'target-pre-production',
'actual-pre-production',
'timeout-pre-production',
'target-shipment',
'actual-shipment',
'timeout-shipment',
'target-warehouse-qc',
'actual-warehouse-qc',
'target-sample-send',
'actual-sample-send',
'target-listing',
'actual-listing',
],
drawing: [
'target-install-manual',
'actual-install-manual',
'target-art',
'actual-art',
'timeout-art',
],
pricing: [
'target-pricing',
'actual-pricing',
'timeout-pricing',
'actual-pricing-parallel',
'target-pricing-flow',
'actual-pricing-flow',
'timeout-pricing-flow',
],
} as const
const DETAIL_COLUMN_GROUPS: DetailColumnGroup[] = [
{
label: '流程标识',
columnSpan: 2,
baseColumnSpan: 3,
toneClass: 'bg-zinc-100 text-zinc-700',
stickyClass: 'sticky left-0 z-50',
},
{ label: '总流程', baseColumnSpan: 4, toneClass: 'bg-sky-50 text-sky-800' },
{ label: '时效指标', baseColumnSpan: 4, toneClass: 'bg-slate-100 text-slate-700' },
{ label: '产品信息', baseColumnSpan: 17, toneClass: 'bg-emerald-50 text-emerald-800' },
{ label: '发起信息', baseColumnSpan: 3, toneClass: 'bg-cyan-50 text-cyan-800' },
{ label: '人员信息', baseColumnSpan: 3, toneClass: 'bg-amber-50 text-amber-800' },
{
label: '总流程',
columnSpan: 1,
toneClass: 'bg-sky-50 text-sky-800',
stickyClass: 'sticky left-[270px] z-50 shadow-[8px_0_10px_-10px_rgba(0,0,0,0.18)]',
label: '外观',
baseColumnSpan: 1,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.appearance.length,
toneClass: 'bg-rose-50 text-rose-800',
},
{ label: '发起信息', columnSpan: 1, toneClass: 'bg-cyan-50 text-cyan-800' },
{ label: '总流程', columnSpan: 3, toneClass: 'bg-sky-50 text-sky-800' },
{ label: '产品信息', columnSpan: 14, toneClass: 'bg-emerald-50 text-emerald-800' },
{ label: '人员信息', columnSpan: 8, toneClass: 'bg-amber-50 text-amber-800' },
{ label: '流程标识', columnSpan: 1, toneClass: 'bg-zinc-100 text-zinc-700' },
{ label: '产品信息', columnSpan: 7, toneClass: 'bg-emerald-50 text-emerald-800' },
{ label: '发起信息', columnSpan: 1, toneClass: 'bg-cyan-50 text-cyan-800' },
{ label: '时效指标', columnSpan: 3, toneClass: 'bg-slate-100 text-slate-700' },
{ label: '跟进信息', columnSpan: 1, toneClass: 'bg-rose-50 text-rose-800' },
{ label: '阶段时效', columnSpan: 1, toneClass: 'bg-slate-100 text-slate-700' },
]
const TIMING_DETAIL_COLUMN_GROUPS: DetailColumnGroup[] = [
{ label: '外观阶段', columnSpan: 3, toneClass: 'bg-rose-50 text-rose-800' },
{ label: '结构阶段', columnSpan: 3, toneClass: 'bg-indigo-50 text-indigo-800' },
{ label: '打样阶段', columnSpan: 3, toneClass: 'bg-amber-50 text-amber-800' },
{ label: '审样阶段', columnSpan: 3, toneClass: 'bg-fuchsia-50 text-fuchsia-800' },
{ label: '生产与资料', columnSpan: 6, toneClass: 'bg-teal-50 text-teal-800' },
{ label: '产品信息', columnSpan: 1, toneClass: 'bg-emerald-50 text-emerald-800' },
{ label: '生产阶段', columnSpan: 9, toneClass: 'bg-teal-50 text-teal-800' },
{ label: '限价阶段', columnSpan: 4, toneClass: 'bg-yellow-50 text-yellow-800' },
{ label: '质检与图纸', columnSpan: 4, toneClass: 'bg-cyan-50 text-cyan-800' },
{ label: '寄样阶段', columnSpan: 2, toneClass: 'bg-pink-50 text-pink-800' },
{ label: '出图阶段', columnSpan: 3, toneClass: 'bg-blue-50 text-blue-800' },
{ label: '限价阶段', columnSpan: 3, toneClass: 'bg-yellow-50 text-yellow-800' },
{ label: '上架阶段', columnSpan: 2, toneClass: 'bg-lime-50 text-lime-800' },
{
label: '结构',
baseColumnSpan: 1,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.structure.length,
toneClass: 'bg-indigo-50 text-indigo-800',
},
{
label: '打样',
baseColumnSpan: 2,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.sampling.length,
toneClass: 'bg-amber-50 text-amber-800',
},
{
label: '审样',
baseColumnSpan: 1,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.review.length,
toneClass: 'bg-fuchsia-50 text-fuchsia-800',
},
{
label: '生产',
baseColumnSpan: 1,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.production.length,
toneClass: 'bg-teal-50 text-teal-800',
},
{
label: '出图',
baseColumnSpan: 1,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.drawing.length,
toneClass: 'bg-blue-50 text-blue-800',
},
{
label: '限价',
baseColumnSpan: 1,
timingColumnSpan: TIMING_FIELD_IDS_BY_GROUP.pricing.length,
toneClass: 'bg-yellow-50 text-yellow-800',
},
{ label: '跟进信息', baseColumnSpan: 1, toneClass: 'bg-rose-50 text-rose-800' },
]
const STAGE_GRAPH_NODE_WIDTH = 104
@ -453,6 +522,25 @@ function formatCellText(value: string | null | undefined) {
return text || '-'
}
function DetailCellText({
value,
className = '',
}: {
value: string | null | undefined
className?: string
}) {
const text = formatCellText(value)
return (
<span
className={`line-clamp-2 break-words leading-4 ${className}`}
title={text === '-' ? undefined : text}
>
{text}
</span>
)
}
function formatDateText(value: string | null | undefined) {
const text = String(value ?? '').trim()
return text ? text.slice(0, 10) : '-'
@ -776,6 +864,50 @@ function buildTimingDetailFields(item: OaBoardProjectListItemDTO): TimingDetailF
})
}
function TimingDetailCells({
itemKey,
fieldsById,
fieldIds,
}: {
itemKey: string
fieldsById: Map<string, TimingDetailField>
fieldIds: readonly string[]
}) {
return fieldIds.map((fieldId) => {
const field = fieldsById.get(fieldId)
if (!field) return null
return (
<td key={`${itemKey}-${field.id}`} className="px-3 py-3 align-top text-[11px]">
{field.timeoutState === null || field.timeoutState === undefined ? (
<DetailCellText value={field.value} />
) : (
<StatusBadge
label={field.timeoutState ? '是' : '否'}
tone={field.timeoutState ? 'danger' : 'success'}
/>
)}
</td>
)
})
}
function TimingDetailHeaderCells({ fieldIds }: { fieldIds: readonly string[] }) {
return fieldIds.map((fieldId) => {
const field = TIMING_DETAIL_DEFINITION_BY_ID.get(fieldId)
if (!field) return null
return (
<th
key={`timing-header-${field.id}`}
className="w-[120px] px-3 py-3 font-apple-semibold leading-4"
>
{field.label}
</th>
)
})
}
function getOvertimeStages(item: OaBoardProjectListItemDTO): OvertimeStage[] {
const stageNames = splitStageText(
item.formSummary.approvalStageDisplay || item.currentApprovalStageName,
@ -2322,6 +2454,7 @@ function DetailTableRow({
onPreviewImage: (image: StyleImagePreview) => void
}) {
const timingFields = buildTimingDetailFields(item)
const timingFieldsById = new Map(timingFields.map((field) => [field.id, field]))
const currentOvertimeStages = getOvertimeStages(item)
const historicalOvertimeStages = getHistoricalOvertimeStages(item)
const followupDisplayStages = getFollowupDisplayStages(
@ -2335,7 +2468,7 @@ function DetailTableRow({
const openingCode = formatCellText(item.formSummary.openingCode || item.openingCode)
return (
<tr className="border-b border-apple-separator odd:bg-white even:bg-[#fbfcfd]">
<tr className="h-24 border-b border-apple-separator odd:bg-white even:bg-[#fbfcfd]">
<td
className={`${stickyCellClass(STICKY_LEFT.image, 'z-10')} px-3 py-3 align-top`}
style={{
@ -2356,18 +2489,18 @@ function DetailTableRow({
})
}
title="打开图片预览"
className="group relative block h-14 w-14 overflow-hidden rounded-apple-md border border-apple-separator bg-white outline-none transition hover:border-[color:var(--color-blue)] focus-visible:ring-2 focus-visible:ring-[color:var(--color-blue)]"
className="group relative block h-[72px] w-[72px] overflow-hidden rounded-apple-md border border-apple-separator bg-white outline-none transition hover:border-[color:var(--color-blue)] focus-visible:ring-2 focus-visible:ring-[color:var(--color-blue)]"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={primaryStyleImage.url}
alt={primaryStyleImage.alt || openingCode}
className="h-full w-full object-cover transition duration-200 group-hover:scale-[1.04]"
className="h-full w-full object-contain transition duration-200 group-hover:scale-[1.04]"
/>
<span className="pointer-events-none absolute inset-0 bg-black/0 transition group-hover:bg-black/8" />
</button>
) : (
<div className="flex h-14 w-14 items-center justify-center rounded-apple-md border border-apple-separator bg-[#f4f6f7] text-[10px] text-apple-label-tertiary">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-apple-md border border-apple-separator bg-[#f4f6f7] text-[10px] text-apple-label-tertiary">
无图片
</div>
)}
@ -2381,12 +2514,22 @@ function DetailTableRow({
maxWidth: COLUMN_WIDTHS.openingCode,
}}
>
{formatCellText(item.formSummary.openingCode || item.openingCode)}
<DetailCellText value={item.formSummary.openingCode || item.openingCode} />
</td>
<td
className={`${stickyCellClass(STICKY_LEFT.status, 'z-10 shadow-[8px_0_10px_-10px_rgba(0,0,0,0.18)]')} px-3 py-3 align-top text-[11px]`}
className={`${stickyCellClass(STICKY_LEFT.workflowLink, 'z-10 shadow-[8px_0_10px_-10px_rgba(0,0,0,0.18)]')} px-3 py-3 align-top text-[11px]`}
style={{
left: STICKY_LEFT.workflowLink,
width: COLUMN_WIDTHS.workflowLink,
minWidth: COLUMN_WIDTHS.workflowLink,
maxWidth: COLUMN_WIDTHS.workflowLink,
}}
>
<DetailCellText value={item.formSummary.workflowLink} className="break-all" />
</td>
<td
className="px-3 py-3 align-top text-[11px]"
style={{
left: STICKY_LEFT.status,
width: COLUMN_WIDTHS.status,
minWidth: COLUMN_WIDTHS.status,
maxWidth: COLUMN_WIDTHS.status,
@ -2403,16 +2546,6 @@ function DetailTableRow({
}
/>
</td>
<td
className="px-3 py-3 align-top text-[11px] whitespace-nowrap"
style={{
width: COLUMN_WIDTHS.createdDate,
minWidth: COLUMN_WIDTHS.createdDate,
maxWidth: COLUMN_WIDTHS.createdDate,
}}
>
{formatDateText(item.createdDate)}
</td>
<td
className="px-3 py-3 align-top text-[11px]"
style={{
@ -2421,7 +2554,7 @@ function DetailTableRow({
maxWidth: COLUMN_WIDTHS.approvalStage,
}}
>
{formatCellText(item.formSummary.approvalStageDisplay || item.currentApprovalStageName)}
<DetailCellText value={item.formSummary.approvalStageDisplay || item.currentApprovalStageName} />
</td>
<td
className="px-3 py-3 align-top text-[11px]"
@ -2431,7 +2564,7 @@ function DetailTableRow({
maxWidth: COLUMN_WIDTHS.currentNode,
}}
>
{formatCellText(item.formSummary.currentNodeDisplay)}
<DetailCellText value={item.formSummary.currentNodeDisplay} />
</td>
<td
className="px-3 py-3 align-top text-[11px]"
@ -2441,25 +2574,41 @@ function DetailTableRow({
maxWidth: COLUMN_WIDTHS.currentOwner,
}}
>
{resolveCurrentOwnerDisplay(item)}
<DetailCellText value={resolveCurrentOwnerDisplay(item)} />
</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.demandType)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.developmentPlan)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.productType || item.productType)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.shippingType)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.needVisualDesign)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.plannedSalesBrands)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.category)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.categoryName)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.salesPriceBand)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.platformPriceBand)}</td>
<td className="px-3 py-3 align-top text-[11px]">
<DetailCellText value={formatDayValue(item.overallTargetHours)} />
</td>
<td className="px-3 py-3 align-top text-[11px]">
<DetailCellText value={formatDayValue(item.overallActualHours)} />
</td>
<td className="px-3 py-3 align-top text-[11px]">
<StatusBadge label={isOvertime ? '是' : '否'} tone={isOvertime ? 'danger' : 'success'} />
</td>
<td className="px-3 py-3 align-top text-[11px]">
<button
type="button"
onClick={onToggleTimingColumns}
className="rounded-full border border-black/8 bg-white px-3 py-1.5 text-[11px] font-apple-medium text-apple-label-primary transition hover:bg-[#f4f6f7]"
>
{showTimingColumns ? '收起时效详情' : '查看时效详情'}
</button>
</td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.demandType} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.developmentPlan} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.productType || item.productType} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.plannedSalesBrands} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.category} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.categoryName} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.salesPriceBand} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.platformPriceBand} /></td>
<td className="px-3 py-3 align-top text-[11px]">
{competitorLink.summary === '-' ? (
'-'
) : competitorLink.href ? (
<div className="flex max-w-[260px] items-center gap-2">
<div className="flex max-w-[240px] items-start gap-2">
<span
className="min-w-0 flex-1 truncate text-apple-label-primary"
className="line-clamp-2 min-w-0 flex-1 break-all leading-4 text-apple-label-primary"
title={competitorLink.title}
>
{competitorLink.summary}
@ -2475,39 +2624,50 @@ function DetailTableRow({
</a>
</div>
) : (
<span className="block max-w-[260px] truncate text-apple-label-primary" title={competitorLink.title}>
<span className="line-clamp-2 block max-w-[240px] break-all leading-4 text-apple-label-primary" title={competitorLink.title}>
{competitorLink.summary}
</span>
)}
</td>
<td className="px-3 py-3 align-top text-[11px] break-all">{formatCellText(item.formSummary.openingReason)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.isTestStyle)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.testStyleCode)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.productPlanner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.operationBrandOwner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.spuOwner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.appearanceDesigner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.structureDesigner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.purchaseOwner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.merchandiserOwner)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.pricingOwner)}</td>
<td className="px-3 py-3 align-top text-[11px] break-all">{formatCellText(item.formSummary.workflowLink)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.needPatent)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.needTrademark)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.reviewResult)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.sampleReviewResult)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.salesBrands)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.spu || item.spu)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(item.formSummary.openingBrand)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatCellText(readCreatorDisplay(item))}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatDayValue(item.overallTargetHours)}</td>
<td className="px-3 py-3 align-top text-[11px]">{formatDayValue(item.overallActualHours)}</td>
<td className="px-3 py-3 align-top text-[11px]">
<StatusBadge
label={isOvertime ? '是' : '否'}
tone={isOvertime ? 'danger' : 'success'}
/>
</td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.isTestStyle} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.testStyleCode} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.needPatent} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.needTrademark} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.reviewResult} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.salesBrands} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.spu || item.spu} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.openingBrand} /></td>
<td className="px-3 py-3 align-top text-[11px] whitespace-nowrap"><DetailCellText value={formatDateText(item.createdDate)} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.openingReason} className="break-all" /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={readCreatorDisplay(item)} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.productPlanner} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.operationBrandOwner} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.spuOwner} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.appearanceDesigner} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.appearance} /> : null}
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.structureDesigner} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.structure} /> : null}
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.purchaseOwner} /></td>
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.merchandiserOwner} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.sampling} /> : null}
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.sampleReviewResult} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.review} /> : null}
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.shippingType} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.production} /> : null}
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.needVisualDesign} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.drawing} /> : null}
<td className="px-3 py-3 align-top text-[11px]"><DetailCellText value={item.formSummary.pricingOwner} /></td>
{showTimingColumns ? <TimingDetailCells itemKey={item.boardKey} fieldsById={timingFieldsById} fieldIds={TIMING_FIELD_IDS_BY_GROUP.pricing} /> : null}
<td className="px-3 py-3 align-top text-[11px]">
{timeoutFollowupSummary ? (
<button
@ -2522,48 +2682,18 @@ function DetailTableRow({
'-'
)}
</td>
<td className="px-3 py-3 align-top text-[11px]">
<button
type="button"
onClick={onToggleTimingColumns}
className="rounded-full border border-black/8 bg-white px-3 py-1.5 text-[11px] font-apple-medium text-apple-label-primary transition hover:bg-[#f4f6f7]"
>
{showTimingColumns ? '收起时效详情' : '查看时效详情'}
</button>
</td>
{showTimingColumns
? timingFields.map((field) => (
<td
key={`${item.boardKey}-${field.id}`}
className="px-3 py-3 align-top text-[11px]"
>
{field.timeoutState === null || field.timeoutState === undefined ? (
field.value
) : (
<StatusBadge
label={field.timeoutState ? '是' : '否'}
tone={field.timeoutState ? 'danger' : 'success'}
/>
)}
</td>
))
: null}
</tr>
)
}
function DetailColumnGroupRow({ showTimingColumns }: { showTimingColumns: boolean }) {
const groups = showTimingColumns
? [...BASE_DETAIL_COLUMN_GROUPS, ...TIMING_DETAIL_COLUMN_GROUPS]
: BASE_DETAIL_COLUMN_GROUPS
return (
<tr className="h-8 border-b border-apple-separator bg-[#f8fafb]">
{groups.map((group, index) => (
{DETAIL_COLUMN_GROUPS.map((group, index) => (
<th
key={`${group.label}-${index}`}
scope="colgroup"
colSpan={group.columnSpan}
colSpan={group.baseColumnSpan + (showTimingColumns ? group.timingColumnSpan ?? 0 : 0)}
className={`h-8 border-r border-white/80 px-3 py-0 text-left text-[10px] font-apple-semibold leading-none ${group.toneClass} ${group.stickyClass ?? ''}`}
>
{group.label}
@ -4369,12 +4499,15 @@ export default function OaBoardClient({
) : (
<>
<div className="relative isolate min-h-0 flex-1 overflow-auto">
<table className={`table-fixed text-left text-[11px] ${showTimingColumns ? 'min-w-[7180px]' : 'min-w-[4780px]'}`}>
<table
className="table-fixed text-left text-[11px]"
style={{ minWidth: showTimingColumns ? 10400 : 5200 }}
>
<colgroup>
<col style={{ width: COLUMN_WIDTHS.image }} />
<col style={{ width: COLUMN_WIDTHS.openingCode }} />
<col style={{ width: COLUMN_WIDTHS.workflowLink }} />
<col style={{ width: COLUMN_WIDTHS.status }} />
<col style={{ width: COLUMN_WIDTHS.createdDate }} />
<col style={{ width: COLUMN_WIDTHS.approvalStage }} />
<col style={{ width: COLUMN_WIDTHS.currentNode }} />
<col style={{ width: COLUMN_WIDTHS.currentOwner }} />
@ -4405,9 +4538,19 @@ export default function OaBoardClient({
开款编号
</th>
<th
className={`${stickyCellClass(STICKY_LEFT.status, `${embeddedTab ? 'top-8' : 'top-0'} z-50 shadow-[8px_0_10px_-10px_rgba(0,0,0,0.18)]`)} px-3 py-3 font-apple-semibold`}
className={`${stickyCellClass(STICKY_LEFT.workflowLink, `${embeddedTab ? 'top-8' : 'top-0'} z-50 shadow-[8px_0_10px_-10px_rgba(0,0,0,0.18)]`)} px-3 py-3 font-apple-semibold`}
style={{
left: STICKY_LEFT.workflowLink,
width: COLUMN_WIDTHS.workflowLink,
minWidth: COLUMN_WIDTHS.workflowLink,
maxWidth: COLUMN_WIDTHS.workflowLink,
}}
>
流程链接
</th>
<th
className="px-3 py-3 font-apple-semibold"
style={{
left: STICKY_LEFT.status,
width: COLUMN_WIDTHS.status,
minWidth: COLUMN_WIDTHS.status,
maxWidth: COLUMN_WIDTHS.status,
@ -4415,16 +4558,6 @@ export default function OaBoardClient({
>
总流程状态
</th>
<th
className="px-3 py-3 font-apple-semibold"
style={{
width: COLUMN_WIDTHS.createdDate,
minWidth: COLUMN_WIDTHS.createdDate,
maxWidth: COLUMN_WIDTHS.createdDate,
}}
>
创建日期
</th>
<th
className="px-3 py-3 font-apple-semibold"
style={{
@ -4455,52 +4588,60 @@ export default function OaBoardClient({
>
当前负责人
</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">总目标时效</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">总实际时效</th>
<th className="w-[100px] px-3 py-3 font-apple-semibold">是否超时</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">时效详情</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">需求类型</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">开发方案</th>
<th className="w-[220px] px-3 py-3 font-apple-semibold">产品类型</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">发货类型</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">是否需要视觉出图</th>
<th className="w-[220px] px-3 py-3 font-apple-semibold">计划售卖品牌</th>
<th className="w-[200px] px-3 py-3 font-apple-semibold">产品类型</th>
<th className="w-[200px] px-3 py-3 font-apple-semibold">计划售卖品牌</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">品类</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">类目</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">售卖价格带</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">平台价格带</th>
<th className="w-[280px] px-3 py-3 font-apple-semibold">竞品链接</th>
<th className="w-[260px] px-3 py-3 font-apple-semibold">开款原因</th>
<th className="w-[240px] px-3 py-3 font-apple-semibold">竞品链接</th>
<th className="w-[100px] px-3 py-3 font-apple-semibold">是否测款</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">测款编号</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">商品企划</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">运营品牌负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">SPU负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">外观设计师</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">结构设计师</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">采购负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">跟单负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">限价负责人</th>
<th className="w-[280px] px-3 py-3 font-apple-semibold">由壹点产品开发流程链接地址</th>
<th className="w-[100px] px-3 py-3 font-apple-semibold">是否申请专利</th>
<th className="w-[100px] px-3 py-3 font-apple-semibold">是否注册商标</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">产品开款评审结果</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">审样结果</th>
<th className="w-[180px] px-3 py-3 font-apple-semibold">售卖品牌</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">SPU</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">SPU</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">开款品牌</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">创建日期</th>
<th className="w-[240px] px-3 py-3 font-apple-semibold">开款原因</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">创建人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">总目标时效</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">总实际时效</th>
<th className="w-[100px] px-3 py-3 font-apple-semibold">是否超时</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">商品企划</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">运营品牌负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">SPU负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">外观设计师</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.appearance} /> : null}
<th className="w-[120px] px-3 py-3 font-apple-semibold">结构设计师</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.structure} /> : null}
<th className="w-[120px] px-3 py-3 font-apple-semibold">采购负责人</th>
<th className="w-[120px] px-3 py-3 font-apple-semibold">跟单负责人</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.sampling} /> : null}
<th className="w-[120px] px-3 py-3 font-apple-semibold">审样结果</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.review} /> : null}
<th className="w-[120px] px-3 py-3 font-apple-semibold">发货类型</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.production} /> : null}
<th className="w-[140px] px-3 py-3 font-apple-semibold">是否需要视觉出图</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.drawing} /> : null}
<th className="w-[120px] px-3 py-3 font-apple-semibold">限价负责人</th>
{showTimingColumns ? <TimingDetailHeaderCells fieldIds={TIMING_FIELD_IDS_BY_GROUP.pricing} /> : null}
<th className="w-[154px] px-3 py-3 font-apple-semibold">超时跟进</th>
<th className="w-[140px] px-3 py-3 font-apple-semibold">时效详情</th>
{showTimingColumns
? TIMING_DETAIL_DEFINITIONS.map((field) => (
<th
key={`timing-header-${field.id}`}
className="w-[160px] px-3 py-3 font-apple-semibold"
>
{field.label}
</th>
))
: null}
</tr>
</thead>
<tbody>