fix(intake): improve planning layout autosave and scrolling

This commit is contained in:
WuYongJie 2026-05-25 16:58:17 +08:00
parent 1e8c5970b0
commit d544c14c5d
5 changed files with 393 additions and 81 deletions

View File

@ -1189,6 +1189,11 @@
background: rgba(45, 51, 56, 0.48);
}
.non-integrated-workspace-table-scroll {
overflow: auto !important;
padding-bottom: 0;
}
[data-theme='dark'] ::-webkit-scrollbar-thumb {
background: rgba(220, 227, 233, 0.15);
}

View File

@ -2508,7 +2508,6 @@ function getPopulatedPlanningLayoutRowCountForList(
row.style,
row.styleTone,
row.audience,
row.model,
row.priceBand,
row.brand ?? "",
row.shop,

View File

@ -3828,28 +3828,34 @@ describe("NonIntegratedDetailClient", () => {
);
await actor.click(screen.getByTestId("document-workspace-trigger-企划布局"));
const modelInput = screen.getByTestId(
"planning-layout-model-stock-space-node-permission-sku-1",
);
expect(modelInput).not.toHaveAttribute("readonly");
fireEvent.change(
modelInput,
{ target: { value: "现代书桌" } },
screen.getByTestId(
"planning-layout-priceBand-stock-space-node-permission-sku-1",
),
{ target: { value: "500-600" } },
);
await waitFor(() => {
const savedProduct = getNonIntegratedProductById(
"STOCK001-space-node-permission",
);
expect(savedProduct?.planningLayoutRows?.[0]?.model).toBe("现代书桌");
});
await actor.click(screen.getByTestId("document-workspace-trigger-产品限价"));
expect(
screen.getByTestId(
"product-limit-expectedDailyLimitPrice-stock-space-node-permission-sku-1",
),
).toHaveAttribute("readonly");
await actor.click(screen.getByTestId("document-workspace-trigger-企划布局"));
expect(
screen.getByTestId(
"planning-layout-priceBand-stock-space-node-permission-sku-1",
),
).toHaveValue("500-600");
await waitFor(() => {
const savedProduct = getNonIntegratedProductById(
"STOCK001-space-node-permission",
);
expect(savedProduct?.planningLayoutRows?.[0]?.priceBand).toBe("500-600");
});
expect(
screen.queryByTestId("document-workspace-trigger-产品定厂"),
).not.toBeInTheDocument();
@ -5692,10 +5698,6 @@ describe("NonIntegratedDetailClient", () => {
{ target: { value: "独居青年" } },
);
expect(within(planningWorkspace).getByText("独居青年")).toBeInTheDocument();
fireEvent.change(
screen.getByTestId("planning-layout-model-sku-planning-layout-fields-1"),
{ target: { value: "窄边书桌" } },
);
fireEvent.change(
screen.getByTestId("planning-layout-priceBand-sku-planning-layout-fields-1"),
{ target: { value: "500-600档" } },
@ -5775,7 +5777,7 @@ describe("NonIntegratedDetailClient", () => {
audiences: ["有孩家庭", "独居青年"],
spaces: ["客厅", "书房"],
categories: ["书柜/架", "书桌/电脑桌"],
model: "窄边书桌",
model: "",
priceBand: "500-600档",
brand: "代理品牌-顾家",
brands: ["代理品牌-顾家"],
@ -5785,6 +5787,166 @@ describe("NonIntegratedDetailClient", () => {
});
});
it("copies planning layout field values and replaces target values on paste", async () => {
const actor = userEvent.setup();
saveNonIntegratedProduct(
createProductRecord({
id: "DNZ0001-planning-layout-copy-paste",
skuItems: [
createSkuItem({
id: "sku-planning-layout-copy-paste-1",
model: "SZ0230K01",
skuCode: "SZ023001",
barcode: "SZ023001N1280V01",
specName: "款式一 140/60/75 胡桃色",
color: "胡桃色",
}),
createSkuItem({
id: "sku-planning-layout-copy-paste-2",
model: "SZ0230K02",
skuCode: "SZ023002",
barcode: "SZ023002N1280V01",
specName: "款式二 140/60/75 白色",
color: "白色",
}),
],
planningLayoutRows: [
{
skuId: "sku-planning-layout-copy-paste-1",
style: "治愈风",
styles: ["治愈风", "日系风"],
styleTone: "",
styleTones: [],
audience: "",
audiences: [],
spaces: [],
categories: [],
model: "",
priceBand: "",
brand: "",
brands: [],
shop: "",
},
{
skuId: "sku-planning-layout-copy-paste-2",
style: "工业风",
styles: ["工业风"],
styleTone: "",
styleTones: [],
audience: "",
audiences: [],
spaces: [],
categories: [],
model: "",
priceBand: "",
brand: "",
brands: [],
shop: "",
},
],
}),
);
render(
<NonIntegratedDetailClient
mode="edit"
productId="DNZ0001-planning-layout-copy-paste"
/>,
);
await actor.click(screen.getByTestId("document-workspace-trigger-企划布局"));
await actor.click(
screen.getByTestId(
"planning-layout-copy-styles-sku-planning-layout-copy-paste-1",
),
);
fireEvent.paste(
screen.getByTestId(
"planning-layout-styles-draft-sku-planning-layout-copy-paste-2",
),
);
const planningWorkspace = screen.getByTestId("planning-layout-workspace");
expect(within(planningWorkspace).queryByText("工业风")).not.toBeInTheDocument();
expect(within(planningWorkspace).getAllByText("治愈风")).toHaveLength(2);
expect(within(planningWorkspace).getAllByText("日系风")).toHaveLength(2);
await waitFor(() => {
const savedProduct = getNonIntegratedProductById(
"DNZ0001-planning-layout-copy-paste",
);
expect(savedProduct?.planningLayoutRows).toEqual([
expect.objectContaining({
skuId: "sku-planning-layout-copy-paste-1",
style: "治愈风",
styles: ["治愈风", "日系风"],
}),
expect.objectContaining({
skuId: "sku-planning-layout-copy-paste-2",
style: "治愈风",
styles: ["治愈风", "日系风"],
}),
]);
});
});
it("keeps planning layout price band empty after manual clear", async () => {
const actor = userEvent.setup();
saveNonIntegratedProduct(
createProductRecord({
id: "DNZ0001-planning-layout-price-clear",
skuItems: [
createSkuItem({
id: "sku-planning-layout-price-clear-1",
model: "SZ0230K01",
skuCode: "SZ023001",
barcode: "SZ023001N1280V01",
specName: "款式一 140/60/75 胡桃色",
}),
],
productLimitRows: [
{
skuId: "sku-planning-layout-price-clear-1",
expectedDailyLimitPrice: "599",
expectedLimitGrossMarginRate: "",
nonSpuOwnerLimitPrice: "",
},
],
}),
);
render(
<NonIntegratedDetailClient
mode="edit"
productId="DNZ0001-planning-layout-price-clear"
/>,
);
await actor.click(screen.getByTestId("document-workspace-trigger-企划布局"));
const priceBandInput = screen.getByTestId(
"planning-layout-priceBand-sku-planning-layout-price-clear-1",
);
expect(priceBandInput).toHaveValue("500-600");
fireEvent.change(priceBandInput, { target: { value: "" } });
expect(priceBandInput).toHaveValue("");
await waitFor(() => {
const savedProduct = getNonIntegratedProductById(
"DNZ0001-planning-layout-price-clear",
);
expect(savedProduct?.planningLayoutRows).toEqual([
expect.objectContaining({
skuId: "sku-planning-layout-price-clear-1",
priceBand: "",
priceBandAutoCleared: true,
}),
]);
});
});
it("renders product limit rows and persists editable limit fields", async () => {
const actor = userEvent.setup();

View File

@ -23,7 +23,7 @@ import {
useState,
} from "react";
import { createPortal } from "react-dom";
import { ArrowLeft, Ban, ChevronDown, Plus, Search, X } from "lucide-react";
import { ArrowLeft, Ban, ChevronDown, Copy, Plus, Search, X } from "lucide-react";
import CameraScannerModal from "@/components/mobile/CameraScannerModal";
import { ModelPreviewPanel } from "@/components/platform/ModelPreviewPanel";
import Button from "@/components/ui/Button";
@ -2698,7 +2698,6 @@ const PLANNING_LAYOUT_SPACE_CATEGORY_OPTIONS: Record<string, string[]> = {
卫生间: ["洗脸池柜", "镜柜", "镜子", "置物架", "毛巾架", "矮凳"],
厨房: ["桌面置物架", "落地置物架", "储物柜", "矮凳", "推车", "桌面收纳架"],
};
const PLANNING_LAYOUT_MODEL_OPTIONS: string[] = [];
const PLANNING_LAYOUT_SHOP_OPTIONS: string[] = [];
const SINGLE_VALUE_BRAND_FIELD_KEY = "brand";
@ -5589,14 +5588,12 @@ type PlanningLayoutSkuRow = {
audiences: string[];
spaces: string[];
categories: string[];
planningModel: string;
priceBand: string;
brands: string[];
shop: string;
};
type PlanningLayoutTextFieldKey =
| "model"
| "priceBand"
| "shop";
type PlanningLayoutListFieldKey =
@ -5606,6 +5603,20 @@ type PlanningLayoutListFieldKey =
| "spaces"
| "categories"
| "brands";
type PlanningLayoutCopyFieldKey =
| PlanningLayoutTextFieldKey
| PlanningLayoutListFieldKey;
type PlanningLayoutClipboardValue =
| {
field: PlanningLayoutTextFieldKey;
type: "text";
value: string;
}
| {
field: PlanningLayoutListFieldKey;
type: "list";
values: string[];
};
type ProductLimitDisplayRow = NonIntegratedProductLimitRow & {
image: string;
@ -5716,22 +5727,23 @@ function createEmptyPlanningLayoutRow(
skuId: string,
defaultBrands: string[] = [],
): NonIntegratedPlanningLayoutRow {
return {
skuId,
style: "",
styles: [],
styleTone: "",
return {
skuId,
style: "",
styles: [],
styleTone: "",
styleTones: [],
audience: "",
audiences: [],
spaces: [],
categories: [],
model: "",
priceBand: "",
brand: defaultBrands[0] ?? "",
brands: defaultBrands,
shop: "",
};
categories: [],
model: "",
priceBand: "",
priceBandAutoCleared: false,
brand: defaultBrands[0] ?? "",
brands: defaultBrands,
shop: "",
};
}
function getPlanningLayoutStyleToneOptions(
@ -5805,8 +5817,11 @@ function normalizePlanningLayoutRowsForPayload(
audiences,
spaces: getUniqueNonEmptyStrings(row.spaces),
categories: getUniqueNonEmptyStrings(row.categories),
model: row.model.trim(),
model: "",
priceBand: row.priceBand.trim(),
...(row.priceBandAutoCleared === true && !row.priceBand.trim()
? { priceBandAutoCleared: true }
: {}),
brand: brands[0] ?? "",
brands,
shop: row.shop.trim(),
@ -5820,10 +5835,10 @@ function normalizePlanningLayoutRowsForPayload(
row.style,
row.styleTone,
row.audience,
row.model,
row.priceBand,
row.brand ?? "",
row.shop,
row.priceBandAutoCleared ? "1" : "",
...row.styles,
...row.styleTones,
...row.audiences,
@ -5896,8 +5911,9 @@ function buildPlanningLayoutSkuRows(
]),
spaces: row?.spaces ?? [],
categories: row?.categories ?? [],
planningModel: row?.model.trim() ?? "",
priceBand: row?.priceBand.trim() || autoPriceBand,
priceBand:
row?.priceBand.trim() ||
(row?.priceBandAutoCleared ? "" : autoPriceBand),
brands: savedBrands,
shop: row?.shop.trim() ?? "",
};
@ -6794,6 +6810,7 @@ function InlineSuggestionInput({
showChevron = true,
filterOptions = false,
closeOnChange,
onPaste,
onChange,
onSelect,
onCommit,
@ -6811,6 +6828,7 @@ function InlineSuggestionInput({
showChevron?: boolean;
filterOptions?: boolean;
closeOnChange?: (value: string) => boolean;
onPaste?: (event: ClipboardEvent<HTMLInputElement>) => void;
onChange: (value: string) => void;
onSelect?: (value: string) => void;
onCommit?: () => void;
@ -6885,6 +6903,7 @@ function InlineSuggestionInput({
setIsOpen(closeOnChange?.(nextValue) ? false : true);
}
}}
onPaste={onPaste}
onFocus={() => {
if (hasOptions) setIsOpen(true);
}}
@ -10243,6 +10262,8 @@ export default function NonIntegratedDetailClient({
const [planningLayoutDraftInputs, setPlanningLayoutDraftInputs] = useState<
Record<string, string>
>({});
const [planningLayoutClipboard, setPlanningLayoutClipboard] =
useState<PlanningLayoutClipboardValue | null>(null);
const [planningLayoutBrandPickerSkuId, setPlanningLayoutBrandPickerSkuId] =
useState<string | null>(null);
const [
@ -14303,6 +14324,10 @@ export default function NonIntegratedDetailClient({
useEffect(() => {
return () => {
if (autoSaveTimeoutRef.current) {
window.clearTimeout(autoSaveTimeoutRef.current);
autoSaveTimeoutRef.current = null;
}
if (autoSaveNoticeTimeoutRef.current) {
window.clearTimeout(autoSaveNoticeTimeoutRef.current);
autoSaveNoticeTimeoutRef.current = null;
@ -14601,13 +14626,6 @@ export default function NonIntegratedDetailClient({
}
scheduleAutoSave();
return () => {
if (autoSaveTimeoutRef.current) {
window.clearTimeout(autoSaveTimeoutRef.current);
autoSaveTimeoutRef.current = null;
}
};
}, [
bomDetails,
documentWorkspaceNotes,
@ -16455,6 +16473,20 @@ export default function NonIntegratedDetailClient({
setSaveMessageType("success");
}
function updatePlanningLayoutTextField(
skuId: string,
field: PlanningLayoutTextFieldKey,
value: string,
) {
updatePlanningLayoutRow(skuId, (current) => ({
...current,
[field]: value,
...(field === "priceBand"
? { priceBandAutoCleared: value.trim().length === 0 }
: {}),
}));
}
function getPlanningLayoutDraftKey(
skuId: string,
field: PlanningLayoutListFieldKey,
@ -16588,6 +16620,96 @@ export default function NonIntegratedDetailClient({
});
}
function getPlanningLayoutFieldTextValue(
sourceRow: PlanningLayoutSkuRow,
field: PlanningLayoutCopyFieldKey,
) {
if (field === "priceBand" || field === "shop") return sourceRow[field].trim();
return getUniqueNonEmptyStrings(sourceRow[field]).join("、");
}
async function copyPlanningLayoutFieldToClipboard(
sourceRow: PlanningLayoutSkuRow,
field: PlanningLayoutCopyFieldKey,
label: string,
) {
if (isReadOnly) return;
const textValue = getPlanningLayoutFieldTextValue(sourceRow, field);
if (field === "priceBand" || field === "shop") {
setPlanningLayoutClipboard({
field,
type: "text",
value: textValue,
});
} else {
setPlanningLayoutClipboard({
field,
type: "list",
values: getUniqueNonEmptyStrings(sourceRow[field]),
});
}
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(textValue);
} catch {
// 页面内剪贴板已记录,系统剪贴板失败不影响本页 Ctrl+V。
}
}
setSaveMessage(`已复制${label},请选择目标字段后按 Ctrl+V`);
setSaveMessageType("success");
}
function pastePlanningLayoutClipboardToField(
skuId: string,
field: PlanningLayoutCopyFieldKey,
) {
if (isReadOnly || !planningLayoutClipboard) return false;
if (planningLayoutClipboard.field !== field) {
setSaveMessage("复制内容和当前字段不一致,请复制同一列后再粘贴");
setSaveMessageType("error");
return true;
}
if (planningLayoutClipboard.type === "text") {
updatePlanningLayoutRow(skuId, (current) => ({
...current,
[field]: planningLayoutClipboard.value,
...(field === "priceBand"
? {
priceBandAutoCleared:
planningLayoutClipboard.value.trim().length === 0,
}
: {}),
}));
return true;
}
if (
field !== "styles" &&
field !== "styleTones" &&
field !== "audiences" &&
field !== "spaces" &&
field !== "categories" &&
field !== "brands"
) {
return false;
}
updatePlanningLayoutRow(skuId, (current) =>
applyPlanningLayoutListValues(
current,
field,
planningLayoutClipboard.values,
),
);
return true;
}
function updateSampleMeasurementRow(
skuId: string,
key: Exclude<keyof NonIntegratedSampleMeasurementRow, "skuId">,
@ -21992,7 +22114,7 @@ export default function NonIntegratedDetailClient({
</div>
)}
</div>
<div className="min-h-0 flex-1 overflow-hidden">
<div className="flex min-h-0 flex-1 overflow-hidden">
{renderFactoryQualityWorkspace()}
</div>
</div>
@ -22271,7 +22393,7 @@ export default function NonIntegratedDetailClient({
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productFactoryRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再定厂。
@ -22281,7 +22403,7 @@ export default function NonIntegratedDetailClient({
当前批次还没有 SKU 信息。
</div>
) : activeProductFactoryTab === "factoryInfo" ? (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[2040px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
@ -22523,7 +22645,7 @@ export default function NonIntegratedDetailClient({
</table>
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[980px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
@ -22623,7 +22745,7 @@ export default function NonIntegratedDetailClient({
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productSampleMeasurementRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再进行样包测量。
@ -22633,8 +22755,8 @@ export default function NonIntegratedDetailClient({
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<table className="min-w-[1560px] w-full border-collapse text-xs">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[2120px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
{[
@ -22798,7 +22920,7 @@ export default function NonIntegratedDetailClient({
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productFinishedSampleShipmentRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再进行成品寄样。
@ -22808,7 +22930,7 @@ export default function NonIntegratedDetailClient({
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[1760px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
@ -22979,6 +23101,26 @@ export default function NonIntegratedDetailClient({
</button>
);
};
const renderCopyFieldButton = (
row: PlanningLayoutSkuRow,
field: PlanningLayoutCopyFieldKey,
label: string,
) => {
if (isReadOnly || displayedPlanningLayoutSkuRows.length < 2) return null;
return (
<button
type="button"
aria-label={`复制${label}`}
title={`复制${label}`}
data-testid={`planning-layout-copy-${field}-${row.skuId}`}
onClick={() => void copyPlanningLayoutFieldToClipboard(row, field, label)}
className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-apple-sm border border-apple-separator bg-apple-bg-primary text-apple-label-tertiary transition hover:bg-apple-bg-tertiary hover:text-apple-label-primary"
>
<Copy className="h-3 w-3" />
</button>
);
};
const renderSearchCell = ({
row,
field,
@ -23015,12 +23157,14 @@ export default function NonIntegratedDetailClient({
onChange(nextValue);
return;
}
updatePlanningLayoutRow(row.skuId, (current) => ({
...current,
[field]: nextValue,
}));
updatePlanningLayoutTextField(row.skuId, field, nextValue);
}}
placeholder={`选择${label}`}
onPaste={(event) => {
if (pastePlanningLayoutClipboardToField(row.skuId, field)) {
event.preventDefault();
}
}}
wrapperClassName="relative min-w-0 flex-1"
inputClassName={`min-w-0 w-full bg-transparent pr-7 text-xs outline-none placeholder:text-apple-label-tertiary ${
isReadOnly
@ -23029,6 +23173,7 @@ export default function NonIntegratedDetailClient({
}`}
panelClassName="absolute left-0 top-[calc(100%+6px)] z-50 max-h-56 min-w-full overflow-auto rounded-apple-lg border border-apple-separator bg-apple-bg-primary p-1 shadow-apple-md"
/>
{renderCopyFieldButton(row, field, label)}
</div>
);
};
@ -23110,6 +23255,15 @@ export default function NonIntegratedDetailClient({
closeOnChange={(nextValue) =>
autoAddOnChange && options.includes(nextValue.trim())
}
onPaste={(event) => {
if (pastePlanningLayoutClipboardToField(row.skuId, field)) {
event.preventDefault();
setPlanningLayoutDraftInputs((current) => ({
...current,
[draftKey]: "",
}));
}
}}
onChange={(nextValue) => {
updatePlanningLayoutDraftInput(row.skuId, field, nextValue);
if (!autoAddOnChange) return;
@ -23130,6 +23284,7 @@ export default function NonIntegratedDetailClient({
onCommit={() => addPlanningLayoutListValue(row.skuId, field)}
/>
)}
{renderCopyFieldButton(row, field, label)}
</div>
</div>
);
@ -23294,7 +23449,7 @@ export default function NonIntegratedDetailClient({
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{planningLayoutSkuRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
{derivedSkuItems.length === 0
@ -23306,7 +23461,7 @@ export default function NonIntegratedDetailClient({
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[3340px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
@ -23331,7 +23486,6 @@ export default function NonIntegratedDetailClient({
"适用人群",
"适用空间",
"关联品类",
"款式",
"价格带",
"适配品牌",
"店铺",
@ -23475,18 +23629,6 @@ export default function NonIntegratedDetailClient({
autoAddOnChange: true,
})}
</td>
<td className="px-3 py-2">
{renderSearchCell({
row,
field: "model",
label: "款式",
value: row.planningModel,
options: getUniqueNonEmptyStrings([
...PLANNING_LAYOUT_MODEL_OPTIONS,
row.planningModel,
]),
})}
</td>
<td className="px-3 py-2">
{renderSearchCell({
row,
@ -23599,7 +23741,7 @@ export default function NonIntegratedDetailClient({
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productLimitDisplayRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再填写产品限价。
@ -23609,7 +23751,7 @@ export default function NonIntegratedDetailClient({
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[1480px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
@ -23714,7 +23856,7 @@ export default function NonIntegratedDetailClient({
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productPricingRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再核价。
@ -23724,7 +23866,7 @@ export default function NonIntegratedDetailClient({
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto overflow-y-hidden rounded-apple-lg border border-apple-separator pb-2">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[1280px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
@ -23994,7 +24136,7 @@ function renderFactoryQualityWorkspace() {
</div>
</div>
<div className="min-h-0 min-w-0 flex-1 overflow-auto p-3">
<div className="min-h-0 min-w-0 flex-1 overflow-hidden p-3">
{productFirstQualityRows.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-apple-xl border border-dashed border-apple-separator text-sm text-apple-label-secondary">
当前还没有 SKU 信息,先维护 SKU 后再进行首批质检。
@ -24004,8 +24146,8 @@ function renderFactoryQualityWorkspace() {
当前批次还没有 SKU 信息。
</div>
) : (
<div className="non-integrated-table-scroll max-w-full overflow-x-auto rounded-apple-lg border border-apple-separator">
<table className="min-w-[1560px] w-full border-collapse text-xs">
<div className="non-integrated-table-scroll non-integrated-workspace-table-scroll h-full max-w-full rounded-apple-lg border border-apple-separator">
<table className="min-w-[2120px] w-full border-collapse text-xs">
<thead className="bg-apple-bg-secondary/40 text-apple-label-secondary">
<tr>
{[

View File

@ -433,6 +433,7 @@ export type NonIntegratedPlanningLayoutRow = {
categories: string[];
model: string;
priceBand: string;
priceBandAutoCleared?: boolean;
brand?: string;
brands: string[];
shop: string;
@ -1400,6 +1401,9 @@ function normalizePlanningLayoutRow(
categories: normalizeStringList(item.categories),
model: normalizeString(item.model),
priceBand: normalizeString(item.priceBand),
...(item.priceBandAutoCleared === true
? { priceBandAutoCleared: true }
: {}),
brand: normalizeNonIntegratedBrand(normalizedBrands[0]) ?? "",
brands: normalizeNonIntegratedBrandList(normalizedBrands),
shop: normalizeString(item.shop),