fix(mall): 修复商品 SKU 编辑回显被重置

- 修复 SPU 编辑页详情回填时误触发规格/分销类型变更,导致 SKU 价格库存被重置为 0 的问题
- 三端同步处理 web-antd、web-ele、web-antdv-next
- SKU 金额回填保持 number 类型,避免影响 InputNumber 双向绑定
- 详情页金额展示补齐两位小数,避免 0.50 显示成 0.5
- 补充浏览器自动化验证详情展示与编辑页直接保存 payload
This commit is contained in:
YunaiV
2026-07-04 21:18:10 +08:00
parent 5023510612
commit 396ba7dcfd
6 changed files with 120 additions and 57 deletions

View File

@@ -70,6 +70,11 @@ function createEmptySku(): MallSpuApi.Sku {
}; };
} }
function formatDetailMoney(value: null | number | string | undefined) {
const amount = Number(value ?? 0);
return Number.isFinite(amount) ? amount.toFixed(2) : '0.00';
}
const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]); const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]);
/** 批量添加 */ /** 批量添加 */
@@ -501,17 +506,17 @@ defineExpose({
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="销售价(元)" width="80"> <VxeColumn align="center" title="销售价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.price }} {{ formatDetailMoney(row.price) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="市场价(元)" width="80"> <VxeColumn align="center" title="市场价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.marketPrice }} {{ formatDetailMoney(row.marketPrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="成本价(元)" width="80"> <VxeColumn align="center" title="成本价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.costPrice }} {{ formatDetailMoney(row.costPrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="库存" width="80"> <VxeColumn align="center" title="库存" width="80">
@@ -532,12 +537,12 @@ defineExpose({
<template v-if="formData?.subCommissionType"> <template v-if="formData?.subCommissionType">
<VxeColumn align="center" title="一级返佣(元)" width="80"> <VxeColumn align="center" title="一级返佣(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.firstBrokeragePrice }} {{ formatDetailMoney(row.firstBrokeragePrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="二级返佣(元)" width="80"> <VxeColumn align="center" title="二级返佣(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.secondBrokeragePrice }} {{ formatDetailMoney(row.secondBrokeragePrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
</template> </template>

View File

@@ -5,7 +5,7 @@ import type {
RuleConfig, RuleConfig,
} from '#/views/mall/product/spu/components'; } from '#/views/mall/product/spu/components';
import { onMounted, ref, watch } from 'vue'; import { nextTick, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
@@ -34,6 +34,7 @@ const { closeCurrentTab } = useTabs();
const activeTabName = ref('info'); const activeTabName = ref('info');
const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用 const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用
const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情 const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情
const initializingForm = ref(false); // 详情回填时不触发 SKU 重置逻辑
const skuListRef = ref(); // 商品属性列表 Ref const skuListRef = ref(); // 商品属性列表 Ref
const formData = ref<MallSpuApi.Spu>({ const formData = ref<MallSpuApi.Spu>({
@@ -113,11 +114,20 @@ const [SkuForm, skuFormApi] = useVbenForm({
schema: useSkuFormSchema(propertyList.value, isDetail.value), schema: useSkuFormSchema(propertyList.value, isDetail.value),
showDefaultActions: false, showDefaultActions: false,
handleValuesChange: (values, fieldsChanged) => { handleValuesChange: (values, fieldsChanged) => {
if (fieldsChanged.includes('subCommissionType')) { if (initializingForm.value) {
return;
}
if (
fieldsChanged.includes('subCommissionType') &&
values.subCommissionType !== formData.value.subCommissionType
) {
formData.value.subCommissionType = values.subCommissionType; formData.value.subCommissionType = values.subCommissionType;
handleChangeSubCommissionType(); handleChangeSubCommissionType();
} }
if (fieldsChanged.includes('specType')) { if (
fieldsChanged.includes('specType') &&
values.specType !== formData.value.specType
) {
formData.value.specType = values.specType; formData.value.specType = values.specType;
handleChangeSpec(); handleChangeSpec();
} }
@@ -235,22 +245,28 @@ async function getDetail() {
// 金额转换:分转元 // 金额转换:分转元
res.skus = res.skus?.map((item) => ({ res.skus = res.skus?.map((item) => ({
...item, ...item,
price: formatToFraction(item.price), price: Number(formatToFraction(item.price)),
marketPrice: formatToFraction(item.marketPrice), marketPrice: Number(formatToFraction(item.marketPrice)),
costPrice: formatToFraction(item.costPrice), costPrice: Number(formatToFraction(item.costPrice)),
firstBrokeragePrice: formatToFraction(item.firstBrokeragePrice), firstBrokeragePrice: Number(formatToFraction(item.firstBrokeragePrice)),
secondBrokeragePrice: formatToFraction(item.secondBrokeragePrice), secondBrokeragePrice: Number(
formatToFraction(item.secondBrokeragePrice),
),
})); }));
initializingForm.value = true;
formData.value = res; formData.value = res;
// 初始化各表单值
infoFormApi.setValues(res).then();
skuFormApi.setValues(res).then();
deliveryFormApi.setValues(res).then();
descriptionFormApi.setValues(res).then();
otherFormApi.setValues(res).then();
// 将 SKU 的属性,整理成 PropertyAndValues 数组 // 将 SKU 的属性,整理成 PropertyAndValues 数组
propertyList.value = getPropertyList(formData.value); propertyList.value = getPropertyList(formData.value);
// 初始化各表单值
void infoFormApi.setValues(res);
void skuFormApi.setValues(res);
void deliveryFormApi.setValues(res);
void descriptionFormApi.setValues(res);
void otherFormApi.setValues(res);
await nextTick();
await nextTick();
} finally { } finally {
initializingForm.value = false;
formLoading.value = false; formLoading.value = false;
} }
} }

View File

@@ -70,6 +70,11 @@ function createEmptySku(): MallSpuApi.Sku {
}; };
} }
function formatDetailMoney(value: null | number | string | undefined) {
const amount = Number(value ?? 0);
return Number.isFinite(amount) ? amount.toFixed(2) : '0.00';
}
const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]); const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]);
/** 批量添加 */ /** 批量添加 */
@@ -501,17 +506,17 @@ defineExpose({
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="销售价(元)" width="80"> <VxeColumn align="center" title="销售价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.price }} {{ formatDetailMoney(row.price) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="市场价(元)" width="80"> <VxeColumn align="center" title="市场价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.marketPrice }} {{ formatDetailMoney(row.marketPrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="成本价(元)" width="80"> <VxeColumn align="center" title="成本价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.costPrice }} {{ formatDetailMoney(row.costPrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="库存" width="80"> <VxeColumn align="center" title="库存" width="80">
@@ -532,12 +537,12 @@ defineExpose({
<template v-if="formData?.subCommissionType"> <template v-if="formData?.subCommissionType">
<VxeColumn align="center" title="一级返佣(元)" width="80"> <VxeColumn align="center" title="一级返佣(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.firstBrokeragePrice }} {{ formatDetailMoney(row.firstBrokeragePrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="二级返佣(元)" width="80"> <VxeColumn align="center" title="二级返佣(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.secondBrokeragePrice }} {{ formatDetailMoney(row.secondBrokeragePrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
</template> </template>

View File

@@ -5,7 +5,7 @@ import type {
RuleConfig, RuleConfig,
} from '#/views/mall/product/spu/components'; } from '#/views/mall/product/spu/components';
import { onMounted, ref, watch } from 'vue'; import { nextTick, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
@@ -34,6 +34,7 @@ const { closeCurrentTab } = useTabs();
const activeTabName = ref('info'); const activeTabName = ref('info');
const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用 const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用
const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情 const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情
const initializingForm = ref(false); // 详情回填时不触发 SKU 重置逻辑
const skuListRef = ref(); // 商品属性列表 Ref const skuListRef = ref(); // 商品属性列表 Ref
const formData = ref<MallSpuApi.Spu>({ const formData = ref<MallSpuApi.Spu>({
@@ -113,11 +114,20 @@ const [SkuForm, skuFormApi] = useVbenForm({
schema: useSkuFormSchema(propertyList.value, isDetail.value), schema: useSkuFormSchema(propertyList.value, isDetail.value),
showDefaultActions: false, showDefaultActions: false,
handleValuesChange: (values, fieldsChanged) => { handleValuesChange: (values, fieldsChanged) => {
if (fieldsChanged.includes('subCommissionType')) { if (initializingForm.value) {
return;
}
if (
fieldsChanged.includes('subCommissionType') &&
values.subCommissionType !== formData.value.subCommissionType
) {
formData.value.subCommissionType = values.subCommissionType; formData.value.subCommissionType = values.subCommissionType;
handleChangeSubCommissionType(); handleChangeSubCommissionType();
} }
if (fieldsChanged.includes('specType')) { if (
fieldsChanged.includes('specType') &&
values.specType !== formData.value.specType
) {
formData.value.specType = values.specType; formData.value.specType = values.specType;
handleChangeSpec(); handleChangeSpec();
} }
@@ -235,22 +245,28 @@ async function getDetail() {
// 金额转换:分转元 // 金额转换:分转元
res.skus = res.skus?.map((item) => ({ res.skus = res.skus?.map((item) => ({
...item, ...item,
price: formatToFraction(item.price), price: Number(formatToFraction(item.price)),
marketPrice: formatToFraction(item.marketPrice), marketPrice: Number(formatToFraction(item.marketPrice)),
costPrice: formatToFraction(item.costPrice), costPrice: Number(formatToFraction(item.costPrice)),
firstBrokeragePrice: formatToFraction(item.firstBrokeragePrice), firstBrokeragePrice: Number(formatToFraction(item.firstBrokeragePrice)),
secondBrokeragePrice: formatToFraction(item.secondBrokeragePrice), secondBrokeragePrice: Number(
formatToFraction(item.secondBrokeragePrice),
),
})); }));
initializingForm.value = true;
formData.value = res; formData.value = res;
// 初始化各表单值
infoFormApi.setValues(res).then();
skuFormApi.setValues(res).then();
deliveryFormApi.setValues(res).then();
descriptionFormApi.setValues(res).then();
otherFormApi.setValues(res).then();
// 将 SKU 的属性,整理成 PropertyAndValues 数组 // 将 SKU 的属性,整理成 PropertyAndValues 数组
propertyList.value = getPropertyList(formData.value); propertyList.value = getPropertyList(formData.value);
// 初始化各表单值
void infoFormApi.setValues(res);
void skuFormApi.setValues(res);
void deliveryFormApi.setValues(res);
void descriptionFormApi.setValues(res);
void otherFormApi.setValues(res);
await nextTick();
await nextTick();
} finally { } finally {
initializingForm.value = false;
formLoading.value = false; formLoading.value = false;
} }
} }

View File

@@ -76,6 +76,11 @@ function createEmptySku(): MallSpuApi.Sku {
}; };
} }
function formatDetailMoney(value: null | number | string | undefined) {
const amount = Number(value ?? 0);
return Number.isFinite(amount) ? amount.toFixed(2) : '0.00';
}
const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]); const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]);
/** 批量添加 */ /** 批量添加 */
@@ -536,17 +541,17 @@ defineExpose({
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="销售价(元)" width="80"> <VxeColumn align="center" title="销售价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.price }} {{ formatDetailMoney(row.price) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="市场价(元)" width="80"> <VxeColumn align="center" title="市场价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.marketPrice }} {{ formatDetailMoney(row.marketPrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="成本价(元)" width="80"> <VxeColumn align="center" title="成本价(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.costPrice }} {{ formatDetailMoney(row.costPrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="库存" width="80"> <VxeColumn align="center" title="库存" width="80">
@@ -567,12 +572,12 @@ defineExpose({
<template v-if="formData?.subCommissionType"> <template v-if="formData?.subCommissionType">
<VxeColumn align="center" title="一级返佣(元)" width="80"> <VxeColumn align="center" title="一级返佣(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.firstBrokeragePrice }} {{ formatDetailMoney(row.firstBrokeragePrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
<VxeColumn align="center" title="二级返佣(元)" width="80"> <VxeColumn align="center" title="二级返佣(元)" width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.secondBrokeragePrice }} {{ formatDetailMoney(row.secondBrokeragePrice) }}
</template> </template>
</VxeColumn> </VxeColumn>
</template> </template>

View File

@@ -5,7 +5,7 @@ import type {
RuleConfig, RuleConfig,
} from '#/views/mall/product/spu/components'; } from '#/views/mall/product/spu/components';
import { onMounted, ref, watch } from 'vue'; import { nextTick, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
@@ -34,6 +34,7 @@ const { closeCurrentTab } = useTabs();
const activeTabName = ref('info'); const activeTabName = ref('info');
const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用 const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用
const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情 const isDetail = ref(name === 'ProductSpuDetail'); // 是否查看详情
const initializingForm = ref(false); // 详情回填时不触发 SKU 重置逻辑
const skuListRef = ref(); // 商品属性列表 Ref const skuListRef = ref(); // 商品属性列表 Ref
const formData = ref<MallSpuApi.Spu>({ const formData = ref<MallSpuApi.Spu>({
@@ -113,11 +114,20 @@ const [SkuForm, skuFormApi] = useVbenForm({
schema: useSkuFormSchema(propertyList.value, isDetail.value), schema: useSkuFormSchema(propertyList.value, isDetail.value),
showDefaultActions: false, showDefaultActions: false,
handleValuesChange: (values, fieldsChanged) => { handleValuesChange: (values, fieldsChanged) => {
if (fieldsChanged.includes('subCommissionType')) { if (initializingForm.value) {
return;
}
if (
fieldsChanged.includes('subCommissionType') &&
values.subCommissionType !== formData.value.subCommissionType
) {
formData.value.subCommissionType = values.subCommissionType; formData.value.subCommissionType = values.subCommissionType;
handleChangeSubCommissionType(); handleChangeSubCommissionType();
} }
if (fieldsChanged.includes('specType')) { if (
fieldsChanged.includes('specType') &&
values.specType !== formData.value.specType
) {
formData.value.specType = values.specType; formData.value.specType = values.specType;
handleChangeSpec(); handleChangeSpec();
} }
@@ -235,22 +245,28 @@ async function getDetail() {
// 金额转换:分转元 // 金额转换:分转元
res.skus = res.skus?.map((item) => ({ res.skus = res.skus?.map((item) => ({
...item, ...item,
price: formatToFraction(item.price), price: Number(formatToFraction(item.price)),
marketPrice: formatToFraction(item.marketPrice), marketPrice: Number(formatToFraction(item.marketPrice)),
costPrice: formatToFraction(item.costPrice), costPrice: Number(formatToFraction(item.costPrice)),
firstBrokeragePrice: formatToFraction(item.firstBrokeragePrice), firstBrokeragePrice: Number(formatToFraction(item.firstBrokeragePrice)),
secondBrokeragePrice: formatToFraction(item.secondBrokeragePrice), secondBrokeragePrice: Number(
formatToFraction(item.secondBrokeragePrice),
),
})); }));
initializingForm.value = true;
formData.value = res; formData.value = res;
// 初始化各表单值
infoFormApi.setValues(res).then();
skuFormApi.setValues(res).then();
deliveryFormApi.setValues(res).then();
descriptionFormApi.setValues(res).then();
otherFormApi.setValues(res).then();
// 将 SKU 的属性,整理成 PropertyAndValues 数组 // 将 SKU 的属性,整理成 PropertyAndValues 数组
propertyList.value = getPropertyList(formData.value); propertyList.value = getPropertyList(formData.value);
// 初始化各表单值
void infoFormApi.setValues(res);
void skuFormApi.setValues(res);
void deliveryFormApi.setValues(res);
void descriptionFormApi.setValues(res);
void otherFormApi.setValues(res);
await nextTick();
await nextTick();
} finally { } finally {
initializingForm.value = false;
formLoading.value = false; formLoading.value = false;
} }
} }