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 {
|
||||
Button, Image, Input, InputNumber, Modal, Radio, Select, Space, Switch, Table, Typography, message,
|
||||
} from 'antd';
|
||||
@@ -50,6 +50,38 @@ function keyOf(ids: string[]) {
|
||||
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 = {
|
||||
productId: string;
|
||||
initialAttrs: SpecAttr[];
|
||||
@@ -58,44 +90,19 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function ProductSpecsEditor({ productId, initialAttrs, initialSkus, onSaved }: Props) {
|
||||
const [attrs, setAttrs] = useState<SpecAttr[]>(
|
||||
initialAttrs.length
|
||||
? 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 [attrs, setAttrs] = useState<SpecAttr[]>(() => (initialAttrs.length ? initialAttrs : []));
|
||||
const [skus, setSkus] = useState<SkuRow[]>(() => normalizeSkuRows(initialSkus));
|
||||
const [saving, setSaving] = useState(false);
|
||||
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]);
|
||||
|
||||
function regenerateMatrix() {
|
||||
|
||||
@@ -610,6 +610,7 @@ export default function ProductsPage() {
|
||||
label: '规格与 SKU',
|
||||
children: (
|
||||
<ProductSpecsEditor
|
||||
key={String(detail.id)}
|
||||
productId={String(detail.id)}
|
||||
initialAttrs={(detail.specAttrs as never) ?? []}
|
||||
initialSkus={(detail.skus as never) ?? []}
|
||||
|
||||
@@ -140,7 +140,8 @@ export default function ProductDetailPage() {
|
||||
|
||||
const attrs = product?.specAttrs ?? [];
|
||||
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(() => {
|
||||
if (!product) return undefined;
|
||||
if (!specEnabled) {
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface ProductDto {
|
||||
allowOnlinePurchase?: boolean;
|
||||
/** 是否允许跨城配送 */
|
||||
allowCrossCityDelivery?: boolean;
|
||||
/** v3.5.4:是否配置了销售规格(多 SKU) */
|
||||
/** C 端是否展示规格选择(可售 SKU > 1 且已配规格轴) */
|
||||
specEnabled?: boolean;
|
||||
/** 列表轻量:展示 SKU 销售单位 */
|
||||
saleUnit?: ProductSaleUnit;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../../common/test-whitelist/test-whitelist.service';
|
||||
import { groupResourcesByProductId, mapProductMedia } from './catalog.mapper';
|
||||
import {
|
||||
catalogSpecPickerEnabled,
|
||||
flattenSkuOntoProduct,
|
||||
listMinOnSalePrice,
|
||||
mapSkuDto,
|
||||
@@ -133,7 +134,7 @@ export class CatalogService {
|
||||
allowOnSitePickup: flat.allowOnSitePickup,
|
||||
allowOnlinePurchase: flat.allowOnlinePurchase,
|
||||
allowCrossCityDelivery: flat.allowCrossCityDelivery,
|
||||
specEnabled: specAttrs.length > 0 || skus.length > 1,
|
||||
specEnabled: catalogSpecPickerEnabled(skus, specAttrs.length),
|
||||
saleUnit: flat.saleUnit,
|
||||
...media,
|
||||
};
|
||||
@@ -196,7 +197,7 @@ export class CatalogService {
|
||||
allowOnlinePurchase: flat.allowOnlinePurchase,
|
||||
allowCrossCityDelivery: flat.allowCrossCityDelivery,
|
||||
saleUnit: flat.saleUnit,
|
||||
specEnabled: specAttrs.length > 0 || skus.length > 1,
|
||||
specEnabled: catalogSpecPickerEnabled(skus, specAttrs.length),
|
||||
specAttrs: mapSpecAttrsDto(specAttrs),
|
||||
skus: cSkus,
|
||||
defaultSkuId: defaultSku?.id.toString(),
|
||||
|
||||
@@ -18,6 +18,14 @@ export function isSkuOnSale(sku: { status: string }): boolean {
|
||||
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 {
|
||||
if (!valueIds.length) return '';
|
||||
return [...valueIds]
|
||||
|
||||
Reference in New Issue
Block a user