fix(intake): improve planning layout autosave and scrolling
This commit is contained in:
parent
1e8c5970b0
commit
d544c14c5d
@ -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);
|
||||
}
|
||||
|
||||
@ -2508,7 +2508,6 @@ function getPopulatedPlanningLayoutRowCountForList(
|
||||
row.style,
|
||||
row.styleTone,
|
||||
row.audience,
|
||||
row.model,
|
||||
row.priceBand,
|
||||
row.brand ?? "",
|
||||
row.shop,
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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>
|
||||
{[
|
||||
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user