feat:【ele】【erp】purchase 的迁移(20%)- supplier

This commit is contained in:
YunaiV
2025-11-16 20:27:30 +08:00
parent e9164912e5
commit 2973e0b70f
5 changed files with 708 additions and 1 deletions

View File

@@ -124,7 +124,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[

View File

@@ -0,0 +1,231 @@
<script lang="ts" setup>
import type { ErpPurchaseInApi } from '#/api/erp/purchase/in';
import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
import {
createPurchaseIn,
getPurchaseIn,
updatePurchaseIn,
} from '#/api/erp/purchase/in';
import { useFormSchema } from '../data';
import ItemForm from './item-form.vue';
import PurchaseOrderSelect from './purchase-order-select.vue';
const emit = defineEmits(['success']);
const formData = ref<
ErpPurchaseInApi.PurchaseIn & {
accountId?: number;
customerId?: number;
discountPercent?: number;
fileUrl?: string;
order?: ErpPurchaseOrderApi.PurchaseOrder;
orderId?: number;
orderNo?: string;
}
>({
id: undefined,
no: undefined,
accountId: undefined,
inTime: undefined,
remark: undefined,
fileUrl: undefined,
discountPercent: 0,
supplierId: undefined,
discountPrice: 0,
totalPrice: 0,
otherPrice: 0,
items: [],
});
const formType = ref(''); // 表单类型:'create' | 'edit' | 'detail'
const itemFormRef = ref<InstanceType<typeof ItemForm>>();
const getTitle = computed(() => {
if (formType.value === 'create') {
return $t('ui.actionTitle.create', ['采购入库']);
} else if (formType.value === 'edit') {
return $t('ui.actionTitle.edit', ['采购入库']);
} else {
return '采购入库详情';
}
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
labelWidth: 120,
},
wrapperClass: 'grid-cols-3',
layout: 'vertical',
schema: useFormSchema(formType.value),
showDefaultActions: false,
handleValuesChange: (values, changedFields) => {
// 目的:同步到 item-form 组件,触发整体的价格计算
if (formData.value) {
if (changedFields.includes('otherPrice')) {
formData.value.otherPrice = values.otherPrice;
}
if (changedFields.includes('discountPercent')) {
formData.value.discountPercent = values.discountPercent;
}
}
},
});
/** 更新采购入库项 */
const handleUpdateItems = (items: ErpPurchaseInApi.PurchaseInItem[]) => {
formData.value.items = items;
formApi.setValues({
items,
});
};
/** 更新其他费用 */
const handleUpdateOtherPrice = (otherPrice: number) => {
formApi.setValues({
otherPrice,
});
};
/** 更新优惠金额 */
const handleUpdateDiscountPrice = (discountPrice: number) => {
formApi.setValues({
discountPrice,
});
};
/** 更新总金额 */
const handleUpdateTotalPrice = (totalPrice: number) => {
formApi.setValues({
totalPrice,
});
};
/** 选择采购订单 */
const handleUpdateOrder = (order: ErpPurchaseOrderApi.PurchaseOrder) => {
formData.value = {
...formData.value,
orderId: order.id,
orderNo: order.no!,
supplierId: order.supplierId!,
accountId: order.accountId!,
remark: order.remark!,
discountPercent: order.discountPercent!,
fileUrl: order.fileUrl!,
};
// 将订单项设置到入库单项
order.items!.forEach((item: any) => {
item.totalCount = item.count;
item.count = item.totalCount - item.inCount;
item.orderItemId = item.id;
item.id = undefined;
});
formData.value.items = order.items!.filter(
(item) => item.count && item.count > 0,
) as ErpPurchaseInApi.PurchaseInItem[];
formApi.setValues(formData.value, false);
};
/** 创建或更新采购入库 */
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const itemFormInstance = Array.isArray(itemFormRef.value)
? itemFormRef.value[0]
: itemFormRef.value;
try {
itemFormInstance.validate();
} catch (error: any) {
ElMessage.error(error.message || '子表单验证失败');
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as ErpPurchaseInApi.PurchaseIn;
try {
await (formType.value === 'create'
? createPurchaseIn(data)
: updatePurchaseIn(data));
// 关闭并提示
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = {} as ErpPurchaseInApi.PurchaseIn;
return;
}
// 加载数据
const data = modalApi.getData<{ id?: number; type: string }>();
formType.value = data.type;
formApi.setDisabled(formType.value === 'detail');
formApi.updateSchema(useFormSchema(formType.value));
if (!data || !data.id) {
// 新增时,默认选中账户
const accountList = await getAccountSimpleList();
const defaultAccount = accountList.find((item) => item.defaultStatus);
if (defaultAccount) {
await formApi.setValues({ accountId: defaultAccount.id });
}
return;
}
modalApi.lock();
try {
formData.value = await getPurchaseIn(data.id);
// 设置到 values
await formApi.setValues(formData.value, false);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal
:title="getTitle"
class="w-3/4"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">
<template #items>
<ItemForm
ref="itemFormRef"
:items="formData?.items ?? []"
:disabled="formType === 'detail'"
:discount-percent="formData?.discountPercent ?? 0"
:other-price="formData?.otherPrice ?? 0"
@update:items="handleUpdateItems"
@update:discount-price="handleUpdateDiscountPrice"
@update:other-price="handleUpdateOtherPrice"
@update:total-price="handleUpdateTotalPrice"
/>
</template>
<template #orderNo>
<PurchaseOrderSelect
:order-no="formData?.orderNo"
@update:order="handleUpdateOrder"
/>
</template>
</Form>
</Modal>
</template>

View File

@@ -0,0 +1,234 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'id',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'name',
label: '供应商名称',
component: 'Input',
rules: 'required',
componentProps: {
placeholder: '请输入供应商名称',
},
},
{
fieldName: 'contact',
label: '联系人',
component: 'Input',
componentProps: {
placeholder: '请输入联系人',
},
},
{
fieldName: 'mobile',
label: '手机号码',
component: 'Input',
componentProps: {
placeholder: '请输入手机号码',
},
},
{
fieldName: 'telephone',
label: '联系电话',
component: 'Input',
componentProps: {
placeholder: '请输入联系电话',
},
},
{
fieldName: 'email',
label: '电子邮箱',
component: 'Input',
componentProps: {
placeholder: '请输入电子邮箱',
},
},
{
fieldName: 'fax',
label: '传真',
component: 'Input',
componentProps: {
placeholder: '请输入传真',
},
},
{
fieldName: 'status',
label: '开启状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'sort',
label: '排序',
component: 'InputNumber',
componentProps: {
placeholder: '请输入排序',
controlsPosition: 'right',
class: '!w-full',
},
rules: 'required',
},
{
fieldName: 'taxNo',
label: '纳税人识别号',
component: 'Input',
componentProps: {
placeholder: '请输入纳税人识别号',
},
},
{
fieldName: 'taxPercent',
label: '税率(%)',
component: 'InputNumber',
componentProps: {
placeholder: '请输入税率',
min: 0,
precision: 2,
controlsPosition: 'right',
class: '!w-full',
},
},
{
fieldName: 'bankName',
label: '开户行',
component: 'Input',
componentProps: {
placeholder: '请输入开户行',
},
},
{
fieldName: 'bankAccount',
label: '开户账号',
component: 'Input',
componentProps: {
placeholder: '请输入开户账号',
},
},
{
fieldName: 'bankAddress',
label: '开户地址',
component: 'Input',
componentProps: {
placeholder: '请输入开户地址',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
formItemClass: 'col-span-2',
},
];
}
/** 搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '供应商名称',
component: 'Input',
componentProps: {
placeholder: '请输入供应商名称',
allowClear: true,
},
},
{
fieldName: 'mobile',
label: '手机号码',
component: 'Input',
componentProps: {
placeholder: '请输入手机号码',
allowClear: true,
},
},
{
fieldName: 'telephone',
label: '联系电话',
component: 'Input',
componentProps: {
placeholder: '请输入联系电话',
allowClear: true,
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'name',
title: '供应商名称',
minWidth: 150,
},
{
field: 'contact',
title: '联系人',
minWidth: 120,
},
{
field: 'mobile',
title: '手机号码',
minWidth: 130,
},
{
field: 'telephone',
title: '联系电话',
minWidth: 130,
},
{
field: 'email',
title: '电子邮箱',
minWidth: 180,
},
{
field: 'status',
title: '状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'sort',
title: '排序',
minWidth: 80,
},
{
field: 'remark',
title: '备注',
minWidth: 150,
showOverflow: 'tooltip',
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@@ -0,0 +1,153 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpSupplierApi } from '#/api/erp/purchase/supplier';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteSupplier,
exportSupplier,
getSupplierPage,
} from '#/api/erp/purchase/supplier';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import SupplierForm from './modules/form.vue';
/** 供应商管理 */
defineOptions({ name: 'ErpSupplier' });
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建供应商 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑供应商 */
function handleEdit(row: ErpSupplierApi.Supplier) {
formModalApi.setData(row).open();
}
/** 删除供应商 */
async function handleDelete(row: ErpSupplierApi.Supplier) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [row.name]),
});
try {
await deleteSupplier(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
loadingInstance.close();
}
}
/** 导出供应商 */
async function handleExport() {
const data = await exportSupplier(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '供应商.xls', source: data });
}
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: SupplierForm,
destroyOnClose: true,
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getSupplierPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<ErpSupplierApi.Supplier>,
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【采购】采购订单、入库、退货"
url="https://doc.iocoder.cn/erp/purchase/"
/>
</template>
<FormModal @success="handleRefresh" />
<Grid table-title="供应商列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['供应商']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['erp:supplier:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['erp:supplier:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['erp:supplier:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:supplier:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,90 @@
<script lang="ts" setup>
import type { ErpSupplierApi } from '#/api/erp/purchase/supplier';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import {
createSupplier,
getSupplier,
updateSupplier,
} from '#/api/erp/purchase/supplier';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<ErpSupplierApi.Supplier>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['供应商'])
: $t('ui.actionTitle.create', ['供应商']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
labelWidth: 100,
},
wrapperClass: 'grid-cols-2',
layout: 'horizontal',
schema: useFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as ErpSupplierApi.Supplier;
try {
await (formData.value?.id ? updateSupplier(data) : createSupplier(data));
// 关闭并提示
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
// 加载数据
const data = modalApi.getData<ErpSupplierApi.Supplier>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getSupplier(data.id);
// 设置到 values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
defineExpose({
modalApi,
});
</script>
<template>
<Modal :title="getTitle" class="w-1/2">
<Form class="mx-4" />
</Modal>
</template>