feat:【mall 商城】sku-list 组件优化(antd)
This commit is contained in:
@@ -7,7 +7,12 @@ import type { MallSpuApi } from '#/api/mall/product/spu';
|
|||||||
|
|
||||||
import { ref, watch } from 'vue';
|
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';
|
import { Button, Image, Input, InputNumber, message } from 'ant-design-vue';
|
||||||
|
|
||||||
@@ -43,9 +48,12 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const { isBatch, isDetail, isComponent, isActivityComponent } = props;
|
const { isBatch, isDetail, isComponent, isActivityComponent } = props;
|
||||||
|
|
||||||
const formData: Ref<MallSpuApi.Spu | undefined> = ref<MallSpuApi.Spu>(); // 表单数据
|
const formData: Ref<MallSpuApi.Spu | undefined> = ref<MallSpuApi.Spu>();
|
||||||
const skuList = ref<MallSpuApi.Sku[]>([
|
const tableHeaders = ref<{ label: string; prop: string }[]>([]);
|
||||||
{
|
|
||||||
|
/** 创建空 SKU 数据 */
|
||||||
|
function createEmptySku(): MallSpuApi.Sku {
|
||||||
|
return {
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
@@ -56,8 +64,10 @@ const skuList = ref<MallSpuApi.Sku[]>([
|
|||||||
volume: 0,
|
volume: 0,
|
||||||
firstBrokeragePrice: 0,
|
firstBrokeragePrice: 0,
|
||||||
secondBrokeragePrice: 0,
|
secondBrokeragePrice: 0,
|
||||||
},
|
};
|
||||||
]); // 批量添加时的临时数据
|
}
|
||||||
|
|
||||||
|
const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]);
|
||||||
|
|
||||||
/** 批量添加 */
|
/** 批量添加 */
|
||||||
function batchAdd() {
|
function batchAdd() {
|
||||||
@@ -79,34 +89,33 @@ function validateProperty() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除 sku */
|
/** 删除 SKU */
|
||||||
function deleteSku(row: MallSpuApi.Sku) {
|
function deleteSku(row: MallSpuApi.Sku) {
|
||||||
const index = formData.value!.skus!.findIndex(
|
const index = formData.value!.skus!.findIndex(
|
||||||
// 直接把列表转成字符串比较
|
|
||||||
(sku: MallSpuApi.Sku) =>
|
(sku: MallSpuApi.Sku) =>
|
||||||
JSON.stringify(sku.properties) === JSON.stringify(row.properties),
|
JSON.stringify(sku.properties) === JSON.stringify(row.properties),
|
||||||
);
|
);
|
||||||
|
if (index !== -1) {
|
||||||
formData.value!.skus!.splice(index, 1);
|
formData.value!.skus!.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tableHeaders = ref<{ label: string; prop: string }[]>([]); // 多属性表头
|
/** 校验 SKU 数据:保存时,每个商品规格的表单要校验。例如:销售金额最低是 0.01 */
|
||||||
|
|
||||||
/** 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种 */
|
|
||||||
function validateSku() {
|
function validateSku() {
|
||||||
validateProperty();
|
validateProperty();
|
||||||
let warningInfo = '请检查商品各行相关属性配置,';
|
let warningInfo = '请检查商品各行相关属性配置,';
|
||||||
let validate = true; // 默认通过
|
let validate = true;
|
||||||
|
|
||||||
for (const sku of formData.value!.skus!) {
|
for (const sku of formData.value!.skus!) {
|
||||||
// 作为活动组件的校验
|
|
||||||
for (const rule of props?.ruleConfig as RuleConfig[]) {
|
for (const rule of props?.ruleConfig as RuleConfig[]) {
|
||||||
const arg = getValue(sku, rule.name);
|
const value = getNestedValue(sku, rule.name);
|
||||||
if (!rule.rule(arg)) {
|
if (!rule.rule(value)) {
|
||||||
validate = false; // 只要有一个不通过则直接不通过
|
validate = false;
|
||||||
warningInfo += rule.message;
|
warningInfo += rule.message;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 只要有一个不通过则结束后续的校验
|
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
message.warning(warningInfo);
|
message.warning(warningInfo);
|
||||||
throw new Error(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[]) {
|
function generateTableData(propertyList: PropertyAndValues[]) {
|
||||||
// 构建数据结构
|
|
||||||
const propertyValues = propertyList.map((item: PropertyAndValues) =>
|
const propertyValues = propertyList.map((item: PropertyAndValues) =>
|
||||||
(item.values || []).map((v: { id: number; name: string }) => ({
|
(item.values || []).map((v: { id: number; name: string }) => ({
|
||||||
propertyId: item.id,
|
propertyId: item.id,
|
||||||
@@ -164,37 +157,32 @@ function generateTableData(propertyList: PropertyAndValues[]) {
|
|||||||
valueName: v.name,
|
valueName: v.name,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const buildSkuList = build(propertyValues);
|
const buildSkuList = build(propertyValues);
|
||||||
|
|
||||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
||||||
if (!validateData(propertyList)) {
|
if (!validateData(propertyList)) {
|
||||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
|
||||||
formData.value!.skus = [];
|
formData.value!.skus = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of buildSkuList) {
|
for (const item of buildSkuList) {
|
||||||
|
const properties = Array.isArray(item) ? item : [item];
|
||||||
const row = {
|
const row = {
|
||||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
...createEmptySku(),
|
||||||
price: 0,
|
properties,
|
||||||
marketPrice: 0,
|
|
||||||
costPrice: 0,
|
|
||||||
barCode: '',
|
|
||||||
picUrl: '',
|
|
||||||
stock: 0,
|
|
||||||
weight: 0,
|
|
||||||
volume: 0,
|
|
||||||
firstBrokeragePrice: 0,
|
|
||||||
secondBrokeragePrice: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果存在属性相同的 sku 则不做处理
|
// 如果存在属性相同的 sku 则不做处理
|
||||||
const index = formData.value!.skus!.findIndex(
|
const exists = formData.value!.skus!.some(
|
||||||
(sku: MallSpuApi.Sku) =>
|
(sku: MallSpuApi.Sku) =>
|
||||||
JSON.stringify(sku.properties) === JSON.stringify(row.properties),
|
JSON.stringify(sku.properties) === JSON.stringify(row.properties),
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
|
||||||
continue;
|
if (!exists) {
|
||||||
}
|
|
||||||
formData.value!.skus!.push(row);
|
formData.value!.skus!.push(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 生成 skus 前置校验 */
|
/** 生成 skus 前置校验 */
|
||||||
function validateData(propertyList: PropertyAndValues[]): boolean {
|
function validateData(propertyList: PropertyAndValues[]): boolean {
|
||||||
@@ -248,43 +236,33 @@ watch(
|
|||||||
if (!formData.value!.specType) {
|
if (!formData.value!.specType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果当前组件作为批量添加数据使用,则重置表数据
|
// 如果当前组件作为批量添加数据使用,则重置表数据
|
||||||
if (props.isBatch) {
|
if (props.isBatch) {
|
||||||
skuList.value = [
|
skuList.value = [createEmptySku()];
|
||||||
{
|
|
||||||
price: 0,
|
|
||||||
marketPrice: 0,
|
|
||||||
costPrice: 0,
|
|
||||||
barCode: '',
|
|
||||||
picUrl: '',
|
|
||||||
stock: 0,
|
|
||||||
weight: 0,
|
|
||||||
volume: 0,
|
|
||||||
firstBrokeragePrice: 0,
|
|
||||||
secondBrokeragePrice: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断代理对象是否为空
|
// 判断代理对象是否为空
|
||||||
if (JSON.stringify(propertyList) === '[]') {
|
if (JSON.stringify(propertyList) === '[]') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 重置表头
|
|
||||||
tableHeaders.value = [];
|
// 重置并生成表头
|
||||||
// 生成表头
|
tableHeaders.value = propertyList.map((item, index) => ({
|
||||||
propertyList.forEach((item, index) => {
|
prop: `name${index}`,
|
||||||
// name加属性项index区分属性值
|
label: item.name,
|
||||||
tableHeaders.value.push({ prop: `name${index}`, label: item.name });
|
}));
|
||||||
});
|
|
||||||
// 如果回显的 sku 属性和添加的属性一致则不处理
|
// 如果回显的 sku 属性和添加的属性一致则不处理
|
||||||
if (validateData(propertyList)) {
|
if (validateData(propertyList)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加新属性没有属性值也不做处理
|
// 添加新属性没有属性值也不做处理
|
||||||
if (propertyList.some((item) => !item.values || isEmpty(item.values))) {
|
if (propertyList.some((item) => !item.values || isEmpty(item.values))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 table 数据,即 sku 列表
|
// 生成 table 数据,即 sku 列表
|
||||||
generateTableData(propertyList);
|
generateTableData(propertyList);
|
||||||
},
|
},
|
||||||
@@ -296,17 +274,23 @@ watch(
|
|||||||
|
|
||||||
const activitySkuListRef = ref();
|
const activitySkuListRef = ref();
|
||||||
|
|
||||||
|
/** 获取 SKU 表格引用 */
|
||||||
function getSkuTableRef() {
|
function getSkuTableRef() {
|
||||||
return activitySkuListRef.value;
|
return activitySkuListRef.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ generateTableData, validateSku, getSkuTableRef });
|
defineExpose({
|
||||||
|
generateTableData,
|
||||||
|
validateSku,
|
||||||
|
getSkuTableRef,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 情况一:添加/修改 -->
|
<!-- 情况一:添加/修改 -->
|
||||||
<!-- TODO @puhui999:有可以通过 grid 来做么?主要考虑,这样不直接使用 vxe 标签,抽象程度更高; -->
|
<!-- TODO @puhui999:有可以通过 grid 来做么?主要考虑,这样不直接使用 vxe 标签,抽象程度更高; -->
|
||||||
|
<!-- TODO 还是先这样吧 🤣🤣 -->
|
||||||
<VxeTable
|
<VxeTable
|
||||||
v-if="!isDetail && !isActivityComponent"
|
v-if="!isDetail && !isActivityComponent"
|
||||||
:data="isBatch ? skuList : formData?.skus || []"
|
:data="isBatch ? skuList : formData?.skus || []"
|
||||||
|
|||||||
Reference in New Issue
Block a user