gaoluyang
5 天以前 98312d84f785c09574bfd5add730ea448fb0cbd5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<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>