feat:【antd】【erp 系统】sale/out 部分重构(form 部分重构)

This commit is contained in:
YunaiV
2025-10-04 09:52:40 +08:00
parent c8c4693983
commit c603b7002c
5 changed files with 155 additions and 108 deletions

View File

@@ -42,7 +42,7 @@ const [Form, formApi] = useVbenForm({
},
wrapperClass: 'grid-cols-3',
layout: 'vertical',
schema: useFormSchema(),
schema: useFormSchema(formType.value),
showDefaultActions: false,
handleValuesChange: (values, changedFields) => {
// 目的:同步到 item-form 组件,触发整体的价格计算
@@ -95,7 +95,6 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as ErpSaleOrderApi.SaleOrder;
data.items = formData.value?.items;
try {
await (formType.value === 'create'
? createSaleOrder(data)

View File

@@ -255,6 +255,10 @@ onMounted(async () => {
/>
<span v-else>{{ row.productPrice || '-' }}</span>
</template>
<template #remark="{ row }">
<Input v-if="!disabled" v-model:value="row.remark" class="w-full" />
<span v-else>{{ row.remark || '-' }}</span>
</template>
<template #taxPercent="{ row }">
<InputNumber
v-if="!disabled"
@@ -266,10 +270,6 @@ onMounted(async () => {
/>
<span v-else>{{ row.taxPercent || '-' }}</span>
</template>
<template #remark="{ row }">
<Input v-if="!disabled" v-model:value="row.remark" class="w-full" />
<span v-else>{{ row.remark || '-' }}</span>
</template>
<template #actions="{ row }">
<TableAction
v-if="!disabled"

View File

@@ -33,6 +33,30 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
disabled: true,
},
},
{
fieldName: 'outTime',
label: '出库时间',
component: 'DatePicker',
componentProps: {
disabled: formType === 'detail',
placeholder: '选择出库时间',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
},
rules: 'required',
},
{
fieldName: 'orderNo',
label: '关联订单',
component: 'Input',
formItemClass: 'col-span-1',
rules: 'required',
componentProps: {
placeholder: '请选择关联订单',
disabled: formType === 'detail',
},
},
{
fieldName: 'customerId',
label: '客户',
@@ -51,29 +75,20 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
rules: 'required',
},
{
fieldName: 'orderNo',
label: '关联订单',
component: 'Input',
formItemClass: 'col-span-1',
rules: 'required',
fieldName: 'saleUserId',
label: '销售人员',
component: 'ApiSelect',
componentProps: {
placeholder: '请选择关联订单',
disabled: formType === 'detail',
placeholder: '请选择销售人员',
allowClear: true,
showSearch: true,
api: getSimpleUserList,
fieldNames: {
label: 'nickname',
value: 'id',
},
},
},
{
fieldName: 'outTime',
label: '出库时间',
component: 'DatePicker',
componentProps: {
disabled: formType === 'detail',
placeholder: '选择出库时间',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
},
rules: 'required',
},
{
fieldName: 'remark',
label: '备注',
@@ -128,7 +143,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
},
{
fieldName: 'discountPrice',
label: '款优惠',
label: '款优惠',
component: 'InputNumber',
componentProps: {
placeholder: '付款优惠',
@@ -182,6 +197,7 @@ export function useFormSchema(formType: string): VbenFormSchema[] {
componentProps: {
precision: 2,
min: 0,
disabled: true
},
rules: z.number().min(0).optional(),
},
@@ -220,14 +236,10 @@ export function useFormItemColumns(): VxeTableGridOptions['columns'] {
minWidth: 80,
},
{
field: 'totalCount',
title: '原数量',
minWidth: 120,
},
{
field: 'outCount',
title: '已出库数量',
minWidth: 120,
field: 'remark',
title: '备注',
minWidth: 150,
slots: { default: 'remark' },
},
{
field: 'count',
@@ -254,7 +266,8 @@ export function useFormItemColumns(): VxeTableGridOptions['columns'] {
fixed: 'right',
field: 'taxPercent',
title: '税率(%)',
minWidth: 100,
minWidth: 105,
slots: { default: 'taxPercent' },
},
{
fixed: 'right',
@@ -270,6 +283,12 @@ export function useFormItemColumns(): VxeTableGridOptions['columns'] {
minWidth: 120,
formatter: 'formatAmount2',
},
{
title: '操作',
width: 50,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@@ -5,6 +5,7 @@ import type { ErpSaleOutApi } from '#/api/erp/sale/out';
import { computed, nextTick, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { message } from 'ant-design-vue';
@@ -45,11 +46,14 @@ const formData = ref<
const formType = ref(''); // 表单类型:'create' | 'edit' | 'detail'
const itemFormRef = ref<InstanceType<typeof ItemForm>>();
const getTitle = computed(() => {
if (formType.value === 'create') return '添加销售出库';
if (formType.value === 'update') return '编辑销售出库';
return '销售出库详情';
});
/* eslint-disable unicorn/no-nested-ternary */
const getTitle = computed(() =>
formType.value === 'create'
? $t('ui.actionTitle.create', ['销售出库'])
: formType.value === 'edit'
? $t('ui.actionTitle.edit', ['销售出库'])
: '销售出库详情',
);
const [Form, formApi] = useVbenForm({
commonConfig: {
@@ -68,7 +72,7 @@ const [Form, formApi] = useVbenForm({
formData.value.totalPrice =
(formData.value.discountedPrice || 0) +
(formData.value.otherPrice || 0);
// TODO @芋艿:是否需要,去掉?
formApi.setValues(formData.value, false);
}
},
@@ -79,11 +83,13 @@ const handleUpdateItems = async (items: ErpSaleOutApi.SaleOutItem[]) => {
if (formData.value) {
const data = await formApi.getValues();
formData.value = { ...data, items };
// TODO @芋艿:是否需要,去掉?
await formApi.setValues(formData.value, false);
}
};
/** 选择销售订单 */
// TODO @AI不确定
const handleUpdateOrder = (order: ErpSaleOrderApi.SaleOrder) => {
formData.value = {
...formData.value,
@@ -109,72 +115,61 @@ const handleUpdateOrder = (order: ErpSaleOrderApi.SaleOrder) => {
formApi.setValues(formData.value, false);
};
watch(
() => formData.value.items!,
(newItems: ErpSaleOutApi.SaleOutItem[]) => {
if (newItems && newItems.length > 0) {
// 计算每个产品的总价、税额和总价
newItems.forEach((item) => {
item.totalProductPrice = (item.productPrice || 0) * (item.count || 0);
item.taxPrice =
(item.totalProductPrice || 0) * ((item.taxPercent || 0) / 100);
item.totalPrice = (item.totalProductPrice || 0) + (item.taxPrice || 0);
});
// 计算总价
formData.value.totalPrice = newItems.reduce((sum, item) => {
return sum + (item.totalProductPrice || 0) + (item.taxPrice || 0);
}, 0);
} else {
formData.value.totalPrice = 0;
}
// 优惠金额
formData.value.discountPrice =
((formData.value.totalPrice || 0) *
(formData.value.discountPercent || 0)) /
100;
// 优惠后价格
formData.value.discountedPrice =
formData.value.totalPrice - formData.value.discountPrice;
// TODO @AI不确定
// watch(
// () => formData.value.items!,
// (newItems: ErpSaleOutApi.SaleOutItem[]) => {
// if (newItems && newItems.length > 0) {
// // 计算每个产品的总价、税额和总价
// newItems.forEach((item) => {
// item.totalProductPrice = (item.productPrice || 0) * (item.count || 0);
// item.taxPrice =
// (item.totalProductPrice || 0) * ((item.taxPercent || 0) / 100);
// item.totalPrice = (item.totalProductPrice || 0) + (item.taxPrice || 0);
// });
// // 计算总价
// formData.value.totalPrice = newItems.reduce((sum, item) => {
// return sum + (item.totalProductPrice || 0) + (item.taxPrice || 0);
// }, 0);
// } else {
// formData.value.totalPrice = 0;
// }
// // 优惠金额
// formData.value.discountPrice =
// ((formData.value.totalPrice || 0) *
// (formData.value.discountPercent || 0)) /
// 100;
// // 优惠后价格
// formData.value.discountedPrice =
// formData.value.totalPrice - formData.value.discountPrice;
//
// // 计算最终价格(包含其他费用)
// formData.value.totalPrice =
// formData.value.discountedPrice + (formData.value.otherPrice || 0);
// formApi.setValues(formData.value, false);
// },
// { immediate: true },
// );
// 计算最终价格(包含其他费用)
formData.value.totalPrice =
formData.value.discountedPrice + (formData.value.otherPrice || 0);
formApi.setValues(formData.value, false);
},
{ immediate: true },
);
/**
* 创建或更新销售出库
*/
/** 创建或更新销售出库 */
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
await nextTick();
const itemFormInstance = Array.isArray(itemFormRef.value)
? itemFormRef.value[0]
: itemFormRef.value;
if (itemFormInstance && typeof itemFormInstance.validate === 'function') {
try {
const isValid = await itemFormInstance.validate();
if (!isValid) {
message.error('子表单验证失败');
return;
}
} catch (error: any) {
message.error(error.message || '子表单验证失败');
return;
}
} else {
message.error('子表单验证方法不存在');
try {
itemFormInstance.validate();
} catch (error: any) {
message.error(error.message || '子表单验证失败');
return;
}
// 验证产品清单不能为空
// TODO @芋艿:需要校验么?
if (!formData.value?.items || formData.value.items.length === 0) {
message.error('产品清单不能为空,请至少添加一个产品');
return;
@@ -195,7 +190,7 @@ const [Modal, modalApi] = useVbenModal({
// 关闭并提示
await modalApi.close();
emit('success');
message.success(formType.value === 'create' ? '新增成功' : '更新成功');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
@@ -278,27 +273,21 @@ defineExpose({ modalApi });
<template>
<Modal
v-bind="$attrs"
:title="getTitle"
class="w-2/3"
:closable="true"
:mask-closable="true"
class="w-3/4"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">
<template #items="slotProps">
<template #items>
<ItemForm
v-bind="slotProps"
ref="itemFormRef"
class="w-full"
:items="formData?.items ?? []"
:disabled="formType === 'detail'"
@update:items="handleUpdateItems"
/>
</template>
<template #orderNo="slotProps">
<template #orderNo>
<SelectSaleOrderForm
v-bind="slotProps"
:order-no="formData?.orderNo"
@update:order="handleUpdateOrder"
/>

View File

@@ -1,13 +1,13 @@
<script lang="ts" setup>
import type { ErpSaleOutApi } from '#/api/erp/sale/out';
import type {ErpSaleOutApi, SaleOutItem} from '#/api/erp/sale/out';
import { nextTick, onMounted, ref, watch } from 'vue';
import { erpPriceMultiply } from '@vben/utils';
import { InputNumber, Select } from 'ant-design-vue';
import {Input, InputNumber, Select} from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {TableAction, useVbenVxeGrid} from '#/adapter/vxe-table';
import { getProductSimpleList } from '#/api/erp/product/product';
import { getWarehouseStockCount } from '#/api/erp/stock/stock';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
@@ -186,6 +186,16 @@ const init = (items: ErpSaleOutApi.SaleOutItem[] | undefined): void => {
});
};
/** 处理删除 */
function handleDelete(row: ErpSaleOutApi.SaleOutItem) {
const index = tableData.value.findIndex((item) => item.id === row.id);
if (index !== -1) {
tableData.value.splice(index, 1);
}
// 通知父组件更新
emit('update:items', [...tableData.value]);
}
defineExpose({
validate,
getData,
@@ -218,7 +228,6 @@ defineExpose({
show-search
/>
</template>
<template #count="{ row }">
<InputNumber
v-if="!disabled"
@@ -238,6 +247,37 @@ defineExpose({
@change="handlePriceChange(row)"
/>
</template>
<template #remark="{ row }">
<Input v-if="!disabled" v-model:value="row.remark" class="w-full" />
<span v-else>{{ row.remark || '-' }}</span>
</template>
<template #taxPercent="{ row }">
<InputNumber
v-if="!disabled"
v-model:value="row.taxPercent"
:min="0"
:max="100"
:precision="2"
@change="handlePriceChange(row)"
/>
<span v-else>{{ row.taxPercent || '-' }}</span>
</template>
<template #actions="{ row }">
<TableAction
v-if="!disabled"
:actions="[
{
label: '删除',
type: 'link',
danger: true,
popConfirm: {
title: '确认删除该产品吗?',
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
<template #bottom>
<div class="border-border bg-muted mt-2 rounded border p-2">