<script lang="ts" setup>
|
import type { FinVoucherApi } from '#/api/erp/finance/voucher';
|
import type { FormType } from '../data';
|
|
import { computed, ref } from 'vue';
|
|
import { useVbenModal } from '@vben/common-ui';
|
import { $t } from '@vben/locales';
|
|
import { message } from 'ant-design-vue';
|
|
import { useVbenForm } from '#/adapter/form';
|
import {
|
createVoucher,
|
getVoucher,
|
updateVoucher,
|
} from '#/api/erp/finance/voucher';
|
|
import { useFormSchema } from '../data';
|
import EntryTable from './entry-table.vue';
|
|
const emit = defineEmits(['success']);
|
const formData = ref<FinVoucherApi.Voucher>();
|
|
const formType = ref<FormType>('create'); // 表单类型:'create' | 'edit' | 'detail'
|
const entryTableRef = ref<InstanceType<typeof EntryTable>>();
|
// 分录的唯一数据源:必须用稳定的 ref,不能在模板里现造 []。
|
// 否则父组件每次渲染都会给 EntryTable 传一个新的空数组,触发其 watch 重建表格,
|
// 把用户刚「添加分录」加进去的行冲掉(新增模式下 formData 为 undefined 时必现)。
|
const entries = ref<FinVoucherApi.VoucherEntry[]>([]);
|
|
const getTitle = computed(() => {
|
if (formType.value === 'create') {
|
return $t('ui.actionTitle.create', ['记账凭证']);
|
} else if (formType.value === 'edit') {
|
return $t('ui.actionTitle.edit', ['记账凭证']);
|
}
|
return '记账凭证详情';
|
});
|
|
const [Form, formApi] = useVbenForm({
|
commonConfig: {
|
componentProps: {
|
class: 'w-full',
|
},
|
labelWidth: 100,
|
},
|
// 三列栅格:凭证字号/凭证日期/附件张数 占满一行,其余字段整行铺开,
|
// 避免两列布局下摘要、附件张数右半边留空(与收款单、付款单弹窗保持一致)
|
wrapperClass: 'grid-cols-3',
|
layout: 'horizontal',
|
schema: useFormSchema(),
|
showDefaultActions: false,
|
});
|
|
/**
|
* 同步分录到表单值,随表单一起提交。
|
* 注意:这里只写表单值,**不要把 value 回写到 entries**——
|
* entries 只在打开弹窗时从服务端播种一次,之后的实时数据以 EntryTable 内部表格为准。
|
* 一旦回写,props.entries 变化会触发 EntryTable 的 watch 重建整张表格,导致每敲一个
|
* 数字/选一次科目就重造一遍行(行标识变化、输入框失焦)。
|
*/
|
function handleUpdateEntries(value: FinVoucherApi.VoucherEntry[]) {
|
formApi.setValues({ entries: value });
|
}
|
|
/** 创建或更新记账凭证 */
|
const [Modal, modalApi] = useVbenModal({
|
async onConfirm() {
|
const { valid } = await formApi.validate();
|
if (!valid) {
|
return;
|
}
|
// 分录校验与后端 FinVoucherServiceImpl 的口径一致,前端先拦一遍,避免无意义的请求
|
const entryTableInstance = Array.isArray(entryTableRef.value)
|
? entryTableRef.value[0]
|
: entryTableRef.value;
|
try {
|
entryTableInstance?.validate();
|
} catch (error: any) {
|
message.error(error.message || '凭证分录校验失败');
|
return;
|
}
|
|
modalApi.lock();
|
const data = (await formApi.getValues()) as FinVoucherApi.Voucher;
|
if (formData.value?.id) {
|
data.id = formData.value.id;
|
}
|
try {
|
await (formType.value === 'create'
|
? createVoucher(data)
|
: updateVoucher(data));
|
await modalApi.close();
|
emit('success');
|
message.success($t('ui.actionMessage.operationSuccess'));
|
} finally {
|
modalApi.unlock();
|
}
|
},
|
async onOpenChange(isOpen: boolean) {
|
if (!isOpen) {
|
formData.value = undefined;
|
return;
|
}
|
formData.value = undefined;
|
entries.value = [];
|
await formApi.resetForm();
|
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
formType.value = data?.formType ?? 'create';
|
formApi.setDisabled(formType.value === 'detail');
|
if (!data?.id) {
|
return;
|
}
|
modalApi.lock();
|
try {
|
formData.value = await getVoucher(data.id);
|
await formApi.setValues(formData.value, false);
|
entries.value = formData.value?.entries ?? [];
|
// 用 attachmentList 代替 blobIds,让上传组件能展示文件名和预览链接
|
if (formData.value?.attachmentList?.length) {
|
const blobIds = formData.value.attachmentList.map((item) => ({
|
uid: String(item.id),
|
name: item.name || '',
|
url: item.url || '',
|
status: 'done',
|
id: item.id,
|
}));
|
await formApi.setFieldValue('blobIds', blobIds);
|
}
|
} finally {
|
modalApi.unlock();
|
}
|
},
|
});
|
</script>
|
|
<template>
|
<Modal
|
:title="getTitle"
|
class="w-5/6"
|
:close-on-click-modal="false"
|
:show-confirm-button="formType !== 'detail'"
|
>
|
<Form class="mx-3">
|
<template #entries>
|
<EntryTable
|
ref="entryTableRef"
|
:entries="entries"
|
:disabled="formType === 'detail'"
|
class="w-full"
|
@update:entries="handleUpdateEntries"
|
/>
|
</template>
|
</Form>
|
</Modal>
|
</template>
|