feat(@vben/web-antd): erp-采购订单表单逻辑
- 使用 handleValuesChange - 优化 discountPrice 和 totalPrice 的计算逻辑 - 改进子表单的验证和数据处理 - 统一数值格式化处理 - 优化表格数据的更新方式pull/181/head
parent
e319888240
commit
8a7239ce24
|
@ -66,7 +66,7 @@ function handleUpdateValue(row: any) {
|
||||||
} else {
|
} else {
|
||||||
tableData.value[index] = row;
|
tableData.value[index] = row;
|
||||||
}
|
}
|
||||||
emit('update:products', tableData.value);
|
emit('update:products', [...tableData.value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格配置 */
|
/** 表格配置 */
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@vben/utils';
|
import { erpCountInputFormatter, erpPriceInputFormatter } from '@vben/utils';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
import { getAccountSimpleList } from '#/api/erp/finance/account';
|
import { getAccountSimpleList } from '#/api/erp/finance/account';
|
||||||
import { getProductSimpleList } from '#/api/erp/product/product';
|
import { getProductSimpleList } from '#/api/erp/product/product';
|
||||||
import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
|
import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
|
||||||
|
@ -96,11 +97,11 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
formatter: erpPriceInputFormatter,
|
|
||||||
style: { width: '100%' },
|
style: { width: '100%' },
|
||||||
},
|
},
|
||||||
fieldName: 'discountPercent',
|
fieldName: 'discountPercent',
|
||||||
label: '优惠率(%)',
|
label: '优惠率(%)',
|
||||||
|
rules: z.number().min(0).optional(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
|
@ -146,11 +147,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入支付订金',
|
placeholder: '请输入支付订金',
|
||||||
precision: 2,
|
precision: 2,
|
||||||
formatter: erpPriceInputFormatter,
|
|
||||||
style: { width: '100%' },
|
style: { width: '100%' },
|
||||||
|
min: 0,
|
||||||
},
|
},
|
||||||
fieldName: 'depositPrice',
|
fieldName: 'depositPrice',
|
||||||
label: '支付订金',
|
label: '支付订金',
|
||||||
|
rules: z.number().min(0).optional(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,19 @@ import { usePurchaseOrderItemTableColumns } from '../data';
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
items: () => [],
|
items: () => [],
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
discountPercent: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:items']);
|
const emit = defineEmits([
|
||||||
|
'update:items',
|
||||||
|
'update:discount-price',
|
||||||
|
'update:total-price',
|
||||||
|
]);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
items?: ErpPurchaseOrderApi.PurchaseOrderItem[];
|
items?: ErpPurchaseOrderApi.PurchaseOrderItem[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
discountPercent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableData = ref<ErpPurchaseOrderApi.PurchaseOrderItem[]>([]);
|
const tableData = ref<ErpPurchaseOrderApi.PurchaseOrderItem[]>([]);
|
||||||
|
@ -62,7 +68,8 @@ watch(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await nextTick();
|
await nextTick();
|
||||||
tableData.value = items;
|
tableData.value = [...items];
|
||||||
|
await nextTick();
|
||||||
gridApi.grid.reloadData(tableData.value);
|
gridApi.grid.reloadData(tableData.value);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -70,6 +77,30 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 计算 discountPrice、totalPrice 价格 */
|
||||||
|
watch(
|
||||||
|
() => [tableData.value, props.discountPercent],
|
||||||
|
() => {
|
||||||
|
if (!tableData.value || tableData.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const totalPrice = tableData.value.reduce(
|
||||||
|
(prev, curr) => prev + (curr.totalPrice || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const discountPrice =
|
||||||
|
props.discountPercent === null
|
||||||
|
? 0
|
||||||
|
: erpPriceMultiply(totalPrice, props.discountPercent / 100);
|
||||||
|
const finalTotalPrice = totalPrice - discountPrice;
|
||||||
|
|
||||||
|
// 发送计算结果给父组件
|
||||||
|
emit('update:discount-price', discountPrice);
|
||||||
|
emit('update:total-price', finalTotalPrice);
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
productOptions.value = await getProductSimpleList();
|
productOptions.value = await getProductSimpleList();
|
||||||
|
@ -81,6 +112,11 @@ function handleAdd() {
|
||||||
|
|
||||||
function handleDelete(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
|
function handleDelete(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
|
||||||
gridApi.grid.remove(row);
|
gridApi.grid.remove(row);
|
||||||
|
const index = tableData.value.findIndex((item) => item.id === row.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
tableData.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
emit('update:items', [...tableData.value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleProductChange(productId: any, row: any) {
|
async function handleProductChange(productId: any, row: any) {
|
||||||
|
@ -121,7 +157,7 @@ function handleUpdateValue(row: any) {
|
||||||
} else {
|
} else {
|
||||||
tableData.value[index] = row;
|
tableData.value[index] = row;
|
||||||
}
|
}
|
||||||
emit('update:items', tableData.value);
|
emit('update:items', [...tableData.value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSummaries = (): {
|
const getSummaries = (): {
|
||||||
|
@ -131,7 +167,7 @@ const getSummaries = (): {
|
||||||
totalPrice: number;
|
totalPrice: number;
|
||||||
totalProductPrice: number;
|
totalProductPrice: number;
|
||||||
} => {
|
} => {
|
||||||
const sums = {
|
return {
|
||||||
productName: '合计',
|
productName: '合计',
|
||||||
count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
|
count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
|
||||||
totalProductPrice: tableData.value.reduce(
|
totalProductPrice: tableData.value.reduce(
|
||||||
|
@ -147,20 +183,21 @@ const getSummaries = (): {
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
return sums;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const validate = async (): Promise<void> => {
|
const validate = async (): Promise<void> => {
|
||||||
for (let i = 0; i < tableData.value.length; i++) {
|
for (let i = 0; i < tableData.value.length; i++) {
|
||||||
const item = tableData.value[i];
|
const item = tableData.value[i];
|
||||||
if (!item.productId) {
|
if (item) {
|
||||||
throw new Error(`第 ${i + 1} 行:产品不能为空`);
|
if (!item.productId) {
|
||||||
}
|
throw new Error(`第 ${i + 1} 行:产品不能为空`);
|
||||||
if (!item.count || item.count <= 0) {
|
}
|
||||||
throw new Error(`第 ${i + 1} 行:产品数量不能为空`);
|
if (!item.count || item.count <= 0) {
|
||||||
}
|
throw new Error(`第 ${i + 1} 行:产品数量不能为空`);
|
||||||
if (!item.productPrice || item.productPrice <= 0) {
|
}
|
||||||
throw new Error(`第 ${i + 1} 行:产品单价不能为空`);
|
if (!item.productPrice || item.productPrice <= 0) {
|
||||||
|
throw new Error(`第 ${i + 1} 行:产品单价不能为空`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -169,8 +206,10 @@ const getData = (): ErpPurchaseOrderApi.PurchaseOrderItem[] => tableData.value;
|
||||||
const init = (
|
const init = (
|
||||||
items: ErpPurchaseOrderApi.PurchaseOrderItem[] | undefined,
|
items: ErpPurchaseOrderApi.PurchaseOrderItem[] | undefined,
|
||||||
): void => {
|
): void => {
|
||||||
tableData.value = items || [];
|
tableData.value = items ? [...items] : [];
|
||||||
gridApi.grid.reloadData(tableData.value);
|
nextTick(() => {
|
||||||
|
gridApi.grid.reloadData(tableData.value);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ validate, getData, init });
|
defineExpose({ validate, getData, init });
|
||||||
|
@ -232,7 +271,6 @@ defineExpose({ validate, getData, init });
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
<!-- 合计行 -->
|
|
||||||
<div class="border-border bg-muted mt-2 rounded border p-2">
|
<div class="border-border bg-muted mt-2 rounded border p-2">
|
||||||
<div class="text-muted-foreground flex justify-between text-sm">
|
<div class="text-muted-foreground flex justify-between text-sm">
|
||||||
<span class="text-foreground font-medium">合计:</span>
|
<span class="text-foreground font-medium">合计:</span>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
|
import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
|
||||||
|
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { erpPriceMultiply, formatDateTime } from '@vben/utils';
|
import { formatDateTime } from '@vben/utils';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
@ -40,52 +40,56 @@ const [Form, formApi] = useVbenForm({
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
handleValuesChange: (values, changedFields) => {
|
||||||
|
if (formData.value && changedFields.includes('discountPercent')) {
|
||||||
|
formData.value.discountPercent = values.discountPercent;
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 计算 discountPrice、totalPrice 价格 */
|
|
||||||
watch(
|
|
||||||
() => [formData.value?.items, formData.value?.discountPercent],
|
|
||||||
() => {
|
|
||||||
if (!formData.value?.items) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 计算 discountPrice 价格
|
|
||||||
let totalPrice = 0;
|
|
||||||
formData.value.items.forEach((item) => {
|
|
||||||
totalPrice += item.totalPrice || 0;
|
|
||||||
});
|
|
||||||
formData.value.discountPrice = erpPriceMultiply(
|
|
||||||
totalPrice,
|
|
||||||
formData.value.discountPercent / 100,
|
|
||||||
);
|
|
||||||
formData.value.totalPrice = totalPrice - formData.value.discountPrice;
|
|
||||||
// 更新表单值
|
|
||||||
formApi.setValues({
|
|
||||||
discountPrice: formData.value.discountPrice,
|
|
||||||
totalPrice: formData.value.totalPrice,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUpdateItems = (items: ErpPurchaseOrderApi.PurchaseOrderItem[]) => {
|
const handleUpdateItems = (items: ErpPurchaseOrderApi.PurchaseOrderItem[]) => {
|
||||||
|
formData.value = modalApi.getData<ErpPurchaseOrderApi.PurchaseOrder>();
|
||||||
if (formData.value) {
|
if (formData.value) {
|
||||||
formData.value.items = items;
|
formData.value.items = items;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdateDiscountPrice = (discountPrice: number) => {
|
||||||
|
if (formData.value) {
|
||||||
|
formData.value.discountPrice = discountPrice;
|
||||||
|
formApi.setValues({
|
||||||
|
discountPrice: formData.value.discountPrice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateTotalPrice = (totalPrice: number) => {
|
||||||
|
if (formData.value) {
|
||||||
|
formData.value.totalPrice = totalPrice;
|
||||||
|
formApi.setValues({
|
||||||
|
totalPrice: formData.value.totalPrice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await itemFormRef.value?.validate();
|
// 验证子表单
|
||||||
|
if (itemFormRef.value && typeof itemFormRef.value.validate === 'function') {
|
||||||
|
await itemFormRef.value.validate();
|
||||||
|
}
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const data =
|
const data =
|
||||||
(await formApi.getValues()) as ErpPurchaseOrderApi.PurchaseOrder;
|
(await formApi.getValues()) as ErpPurchaseOrderApi.PurchaseOrder;
|
||||||
data.items = itemFormRef.value?.getData();
|
data.items =
|
||||||
|
itemFormRef.value && typeof itemFormRef.value.getData === 'function'
|
||||||
|
? itemFormRef.value.getData()
|
||||||
|
: [];
|
||||||
try {
|
try {
|
||||||
await (formType.value === 'create'
|
await (formType.value === 'create'
|
||||||
? createPurchaseOrder(data)
|
? createPurchaseOrder(data)
|
||||||
|
@ -140,9 +144,12 @@ defineExpose({ modalApi });
|
||||||
v-bind="slotProps"
|
v-bind="slotProps"
|
||||||
ref="itemFormRef"
|
ref="itemFormRef"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:items="formData?.items || []"
|
:items="formData?.items ?? []"
|
||||||
:disabled="formType === 'detail'"
|
:disabled="formType === 'detail'"
|
||||||
|
:discount-percent="formData?.discountPercent ?? 0"
|
||||||
@update:items="handleUpdateItems"
|
@update:items="handleUpdateItems"
|
||||||
|
@update:discount-price="handleUpdateDiscountPrice"
|
||||||
|
@update:total-price="handleUpdateTotalPrice"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
Loading…
Reference in New Issue