feat:【mall 商城】sku-list 组件优化(antd)

This commit is contained in:
puhui999
2025-10-31 23:40:48 +08:00
parent 58a0636959
commit abe2c04171

View File

@@ -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<MallSpuApi.Spu | undefined> = ref<MallSpuApi.Spu>(); // 表单数据
const skuList = ref<MallSpuApi.Sku[]>([
{
const formData: Ref<MallSpuApi.Spu | undefined> = ref<MallSpuApi.Spu>();
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<MallSpuApi.Sku[]>([
volume: 0,
firstBrokeragePrice: 0,
secondBrokeragePrice: 0,
},
]); // 批量添加时的临时数据
};
}
const skuList = ref<MallSpuApi.Sku[]>([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,
});
</script>
<template>
<div>
<!-- 情况一添加/修改 -->
<!-- TODO @puhui999有可以通过 grid 来做么主要考虑这样不直接使用 vxe 标签抽象程度更高 -->
<!-- TODO 还是先这样吧 🤣🤣 -->
<VxeTable
v-if="!isDetail && !isActivityComponent"
:data="isBatch ? skuList : formData?.skus || []"