From abe2c041719fcd0d8432936ce059e9ea412a2ce7 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 Oct 2025 23:40:48 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90mall=20=E5=95=86=E5=9F=8E?= =?UTF-8?q?=E3=80=91sku-list=20=E7=BB=84=E4=BB=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=EF=BC=88antd=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/spu/form/modules/sku-list.vue | 128 ++++++++---------- 1 file changed, 56 insertions(+), 72 deletions(-) diff --git a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue b/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue index b3df262c1..8c3b4a1f1 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue +++ b/apps/web-antd/src/views/mall/product/spu/form/modules/sku-list.vue @@ -7,7 +7,12 @@ import type { MallSpuApi } from '#/api/mall/product/spu'; import { ref, watch } from 'vue'; -import { copyValueToTarget, formatToFraction, isEmpty } from '@vben/utils'; +import { + copyValueToTarget, + formatToFraction, + getNestedValue, + isEmpty, +} from '@vben/utils'; import { Button, Image, Input, InputNumber, message } from 'ant-design-vue'; @@ -43,9 +48,12 @@ const emit = defineEmits<{ const { isBatch, isDetail, isComponent, isActivityComponent } = props; -const formData: Ref = ref(); // 表单数据 -const skuList = ref([ - { +const formData: Ref = ref(); +const tableHeaders = ref<{ label: string; prop: string }[]>([]); + +/** 创建空 SKU 数据 */ +function createEmptySku(): MallSpuApi.Sku { + return { price: 0, marketPrice: 0, costPrice: 0, @@ -56,8 +64,10 @@ const skuList = ref([ volume: 0, firstBrokeragePrice: 0, secondBrokeragePrice: 0, - }, -]); // 批量添加时的临时数据 + }; +} + +const skuList = ref([createEmptySku()]); /** 批量添加 */ function batchAdd() { @@ -79,34 +89,33 @@ function validateProperty() { } } -/** 删除 sku */ +/** 删除 SKU */ function deleteSku(row: MallSpuApi.Sku) { const index = formData.value!.skus!.findIndex( - // 直接把列表转成字符串比较 (sku: MallSpuApi.Sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties), ); - formData.value!.skus!.splice(index, 1); + if (index !== -1) { + formData.value!.skus!.splice(index, 1); + } } -const tableHeaders = ref<{ label: string; prop: string }[]>([]); // 多属性表头 - -/** 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种 */ +/** 校验 SKU 数据:保存时,每个商品规格的表单要校验。例如:销售金额最低是 0.01 */ function validateSku() { validateProperty(); let warningInfo = '请检查商品各行相关属性配置,'; - let validate = true; // 默认通过 + let validate = true; + for (const sku of formData.value!.skus!) { - // 作为活动组件的校验 for (const rule of props?.ruleConfig as RuleConfig[]) { - const arg = getValue(sku, rule.name); - if (!rule.rule(arg)) { - validate = false; // 只要有一个不通过则直接不通过 + const value = getNestedValue(sku, rule.name); + if (!rule.rule(value)) { + validate = false; warningInfo += rule.message; break; } } - // 只要有一个不通过则结束后续的校验 + if (!validate) { message.warning(warningInfo); throw new Error(warningInfo); @@ -114,21 +123,6 @@ function validateSku() { } } -// TODO @puhui999:是不是可以通过 getNestedValue 简化? -function getValue(obj: any, arg: string): unknown { - const keys = arg.split('.'); - let value: any = obj; - for (const key of keys) { - if (value && typeof value === 'object' && key in value) { - value = value[key]; - } else { - value = undefined; - break; - } - } - return value; -} - /** * 选择时触发 * @@ -155,7 +149,6 @@ watch( /** 生成表数据 */ function generateTableData(propertyList: PropertyAndValues[]) { - // 构建数据结构 const propertyValues = propertyList.map((item: PropertyAndValues) => (item.values || []).map((v: { id: number; name: string }) => ({ propertyId: item.id, @@ -164,35 +157,30 @@ function generateTableData(propertyList: PropertyAndValues[]) { valueName: v.name, })), ); + const buildSkuList = build(propertyValues); + // 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表 if (!validateData(propertyList)) { - // 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表 formData.value!.skus = []; } + for (const item of buildSkuList) { + const properties = Array.isArray(item) ? item : [item]; const row = { - properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象 - price: 0, - marketPrice: 0, - costPrice: 0, - barCode: '', - picUrl: '', - stock: 0, - weight: 0, - volume: 0, - firstBrokeragePrice: 0, - secondBrokeragePrice: 0, + ...createEmptySku(), + properties, }; + // 如果存在属性相同的 sku 则不做处理 - const index = formData.value!.skus!.findIndex( + const exists = formData.value!.skus!.some( (sku: MallSpuApi.Sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties), ); - if (index !== -1) { - continue; + + if (!exists) { + formData.value!.skus!.push(row); } - formData.value!.skus!.push(row); } } @@ -248,43 +236,33 @@ watch( if (!formData.value!.specType) { return; } + // 如果当前组件作为批量添加数据使用,则重置表数据 if (props.isBatch) { - skuList.value = [ - { - price: 0, - marketPrice: 0, - costPrice: 0, - barCode: '', - picUrl: '', - stock: 0, - weight: 0, - volume: 0, - firstBrokeragePrice: 0, - secondBrokeragePrice: 0, - }, - ]; + skuList.value = [createEmptySku()]; } // 判断代理对象是否为空 if (JSON.stringify(propertyList) === '[]') { return; } - // 重置表头 - tableHeaders.value = []; - // 生成表头 - propertyList.forEach((item, index) => { - // name加属性项index区分属性值 - tableHeaders.value.push({ prop: `name${index}`, label: item.name }); - }); + + // 重置并生成表头 + tableHeaders.value = propertyList.map((item, index) => ({ + prop: `name${index}`, + label: item.name, + })); + // 如果回显的 sku 属性和添加的属性一致则不处理 if (validateData(propertyList)) { return; } + // 添加新属性没有属性值也不做处理 if (propertyList.some((item) => !item.values || isEmpty(item.values))) { return; } + // 生成 table 数据,即 sku 列表 generateTableData(propertyList); }, @@ -296,17 +274,23 @@ watch( const activitySkuListRef = ref(); +/** 获取 SKU 表格引用 */ function getSkuTableRef() { return activitySkuListRef.value; } -defineExpose({ generateTableData, validateSku, getSkuTableRef }); +defineExpose({ + generateTableData, + validateSku, + getSkuTableRef, +});