| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from '../data'; |
| | | import type { FinanceReimbursementApi } from '#/api/finance/reimbursement'; |
| | | import type { SystemStorageApi } from '#/api/system/storage'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { $t } from '@vben/locales'; |
| | | import { useUserStore } from '@vben/stores'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import dayjs from 'dayjs'; |
| | |
| | | import ItemForm from './item-form.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | /** 当前登录用户 ID(用于本人发起时回填申请人) */ |
| | | const currentUserId = computed(() => { |
| | | const id = Number(userStore.userInfo?.userId); |
| | | return Number.isFinite(id) && id > 0 ? id : undefined; |
| | | }); |
| | | |
| | | const formData = ref<FinanceReimbursementApi.Reimbursement>({ |
| | | title: '', |
| | |
| | | formApi.setValues({ totalAmount }, false); |
| | | } |
| | | |
| | | /** |
| | | * OCR 识别时上传的发票文件同步到「发票附件」,保证识别过的发票随单保存。 |
| | | * 按 blobId 去重,避免同一张发票被重复识别时出现重复附件。 |
| | | */ |
| | | async function handleOcrBlobs(blobs: SystemStorageApi.StorageBlob[]) { |
| | | if (blobs.length === 0) { |
| | | return; |
| | | } |
| | | const values = await formApi.getValues(); |
| | | const existing = (values.blobIds ?? []) as Array< |
| | | | number |
| | | | { id?: number; name?: string; status?: string; uid?: string; url?: string } |
| | | >; |
| | | const existingFiles = existing.map((item) => |
| | | typeof item === 'number' |
| | | ? { uid: String(item), status: 'done', id: item } |
| | | : item, |
| | | ); |
| | | const existingIds = new Set( |
| | | existingFiles |
| | | .map((item) => item.id) |
| | | .filter((id): id is number => typeof id === 'number'), |
| | | ); |
| | | const appended = blobs |
| | | .filter((blob) => !existingIds.has(blob.id)) |
| | | .map((blob) => ({ |
| | | uid: String(blob.id), |
| | | name: blob.name || blob.originalFilename || '', |
| | | url: blob.url || blob.previewURL || '', |
| | | status: 'done', |
| | | id: blob.id, |
| | | })); |
| | | if (appended.length === 0) { |
| | | return; |
| | | } |
| | | await formApi.setValues( |
| | | { blobIds: [...existingFiles, ...appended] }, |
| | | false, |
| | | ); |
| | | } |
| | | |
| | | /** 创建或更新报销单 */ |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | |
| | | return; |
| | | } |
| | | |
| | | modalApi.lock(); |
| | | const data = |
| | | (await formApi.getValues()) as FinanceReimbursementApi.Reimbursement; |
| | | const data = (await formApi.getValues()) as FinanceReimbursementApi.Reimbursement & { |
| | | proxyFlag?: boolean; |
| | | }; |
| | | // 代他人发起必须选择实际申请人;本人发起时以当前登录用户为准 |
| | | if (data.proxyFlag) { |
| | | if (!data.applicantId) { |
| | | message.error('代他人发起时,请选择实际申请人'); |
| | | return; |
| | | } |
| | | } else { |
| | | data.applicantId = currentUserId.value; |
| | | } |
| | | // proxyFlag 仅用于前端交互,不提交给后端 |
| | | delete data.proxyFlag; |
| | | // 发票附件为全量替换语义:清空全部发票时必须显式传空数组, |
| | | // 传 null 后端会视为「不动附件」,导致删除操作不生效 |
| | | data.blobIds = data.blobIds ?? []; |
| | | modalApi.lock(); |
| | | try { |
| | | await (formType.value === 'create' |
| | | ? createReimbursement(data) |
| | |
| | | formApi.updateSchema(useFormSchema(formType.value)); |
| | | if (!data || !data.id) { |
| | | initialItems.value = []; |
| | | // 新建时默认申请日期为今天 |
| | | // 新建时默认本人发起:申请人展示当前登录用户,申请日期为今天 |
| | | await formApi.setValues( |
| | | { applyDate: dayjs().format('YYYY-MM-DD') }, |
| | | { |
| | | applicantId: currentUserId.value, |
| | | applicantName: userStore.userInfo?.nickname ?? '', |
| | | proxyFlag: false, |
| | | applyDate: dayjs().format('YYYY-MM-DD'), |
| | | }, |
| | | false, |
| | | ); |
| | | return; |
| | |
| | | status: 'done', |
| | | id: item.id, |
| | | })); |
| | | // 实际申请人与当前登录用户不一致时,视为代他人发起,回显申请人选择器 |
| | | const isProxy = |
| | | formType.value === 'edit' && |
| | | formData.value.applicantId != null && |
| | | formData.value.applicantId !== currentUserId.value; |
| | | await formApi.setValues( |
| | | { |
| | | ...formData.value, |
| | | proxyFlag: isProxy, |
| | | // 表单里的 items 只用于提交,必须剥掉行 id(后端「先删后增」会与软删行主键冲突) |
| | | items: toSubmitItems(formData.value.items), |
| | | blobIds, |
| | |
| | | :items="initialItems" |
| | | :disabled="formType === 'detail'" |
| | | class="w-full" |
| | | @ocr-blobs="handleOcrBlobs" |
| | | @update:items="handleUpdateItems" |
| | | @update:total-amount="handleUpdateTotalAmount" |
| | | /> |