From 9ebc027b962789ae0b2c26f9fc29fe052e53e81a Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 18 Aug 2026 13:01:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=95=86=E5=93=81=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/admin/panels/ProductMatchPanel.tsx | 182 ++++++++++++++---- 1 file changed, 147 insertions(+), 35 deletions(-) diff --git a/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx b/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx index 37a7c46e..9759ed0e 100644 --- a/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx @@ -4,6 +4,7 @@ import { App, Button, Card, + Checkbox, Descriptions, Form, Input, @@ -43,6 +44,7 @@ type MappingFormValues = { mappingId?: number ruleId: number sellerIds: string[] + productScope: 'all_products' | 'selected_products' relItemId?: string itemTitle?: string relSkuId?: string @@ -51,6 +53,11 @@ type MappingFormValues = { enabled?: boolean } +type SellerOption = { + value: string + label: string +} + export default function ProductMatchPanel() { const { message } = App.useApp() const queryClient = useQueryClient() @@ -99,7 +106,11 @@ export default function ProductMatchPanel() { function resetMappingForm() { mappingForm.resetFields() - mappingForm.setFieldsValue({ mappingType: 'product_default', enabled: true }) + mappingForm.setFieldsValue({ + mappingType: 'product_default', + productScope: 'selected_products', + enabled: true, + }) } function fillMappingFromSource( @@ -109,6 +120,7 @@ export default function ProductMatchPanel() { mappingForm.setFieldsValue({ mappingId: undefined, sellerIds: [source.sellerId], + productScope: mappingType === 'product_default' ? 'selected_products' : 'all_products', relItemId: mappingType === 'product_default' ? source.relItemId : '', itemTitle: mappingType === 'product_default' ? source.itemTitle : '', relSkuId: mappingType === 'sku_exact' ? source.relSkuId : '', @@ -128,6 +140,7 @@ export default function ProductMatchPanel() { mappingId: mapping.mappingId, ruleId: mapping.ruleId, sellerIds: mapping.sellerIds, + productScope: mapping.relItemId || mapping.itemTitle ? 'selected_products' : 'all_products', relItemId: mapping.relItemId, itemTitle: mapping.itemTitle, relSkuId: mapping.relSkuId, @@ -139,7 +152,8 @@ export default function ProductMatchPanel() { async function saveMapping(values: MappingFormValues) { try { - await saveAdminWorkProductRuleMapping(values) + const { productScope: _productScope, ...payload } = values + await saveAdminWorkProductRuleMapping(payload) message.success(values.mappingId ? '商品映射已更新' : '商品映射已创建') resetMappingForm() await queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-product-mappings'] }) @@ -149,6 +163,16 @@ export default function ProductMatchPanel() { } function handleMappingValuesChange(changedValues: Partial) { + if (changedValues.mappingType) { + const isProductDefault = changedValues.mappingType === 'product_default' + mappingForm.setFieldsValue({ + productScope: isProductDefault ? 'selected_products' : 'all_products', + ...(isProductDefault ? {} : { relItemId: '', itemTitle: '' }), + }) + } + if (changedValues.productScope === 'all_products') { + mappingForm.setFieldsValue({ relItemId: '', itemTitle: '' }) + } if ( changedValues.sellerIds && changedValues.sellerIds.length > 1 && @@ -187,15 +211,15 @@ export default function ProductMatchPanel() { key: 'product', render: (_, row) => (
- - {row.itemTitle || row.relItemId} + + {mappingProductScopeLabel(row)} 店铺{' '} {row.sellerIds .map((sellerId) => formatShopLabel(sellerId, shopNameBySellerId)) .join('、')}{' '} - {row.relItemId ? `· 商品 ${row.relItemId}` : ''} + · {mappingProductScopeLabel(row)} {row.mappingType !== 'product_default' ? ( @@ -378,7 +402,11 @@ export default function ProductMatchPanel() {
@@ -414,39 +442,63 @@ export default function ProductMatchPanel() { name="sellerIds" rules={[{ required: true, message: '至少选择一个店铺' }]} > - + - {({ getFieldValue }) => - getFieldValue('mappingType') === 'sku_exact' ? ( + {({ getFieldValue }) => { + const mappingType = getFieldValue('mappingType') + const isProductDefault = mappingType === 'product_default' + const productScope = getFieldValue('productScope') + const showProductScope = + isProductDefault || productScope === 'selected_products' + return ( <> - - - - - - + {!isProductDefault ? ( + + + + + ) : null} + {mappingType === 'sku_exact' ? ( + <> + + + + + + + + ) : mappingType === 'sku_series' ? ( + + + + ) : null} - ) : getFieldValue('mappingType') === 'sku_series' ? ( - - - - ) : null - } + ) + }} @@ -597,6 +649,54 @@ function MatchStatusTag({ status }: { status: string }) { return {label} } +function ShopSelector({ + value = [], + onChange, + options, +}: { + value?: string[] + onChange?: (nextValue: string[]) => void + options: SellerOption[] +}) { + const optionValues = options.map((option) => option.value) + const selectedValues = Array.isArray(value) ? value.map(String) : [] + const selectedOptionValues = selectedValues.filter((sellerId) => optionValues.includes(sellerId)) + const otherValues = selectedValues.filter((sellerId) => !optionValues.includes(sellerId)) + const allSelected = optionValues.length > 0 && selectedOptionValues.length === optionValues.length + const partiallySelected = selectedOptionValues.length > 0 && !allSelected + + return ( +
+ + onChange?.(event.target.checked ? [...otherValues, ...optionValues] : otherValues) + } + > + 全选店铺({optionValues.length}) + + onChange?.(nextValues.map(String))} + style={{ + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', + gap: 8, + width: '100%', + }} + > + {options.map((option) => ( + + {option.label} + + ))} + +
+ ) +} + function normalizeSkuSeriesName(value: string) { return value .normalize('NFKC') @@ -604,6 +704,18 @@ function normalizeSkuSeriesName(value: string) { .replace(/(?:\d+|[零一二三四五六七八九十百千两]+)(?:个|份|张|枚)/g, '') } +function mappingProductScopeLabel(mapping: WorkProductRuleMapping) { + if (mapping.itemTitle) return mapping.itemTitle + const productIds = String(mapping.relItemId || '') + .split(/[,,;;\s\n\r]+/) + .filter(Boolean) + return productIds.length > 0 ? `指定商品(${productIds.length} 个)` : '全部商品' +} + +function mappingProductScopeTooltip(mapping: WorkProductRuleMapping) { + return mapping.itemTitle || mapping.relItemId || '覆盖店铺内的全部商品' +} + function createShopNameBySellerId(shops: AdminKuaishouIndustryShopOption[]) { const names = new Map() for (const shop of shops) {