<script lang="ts" setup>
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { PurchaseOrderApi } from '#/api/purchase/order';
|
|
import { nextTick, watch } from 'vue';
|
|
import { Button, Input, InputNumber } from 'ant-design-vue';
|
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { $t } from '#/locales';
|
|
import { toSubmitItems } from '../data';
|
|
const props = withDefaults(
|
defineProps<{
|
items?: PurchaseOrderApi.OrderItem[];
|
disabled?: boolean;
|
}>(),
|
{
|
items: () => [],
|
disabled: false,
|
},
|
);
|
|
const emit = defineEmits<{
|
'update:items': [items: PurchaseOrderApi.OrderItem[]];
|
}>();
|
|
const [Grid, gridApi] = useVbenVxeGrid({
|
gridOptions: {
|
columns: [
|
{ type: 'seq', title: '序号', width: 55, fixed: 'left' },
|
{
|
field: 'itemName',
|
title: '商品名称',
|
minWidth: 160,
|
slots: { default: 'itemName' },
|
},
|
{
|
field: 'spec',
|
title: '规格型号',
|
minWidth: 150,
|
slots: { default: 'spec' },
|
},
|
{
|
field: 'quantity',
|
title: '数量',
|
width: 120,
|
slots: { default: 'quantity' },
|
},
|
{
|
field: 'unitPrice',
|
title: '单价(元)',
|
width: 130,
|
slots: { default: 'unitPrice' },
|
},
|
{
|
field: 'amount',
|
title: '金额(元)',
|
width: 130,
|
slots: { default: 'amount' },
|
},
|
{
|
title: '操作',
|
width: 80,
|
fixed: 'right',
|
slots: { default: 'actions' },
|
},
|
],
|
data: [],
|
minHeight: 180,
|
autoResize: true,
|
border: true,
|
rowConfig: { keyField: 'id', isHover: true },
|
pagerConfig: { enabled: false },
|
toolbarConfig: { enabled: false },
|
} as VxeTableGridOptions<PurchaseOrderApi.OrderItem>,
|
});
|
|
function calcAmount(row: PurchaseOrderApi.OrderItem) {
|
const quantity = Number(row.quantity ?? 0);
|
const unitPrice = Number(row.unitPrice ?? 0);
|
return Math.round(quantity * unitPrice * 100) / 100;
|
}
|
|
/** 读取当前全部明细行(提交形态) */
|
function collectItems(): PurchaseOrderApi.OrderItem[] {
|
const rows =
|
(gridApi.grid.getData() as PurchaseOrderApi.OrderItem[]) ?? [];
|
const removed =
|
(gridApi.grid.getRemoveRecords() as PurchaseOrderApi.OrderItem[]) ?? [];
|
const inserted =
|
(gridApi.grid.getInsertRecords() as PurchaseOrderApi.OrderItem[]) ?? [];
|
const isRemoved = (row: PurchaseOrderApi.OrderItem) =>
|
removed.some(
|
(item) => item === row || (row.id != null && item.id === row.id),
|
);
|
return toSubmitItems([
|
...rows.filter((row) => !isRemoved(row)),
|
...inserted,
|
]).map((item) => ({ ...item, amount: calcAmount(item as PurchaseOrderApi.OrderItem) }));
|
}
|
|
function recalc() {
|
emit('update:items', collectItems());
|
}
|
|
async function handleAdd() {
|
await gridApi.grid.insertAt(
|
{ quantity: 1, unitPrice: 0, sortNo: collectItems().length + 1 } as unknown as PurchaseOrderApi.OrderItem,
|
-1,
|
);
|
recalc();
|
}
|
|
async function handleDelete(row: PurchaseOrderApi.OrderItem) {
|
await gridApi.grid.remove(row);
|
recalc();
|
}
|
|
/** 校验明细:商品名称必填,数量>0,单价≥0 */
|
function validate() {
|
const rows = collectItems();
|
if (rows.length === 0) {
|
throw new Error('采购单至少需要 1 行采购商品明细');
|
}
|
for (let i = 0; i < rows.length; i++) {
|
const row = rows[i]!;
|
if (!row.itemName || String(row.itemName).trim() === '') {
|
throw new Error(`第 ${i + 1} 行采购商品:商品名称不能为空`);
|
}
|
if (!row.quantity || Number(row.quantity) <= 0) {
|
throw new Error(`第 ${i + 1} 行采购商品:数量必须大于 0`);
|
}
|
if (row.unitPrice == null || Number(row.unitPrice) < 0) {
|
throw new Error(`第 ${i + 1} 行采购商品:单价不能小于 0`);
|
}
|
}
|
}
|
|
defineExpose({ validate, getItems: collectItems });
|
|
watch(
|
() => props.items,
|
async (items) => {
|
if (!items) {
|
return;
|
}
|
await nextTick();
|
await gridApi.grid.loadData([...items]);
|
},
|
{ immediate: true },
|
);
|
</script>
|
|
<template>
|
<Grid class="w-full">
|
<template #itemName="{ row }">
|
<Input
|
v-model:value="row.itemName"
|
:disabled="disabled"
|
placeholder="请输入商品名称"
|
@change="() => recalc()"
|
/>
|
</template>
|
<template #spec="{ row }">
|
<Input
|
v-model:value="row.spec"
|
:disabled="disabled"
|
placeholder="请输入规格型号"
|
@change="() => recalc()"
|
/>
|
</template>
|
<template #quantity="{ row }">
|
<InputNumber
|
v-model:value="row.quantity"
|
:disabled="disabled"
|
:min="0.01"
|
:precision="2"
|
class="!w-full"
|
@change="() => recalc()"
|
/>
|
</template>
|
<template #unitPrice="{ row }">
|
<InputNumber
|
v-model:value="row.unitPrice"
|
:disabled="disabled"
|
:min="0"
|
:precision="2"
|
class="!w-full"
|
@change="() => recalc()"
|
/>
|
</template>
|
<template #amount="{ row }">
|
<span>{{ calcAmount(row).toFixed(2) }}</span>
|
</template>
|
<template #actions="{ row }">
|
<TableAction
|
:actions="[
|
{
|
label: $t('common.delete'),
|
danger: true,
|
type: 'link',
|
icon: ACTION_ICON.DELETE,
|
disabled,
|
onClick: handleDelete.bind(null, row),
|
},
|
]"
|
/>
|
</template>
|
</Grid>
|
<div v-if="!disabled" class="-mt-4 flex justify-center">
|
<Button type="primary" ghost @click="handleAdd"> 添加商品明细 </Button>
|
</div>
|
</template>
|