C端SKU数量小于等于1不展示chip
后端修复规格轴数据错乱显示问题
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Image, Input, InputNumber, Modal, Radio, Select, Space, Switch, Table, Typography, message,
|
Button, Image, Input, InputNumber, Modal, Radio, Select, Space, Switch, Table, Typography, message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
@@ -50,6 +50,38 @@ function keyOf(ids: string[]) {
|
|||||||
return [...ids].sort().join('_');
|
return [...ids].sort().join('_');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSkuRows(initialSkus: SkuRow[]): SkuRow[] {
|
||||||
|
if (!initialSkus.length) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
specValueIds: [],
|
||||||
|
barcode69: '',
|
||||||
|
price: 0,
|
||||||
|
status: 'ON_SALE',
|
||||||
|
allowOnlinePurchase: true,
|
||||||
|
allowCrossCityDelivery: true,
|
||||||
|
allowOnSitePickup: false,
|
||||||
|
saleUnit: 'BOTTLE',
|
||||||
|
bottlesPerUnit: 1,
|
||||||
|
isDefault: true,
|
||||||
|
imageUrl: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return initialSkus.map((s) => ({
|
||||||
|
...s,
|
||||||
|
specValueIds: s.specValueIds ?? [],
|
||||||
|
status: s.status || 'ON_SALE',
|
||||||
|
allowOnlinePurchase: s.allowOnlinePurchase !== false,
|
||||||
|
allowCrossCityDelivery: s.allowCrossCityDelivery !== false,
|
||||||
|
allowOnSitePickup: !!s.allowOnSitePickup,
|
||||||
|
saleUnit: s.saleUnit === 'BOX' ? 'BOX' : 'BOTTLE',
|
||||||
|
bottlesPerUnit: s.bottlesPerUnit || (s.saleUnit === 'BOX' ? 6 : 1),
|
||||||
|
isDefault: !!s.isDefault,
|
||||||
|
imageUrl: s.imageUrl ?? '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
productId: string;
|
productId: string;
|
||||||
initialAttrs: SpecAttr[];
|
initialAttrs: SpecAttr[];
|
||||||
@@ -58,44 +90,19 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function ProductSpecsEditor({ productId, initialAttrs, initialSkus, onSaved }: Props) {
|
export default function ProductSpecsEditor({ productId, initialAttrs, initialSkus, onSaved }: Props) {
|
||||||
const [attrs, setAttrs] = useState<SpecAttr[]>(
|
const [attrs, setAttrs] = useState<SpecAttr[]>(() => (initialAttrs.length ? initialAttrs : []));
|
||||||
initialAttrs.length
|
const [skus, setSkus] = useState<SkuRow[]>(() => normalizeSkuRows(initialSkus));
|
||||||
? initialAttrs
|
|
||||||
: [],
|
|
||||||
);
|
|
||||||
const [skus, setSkus] = useState<SkuRow[]>(
|
|
||||||
initialSkus.length
|
|
||||||
? initialSkus.map((s) => ({
|
|
||||||
...s,
|
|
||||||
specValueIds: s.specValueIds ?? [],
|
|
||||||
status: s.status || 'ON_SALE',
|
|
||||||
allowOnlinePurchase: s.allowOnlinePurchase !== false,
|
|
||||||
allowCrossCityDelivery: s.allowCrossCityDelivery !== false,
|
|
||||||
allowOnSitePickup: !!s.allowOnSitePickup,
|
|
||||||
saleUnit: s.saleUnit === 'BOX' ? 'BOX' : 'BOTTLE',
|
|
||||||
bottlesPerUnit: s.bottlesPerUnit || (s.saleUnit === 'BOX' ? 6 : 1),
|
|
||||||
isDefault: !!s.isDefault,
|
|
||||||
imageUrl: s.imageUrl ?? '',
|
|
||||||
}))
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
specValueIds: [],
|
|
||||||
barcode69: '',
|
|
||||||
price: 0,
|
|
||||||
status: 'ON_SALE',
|
|
||||||
allowOnlinePurchase: true,
|
|
||||||
allowCrossCityDelivery: true,
|
|
||||||
allowOnSitePickup: false,
|
|
||||||
saleUnit: 'BOTTLE',
|
|
||||||
bottlesPerUnit: 1,
|
|
||||||
isDefault: true,
|
|
||||||
imageUrl: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [editIndex, setEditIndex] = useState<number | null>(null);
|
const [editIndex, setEditIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAttrs(initialAttrs.length ? initialAttrs : []);
|
||||||
|
setSkus(normalizeSkuRows(initialSkus));
|
||||||
|
setEditIndex(null);
|
||||||
|
// 仅切商品时重载,避免父组件重渲染冲掉编辑中内容
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [productId]);
|
||||||
|
|
||||||
const combos = useMemo(() => cartesian(attrs.filter((a) => a.name.trim() && a.values.some((v) => v.name.trim()))), [attrs]);
|
const combos = useMemo(() => cartesian(attrs.filter((a) => a.name.trim() && a.values.some((v) => v.name.trim()))), [attrs]);
|
||||||
|
|
||||||
function regenerateMatrix() {
|
function regenerateMatrix() {
|
||||||
|
|||||||
@@ -610,6 +610,7 @@ export default function ProductsPage() {
|
|||||||
label: '规格与 SKU',
|
label: '规格与 SKU',
|
||||||
children: (
|
children: (
|
||||||
<ProductSpecsEditor
|
<ProductSpecsEditor
|
||||||
|
key={String(detail.id)}
|
||||||
productId={String(detail.id)}
|
productId={String(detail.id)}
|
||||||
initialAttrs={(detail.specAttrs as never) ?? []}
|
initialAttrs={(detail.specAttrs as never) ?? []}
|
||||||
initialSkus={(detail.skus as never) ?? []}
|
initialSkus={(detail.skus as never) ?? []}
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ export default function ProductDetailPage() {
|
|||||||
|
|
||||||
const attrs = product?.specAttrs ?? [];
|
const attrs = product?.specAttrs ?? [];
|
||||||
const skus = product?.skus ?? [];
|
const skus = product?.skus ?? [];
|
||||||
const specEnabled = !!(product?.specEnabled && attrs.length > 0);
|
const onSaleSkuCount = skus.filter((s) => s.status === 'ON_SALE').length;
|
||||||
|
const specEnabled = onSaleSkuCount > 1 && attrs.length > 0;
|
||||||
const activeSku = useMemo(() => {
|
const activeSku = useMemo(() => {
|
||||||
if (!product) return undefined;
|
if (!product) return undefined;
|
||||||
if (!specEnabled) {
|
if (!specEnabled) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export interface ProductDto {
|
|||||||
allowOnlinePurchase?: boolean;
|
allowOnlinePurchase?: boolean;
|
||||||
/** 是否允许跨城配送 */
|
/** 是否允许跨城配送 */
|
||||||
allowCrossCityDelivery?: boolean;
|
allowCrossCityDelivery?: boolean;
|
||||||
/** v3.5.4:是否配置了销售规格(多 SKU) */
|
/** C 端是否展示规格选择(可售 SKU > 1 且已配规格轴) */
|
||||||
specEnabled?: boolean;
|
specEnabled?: boolean;
|
||||||
/** 列表轻量:展示 SKU 销售单位 */
|
/** 列表轻量:展示 SKU 销售单位 */
|
||||||
saleUnit?: ProductSaleUnit;
|
saleUnit?: ProductSaleUnit;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '../../common/test-whitelist/test-whitelist.service';
|
} from '../../common/test-whitelist/test-whitelist.service';
|
||||||
import { groupResourcesByProductId, mapProductMedia } from './catalog.mapper';
|
import { groupResourcesByProductId, mapProductMedia } from './catalog.mapper';
|
||||||
import {
|
import {
|
||||||
|
catalogSpecPickerEnabled,
|
||||||
flattenSkuOntoProduct,
|
flattenSkuOntoProduct,
|
||||||
listMinOnSalePrice,
|
listMinOnSalePrice,
|
||||||
mapSkuDto,
|
mapSkuDto,
|
||||||
@@ -133,7 +134,7 @@ export class CatalogService {
|
|||||||
allowOnSitePickup: flat.allowOnSitePickup,
|
allowOnSitePickup: flat.allowOnSitePickup,
|
||||||
allowOnlinePurchase: flat.allowOnlinePurchase,
|
allowOnlinePurchase: flat.allowOnlinePurchase,
|
||||||
allowCrossCityDelivery: flat.allowCrossCityDelivery,
|
allowCrossCityDelivery: flat.allowCrossCityDelivery,
|
||||||
specEnabled: specAttrs.length > 0 || skus.length > 1,
|
specEnabled: catalogSpecPickerEnabled(skus, specAttrs.length),
|
||||||
saleUnit: flat.saleUnit,
|
saleUnit: flat.saleUnit,
|
||||||
...media,
|
...media,
|
||||||
};
|
};
|
||||||
@@ -196,7 +197,7 @@ export class CatalogService {
|
|||||||
allowOnlinePurchase: flat.allowOnlinePurchase,
|
allowOnlinePurchase: flat.allowOnlinePurchase,
|
||||||
allowCrossCityDelivery: flat.allowCrossCityDelivery,
|
allowCrossCityDelivery: flat.allowCrossCityDelivery,
|
||||||
saleUnit: flat.saleUnit,
|
saleUnit: flat.saleUnit,
|
||||||
specEnabled: specAttrs.length > 0 || skus.length > 1,
|
specEnabled: catalogSpecPickerEnabled(skus, specAttrs.length),
|
||||||
specAttrs: mapSpecAttrsDto(specAttrs),
|
specAttrs: mapSpecAttrsDto(specAttrs),
|
||||||
skus: cSkus,
|
skus: cSkus,
|
||||||
defaultSkuId: defaultSku?.id.toString(),
|
defaultSkuId: defaultSku?.id.toString(),
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ export function isSkuOnSale(sku: { status: string }): boolean {
|
|||||||
return sku.status === 'ON_SALE';
|
return sku.status === 'ON_SALE';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** C 端是否展示规格选择:可售 SKU 多于 1 且已配规格轴 */
|
||||||
|
export function catalogSpecPickerEnabled(
|
||||||
|
skus: Array<{ status: string }>,
|
||||||
|
specAttrCount: number,
|
||||||
|
): boolean {
|
||||||
|
return skus.filter(isSkuOnSale).length > 1 && specAttrCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildSpecKey(valueIds: bigint[]): string {
|
export function buildSpecKey(valueIds: bigint[]): string {
|
||||||
if (!valueIds.length) return '';
|
if (!valueIds.length) return '';
|
||||||
return [...valueIds]
|
return [...valueIds]
|
||||||
|
|||||||
Reference in New Issue
Block a user