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

This commit is contained in:
YunaiV
2025-10-04 10:56:40 +08:00
parent 86e701ba3a
commit 40b5d952eb
2 changed files with 104 additions and 186 deletions

View File

@@ -2,7 +2,7 @@
import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
import type { ErpSaleOutApi } from '#/api/erp/sale/out';
import { computed, nextTick, ref } from 'vue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
@@ -110,7 +110,6 @@ const handleUpdateTotalPrice = (totalPrice: number) => {
};
/** 选择销售订单 */
// TODO @AI不确定
const handleUpdateOrder = (order: ErpSaleOrderApi.SaleOrder) => {
formData.value = {
...formData.value,
@@ -132,8 +131,6 @@ const handleUpdateOrder = (order: ErpSaleOrderApi.SaleOrder) => {
formData.value.items = order.items!.filter(
(item) => item.count && item.count > 0,
) as ErpSaleOutApi.SaleOutItem[];
formApi.setValues(formData.value, false);
};
/** 创建或更新销售出库 */
@@ -184,64 +181,26 @@ const [Modal, modalApi] = useVbenModal({
otherPrice: 0,
items: [],
};
// await formApi.setValues(formData.value, false);
return;
}
// 加载数据
const data = modalApi.getData<{ id?: number; type: string }>();
if (!data) {
return;
}
formType.value = data.type;
formApi.setDisabled(formType.value === 'detail');
formApi.updateSchema(useFormSchema(formType.value));
if (!data.id) {
// 初始化空的表单数据
formData.value = {
id: undefined,
no: undefined,
accountId: undefined,
outTime: undefined,
remark: undefined,
fileUrl: undefined,
discountPercent: 0,
customerId: undefined,
discountPrice: 0,
totalPrice: 0,
otherPrice: 0,
items: [],
} as unknown as ErpSaleOutApi.SaleOut;
await nextTick();
const itemFormInstance = Array.isArray(itemFormRef.value)
? itemFormRef.value[0]
: itemFormRef.value;
if (itemFormInstance && typeof itemFormInstance.init === 'function') {
itemFormInstance.init([]);
}
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getSaleOut(data.id);
// 设置到 values
await formApi.setValues(formData.value, false);
// 初始化子表单
await nextTick();
const itemFormInstance = Array.isArray(itemFormRef.value)
? itemFormRef.value[0]
: itemFormRef.value;
if (itemFormInstance && typeof itemFormInstance.init === 'function') {
itemFormInstance.init(formData.value.items || []);
}
} finally {
modalApi.unlock();
}
},
});
defineExpose({ modalApi });
</script>
<template>

View File

