fix: 包材列表改用 HTML table 替代 CSS Grid,保证列对齐

This commit is contained in:
Codex 2026-04-03 20:29:53 +08:00
parent 8f2875480d
commit f28e932576

View File

@ -17,7 +17,6 @@ import {
Copy,
FolderPlus,
ImageIcon,
MoreHorizontal,
Package2,
Pencil,
Plus,
@ -1097,166 +1096,153 @@ export default function PackagingMaterialsClient({
</div>
<div className="overflow-hidden rounded-[24px] border border-black/5">
<div className="grid grid-cols-[48px_1.4fr_1fr_0.9fr_1fr_0.8fr_0.7fr_auto] items-center gap-2 bg-apple-bg-secondary/65 px-4 py-3 text-sm text-apple-label-tertiary">
<span>图片</span>
<span>名称</span>
<span>编码</span>
<span>模板类型</span>
<span>规格</span>
<span>成本</span>
<span>状态</span>
<span></span>
</div>
<table className="w-full">
<thead>
<tr className="bg-apple-bg-secondary/65 text-left text-sm text-apple-label-tertiary">
<th className="w-12 px-4 py-3 font-medium">图片</th>
<th className="px-2 py-3 font-medium">名称</th>
<th className="px-2 py-3 font-medium">编码</th>
<th className="px-2 py-3 font-medium">模板类型</th>
<th className="px-2 py-3 font-medium">规格</th>
<th className="px-2 py-3 font-medium">成本</th>
<th className="px-2 py-3 font-medium">状态</th>
<th className="w-28 px-4 py-3 font-medium"></th>
</tr>
</thead>
<tbody className="divide-y divide-black/5">
{visibleVariants.length === 0 ? (
<tr>
<td colSpan={8} className="px-6 py-14 text-center text-sm text-apple-label-tertiary">
当前条件下还没有包材记录,可以先新建二级分类,再建包材。
</td>
</tr>
) : (
visibleVariants.map((variant) => {
const family = familyMap.get(variant.assetFamilyId) ?? null
const templateType = readPackagingTemplateType(variant, family)
const template = templateType ? PACKAGING_TEMPLATE_MAP.get(templateType) ?? null : null
const dimensions = (variant.dimensions ?? {}) as Record<string, number>
const rowComputedCost = (() => {
const businessData = (variant.businessData ?? {}) as Record<string, unknown>
const templateFields =
typeof businessData.templateFields === 'object' && businessData.templateFields
? (businessData.templateFields as Record<string, string>)
: {}
{visibleVariants.length === 0 ? (
<div className="px-6 py-14 text-center text-sm text-apple-label-tertiary">
当前条件下还没有包材记录,可以先新建二级分类,再建包材。
</div>
) : (
<div className="divide-y divide-black/5">
{visibleVariants.map((variant) => {
const family = familyMap.get(variant.assetFamilyId) ?? null
const templateType = readPackagingTemplateType(variant, family)
const template = templateType ? PACKAGING_TEMPLATE_MAP.get(templateType) ?? null : null
const dimensions = (variant.dimensions ?? {}) as Record<string, number>
const rowComputedCost = (() => {
const businessData = (variant.businessData ?? {}) as Record<string, unknown>
const templateFields =
typeof businessData.templateFields === 'object' && businessData.templateFields
? (businessData.templateFields as Record<string, string>)
: {}
return templateType
? computePackagingMaterialCost(templateType, templateFields)
: null
})()
return templateType
? computePackagingMaterialCost(templateType, templateFields)
: null
})()
const specParts: string[] = []
if (dimensions.length) specParts.push(`${dimensions.length}`)
if (dimensions.width) specParts.push(`${dimensions.width}`)
if (dimensions.height) specParts.push(`${dimensions.height}`)
const specLabel = specParts.length > 0 ? `${specParts.join('×')} mm` : '-'
const specParts: string[] = []
if (dimensions.length) specParts.push(`${dimensions.length}`)
if (dimensions.width) specParts.push(`${dimensions.width}`)
if (dimensions.height) specParts.push(`${dimensions.height}`)
const specLabel = specParts.length > 0 ? `${specParts.join('×')} mm` : '-'
return (
<div
key={variant.id}
role="button"
tabIndex={0}
aria-label={`查看包材 ${variant.name}`}
className={cn(
'grid w-full grid-cols-[48px_1.4fr_1fr_0.9fr_1fr_0.8fr_0.7fr_auto] items-center gap-2 px-4 py-3 text-left transition hover:bg-apple-bg-secondary/45',
selectedVariantId === variant.id && 'bg-apple-blue/5',
)}
onClick={() => {
setSelectedVariantId(variant.id)
setDetailMode('preview')
setEditDraft(null)
setDetailError(null)
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
return (
<tr
key={variant.id}
className={cn(
'cursor-pointer transition hover:bg-apple-bg-secondary/45',
selectedVariantId === variant.id && 'bg-apple-blue/5',
)}
onClick={() => {
setSelectedVariantId(variant.id)
setDetailMode('preview')
setEditDraft(null)
setDetailError(null)
}
}}
>
<div
className="flex items-center justify-center"
onClick={(event) => event.stopPropagation()}
}}
>
<label className="relative cursor-pointer">
<input
type="file"
accept="image/jpeg,image/png,image/webp"
className="hidden"
onChange={(event) => {
const file = event.target.files?.[0]
if (file) void handleUploadImage(variant, file)
event.target.value = ''
}}
/>
{variant.thumbnailUrl ? (
<img
src={variant.thumbnailUrl}
alt={variant.name}
className="h-9 w-9 rounded-apple-md border border-black/5 object-cover transition hover:opacity-80"
<td
className="px-4 py-3"
onClick={(event) => event.stopPropagation()}
>
<label className="relative inline-flex cursor-pointer">
<input
type="file"
accept="image/jpeg,image/png,image/webp"
className="hidden"
onChange={(event) => {
const file = event.target.files?.[0]
if (file) void handleUploadImage(variant, file)
event.target.value = ''
}}
/>
{variant.thumbnailUrl ? (
<img
src={variant.thumbnailUrl}
alt={variant.name}
className="h-9 w-9 rounded-apple-md border border-black/5 object-cover transition hover:opacity-80"
/>
) : (
<div className="flex h-9 w-9 items-center justify-center rounded-apple-md border border-dashed border-black/15 bg-apple-bg-secondary/60 transition hover:border-apple-blue/40 hover:bg-apple-blue/5">
<ImageIcon className="h-4 w-4 text-apple-label-tertiary" />
</div>
)}
</label>
</td>
<td className="max-w-0 px-2 py-3 text-sm font-apple-semibold">{variant.name}</td>
<td className="max-w-0 px-2 py-3 font-mono text-sm text-apple-label-secondary">{variant.code}</td>
<td className="max-w-0 px-2 py-3 text-sm text-apple-label-secondary">{template?.label ?? '未设置'}</td>
<td className="max-w-0 px-2 py-3 font-mono text-sm text-apple-label-secondary">{specLabel}</td>
<td className="px-2 py-3 text-sm">
{variant.costPrice != null ? (
<span className="font-apple-medium">¥ {variant.costPrice}</span>
) : rowComputedCost != null ? (
<span className="text-apple-label-secondary">¥ {rowComputedCost.toFixed(2)}</span>
) : (
<div className="flex h-9 w-9 items-center justify-center rounded-apple-md border border-dashed border-black/15 bg-apple-bg-secondary/60 transition hover:border-apple-blue/40 hover:bg-apple-blue/5">
<ImageIcon className="h-4 w-4 text-apple-label-tertiary" />
</div>
<span className="text-apple-label-tertiary">缺成本</span>
)}
</label>
</div>
<div className="min-w-0 flex items-center">
<span className="truncate text-sm font-apple-semibold">{variant.name}</span>
</div>
<div className="min-w-0 flex items-center text-apple-label-secondary">
<span className="truncate font-mono text-sm">{variant.code}</span>
</div>
<div className="min-w-0 flex items-center text-apple-label-secondary">
<span className="truncate text-sm">{template?.label ?? '未设置'}</span>
</div>
<div className="min-w-0 flex items-center text-apple-label-secondary">
<span className="truncate font-mono text-sm">{specLabel}</span>
</div>
<div className="min-w-0 flex items-center text-sm">
{variant.costPrice != null ? (
<span className="font-apple-medium">¥ {variant.costPrice}</span>
) : rowComputedCost != null ? (
<span className="text-apple-label-secondary">¥ {rowComputedCost.toFixed(2)}</span>
) : (
<span className="text-apple-label-tertiary">缺成本</span>
)}
</div>
<div className="flex items-center">
<Badge type={getStatusTone(variant.status)} variant="subtle" size="small">
{getStatusLabel(variant.status)}
</Badge>
</div>
<div
className="flex items-center justify-end gap-1"
onClick={(event) => event.stopPropagation()}
>
<button
type="button"
aria-label="编辑"
className="rounded-apple-md p-1.5 text-apple-label-secondary transition hover:bg-apple-blue/10 hover:text-apple-blue"
onClick={() => startEditVariant(variant)}
</td>
<td className="px-2 py-3">
<Badge type={getStatusTone(variant.status)} variant="subtle" size="small">
{getStatusLabel(variant.status)}
</Badge>
</td>
<td
className="px-4 py-3"
onClick={(event) => event.stopPropagation()}
>
<Pencil className="h-4 w-4" />
</button>
<button
type="button"
aria-label="复制"
className="rounded-apple-md p-1.5 text-apple-label-secondary transition hover:bg-apple-blue/10 hover:text-apple-blue"
onClick={() => openCreateDrawer(variant)}
>
<Copy className="h-4 w-4" />
</button>
<button
type="button"
aria-label="删除包材记录"
className="rounded-apple-md p-1.5 text-apple-label-secondary transition hover:bg-apple-red/10 hover:text-apple-red"
onClick={() =>
setDeleteTarget({
type: 'variant',
id: variant.id,
name: variant.name,
})
}
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
)
})}
</div>
)}
<div className="flex items-center justify-end gap-1">
<button
type="button"
aria-label="编辑"
className="rounded-apple-md p-1.5 text-apple-label-secondary transition hover:bg-apple-blue/10 hover:text-apple-blue"
onClick={() => startEditVariant(variant)}
>
<Pencil className="h-4 w-4" />
</button>
<button
type="button"
aria-label="复制"
className="rounded-apple-md p-1.5 text-apple-label-secondary transition hover:bg-apple-blue/10 hover:text-apple-blue"
onClick={() => openCreateDrawer(variant)}
>
<Copy className="h-4 w-4" />
</button>
<button
type="button"
aria-label="删除包材记录"
className="rounded-apple-md p-1.5 text-apple-label-secondary transition hover:bg-apple-red/10 hover:text-apple-red"
onClick={() =>
setDeleteTarget({
type: 'variant',
id: variant.id,
name: variant.name,
})
}
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</td>
</tr>
)
})
)}
</tbody>
</table>
</div>
</div>