@@ -1,13 +1,17 @@
<script lang="ts" setup>
import type {ErpSaleOutApi, SaleOutItem} from '#/api/erp/sale/out';
import type { ErpSaleOutApi } from '#/api/erp/sale/out';
import { nextTick, onMounted, ref, watch } from 'vue';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { erpPriceMultiply } from '@vben/utils';
import {
erpCountInputFormatter,
erpPriceInputFormatter,
erpPriceMultiply,
} from '@vben/utils';
import {Input, InputNumber, Select} from 'ant-design-vue';
import { Input, InputNumber, Select } from 'ant-design-vue';
import {TableAction, 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';
@@ -36,25 +40,39 @@ const emit = defineEmits([
]);
const tableData = ref<ErpSaleOutApi.SaleOutItem[]>([]);
const productOptions = ref<any[]>([]);
const warehouseOptions = ref<any[]>([]);
const productOptions = ref<any[]>([]); // 产品下拉选项
const warehouseOptions = ref<any[]>([]); // 仓库下拉选项
/** 获取表格合计数据 */
const summaries = computed(() => {
return {
count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
totalProductPrice: tableData.value.reduce(
(sum, item) => sum + (item.totalProductPrice || 0),
0,
),
taxPrice: tableData.value.reduce(
(sum, item) => sum + (item.taxPrice || 0),
0,
),
totalPrice: tableData.value.reduce(
(sum, item) => sum + (item.totalPrice || 0),
0,
),
};
});
/** 表格配置 */
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
editConfig: {
trigger: 'click',
mode: 'cell',
},
columns: useFormItemColumns(),
data: tableData.value,
border: true,
showOverflow: true,
autoResize: true,
minHeight: 250,
keepSource: true,
autoResize: true,
border: true,
rowConfig: {
keyField: 'id',
keyField: 'row_id',
isHover: true,
},
pagerConfig: {
enabled: false,
@@ -72,17 +90,17 @@ watch(
if (!items) {
return;
}
await nextTick();
items.forEach((item) => initRow(item));
tableData.value = [...items];
await nextTick();
gridApi.grid.reloadData(tableData.value);
await nextTick(); // 特殊:保证 gridApi 已经初始化
await gridApi.grid.reloadData(tableData.value);
},
{
immediate: true,
},
);
/** 计算 discountPrice、totalPrice 价格 */
/** 计算 discountPrice、otherPrice、totalPrice 价格 */
watch(
() => [tableData.value, props.discountPercent, props.otherPrice],
() => {
@@ -108,119 +126,6 @@ watch(
{ deep: true },
);
/** 初始化 */
onMounted(async () => {
productOptions.value = await getProductSimpleList();
warehouseOptions.value = await getWarehouseSimpleList();
});
function handlePriceChange(row: any) {
if (row.productPrice && row.count) {
row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
row.taxPrice =
erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
row.totalPrice = row.totalProductPrice + row.taxPrice;
}
handleUpdateValue(row);
}
const handleWarehouseChange = async (row: ErpSaleOutApi.SaleOutItem) => {
const warehouseId = row.warehouseId;
const stockCount = await getWarehouseStockCount({
productId: row.productId!,
warehouseId: warehouseId!,
});
row.stockCount = stockCount || 0;
handleUpdateValue(row);
};
function handleUpdateValue(row: any) {
const index = tableData.value.findIndex((item) => item.id === row.id);
if (index === -1) {
tableData.value.push(row);
} else {
tableData.value[index] = row;
}
emit('update:items', [...tableData.value]);
}
const getSummaries = (): {
count: number;
productName: string;
taxPrice: number;
totalPrice: number;
totalProductPrice: number;
} => {
const count = tableData.value.reduce(
(sum, item) => sum + (item.count || 0),
0,
);
const totalProductPrice = tableData.value.reduce(
(sum, item) => sum + (item.totalProductPrice || 0),
0,
);
const taxPrice = tableData.value.reduce(
(sum, item) => sum + (item.taxPrice || 0),
0,
);
const totalPrice = tableData.value.reduce(
(sum, item) => sum + (item.totalPrice || 0),
0,
);
return {
productName: '合计',
count,
totalProductPrice,
taxPrice,
totalPrice,
};
};
const validate = async (): Promise<boolean> => {
try {
for (let i = 0; i < tableData.value.length; i++) {
const item = tableData.value[i];
if (item) {
if (!item.warehouseId) {
throw new Error(`${i + 1} 行:仓库不能为空`);
}
if (!item.count || item.count <= 0) {
throw new Error(`${i + 1} 行:产品数量不能为空`);
}
}
}
return true;
} catch (error) {
console.error('验证失败:', error);
throw error;
}
};
const getData = (): ErpSaleOutApi.SaleOutItem[] => tableData.value;
const init = (items: ErpSaleOutApi.SaleOutItem[] | undefined): void => {
tableData.value =
items && items.length > 0
? items.map((item) => {
const newItem = { ...item };
if (newItem.productPrice && newItem.count) {
newItem.totalProductPrice =
erpPriceMultiply(newItem.productPrice, newItem.count) ?? 0;
newItem.taxPrice =
erpPriceMultiply(
newItem.totalProductPrice,
(newItem.taxPercent || 0) / 100,
) ?? 0;
newItem.totalPrice = newItem.totalProductPrice + newItem.taxPrice;
}
return newItem;
})
: [];
// TODO @XuZhiqiang使用 await 风格哈;
nextTick(() => {
gridApi.grid.reloadData(tableData.value);
});
};
/** 处理删除 */
function handleDelete(row: ErpSaleOutApi.SaleOutItem) {
const index = tableData.value.findIndex((item) => item.id === row.id);
@@ -231,10 +136,60 @@ function handleDelete(row: ErpSaleOutApi.SaleOutItem) {
emit('update:items', [...tableData.value]);
}
/** 处理仓库变更 */
const handleWarehouseChange = async (row: ErpSaleOutApi.SaleOutItem) => {
const stockCount = await getWarehouseStockCount({
productId: row.productId!,
warehouseId: row.warehouseId!,
});
row.stockCount = stockCount || 0;
handleRowChange(row);
};
/** 处理行数据变更 */
function handleRowChange(row: any) {
const index = tableData.value.findIndex((item) => item.id === row.id);
if (index === -1) {
tableData.value.push(row);
} else {
tableData.value[index] = row;
}
emit('update:items', [...tableData.value]);
}
/** 初始化行数据 */
const initRow = (row: ErpSaleOutApi.SaleOutItem): void => {
if (row.productPrice && row.count) {
row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
row.taxPrice =
erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
row.totalPrice = row.totalProductPrice + row.taxPrice;
}
};
/** 表单校验 */
function validate() {
for (let i = 0; i < tableData.value.length; i++) {
const item = tableData.value[i];
if (item) {
if (!item.warehouseId) {
throw new Error(`${i + 1} 行:仓库不能为空`);
}
if (!item.count || item.count <= 0) {
throw new Error(`${i + 1} 行:产品数量不能为空`);
}
}
}
}
defineExpose({
validate,
getData,
init,
});
/** 初始化 */
onMounted(async () => {
productOptions.value = await getProductSimpleList();
warehouseOptions.value = await getWarehouseSimpleList();
});
</script>
@@ -269,7 +224,7 @@ defineExpose({
v-model:value="row.count"
:min="0"
:precision="2"
@change="handlePriceChange(row)"
@change="handleRowChange(row)"
/>
<span v-else>{{ row.count || '-' }}</span>
</template>
@@ -279,7 +234,7 @@ defineExpose({
v-model:value="row.productPrice"
:min="0"
:precision="2"
@change="handlePriceChange(row)"
@change="handleRowChange(row)"
/>
</template>
<template #remark="{ row }">
@@ -293,7 +248,7 @@ defineExpose({
:min="0"
:max="100"
:precision="2"
@change="handlePriceChange(row)"
@change="handleRowChange(row)"
/>
<span v-else>{{ row.taxPercent || '-' }}</span>
</template>
@@ -319,10 +274,14 @@ defineExpose({
<div class="text-muted-foreground flex justify-between text-sm">
<span class="text-foreground font-medium">合计</span>
<div class="flex space-x-4">
<span>数量{{ getSummaries().count }}</span>
<span>金额{{ getSummaries().totalProductPrice }}</span>
<span>税额{{ getSummaries().taxPrice }}</span>
<span>税额合计{{ getSummaries().totalPrice }}</span>
<span>数量{{ erpCountInputFormatter(summaries.count) }}</span>
<span>
金额{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
</span>
<span>税额{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
<span>
税额合计{{ erpPriceInputFormatter(summaries.totalPrice) }}
</span>
</div>
</div>
</div>