liu
3 天以前 fe2d6627b8f1002af7a31b08f6f43bec034d04ee
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
<script lang="ts" setup>
import type { FormType } from '../data';
import type { ResearchApplicationApi } from '#/api/research/application';
import type { ResearchGuidelineApi } from '#/api/research/guideline';
 
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 {
  createApplication,
  getApplication,
  updateApplication,
} from '#/api/research/application';
import {
  getGuideline,
  getPublishedGuidelineList,
} from '#/api/research/guideline';
 
import { toSubmitMembers, useFormSchema } from '../data';
import MemberTable from './member-table.vue';
 
defineOptions({ name: 'ResearchApplicationForm' });
 
const emit = defineEmits(['success']);
 
const formType = ref<FormType>('create');
/** 供「所属课题指南」下拉与类别带出使用 */
const guidelines = ref<ResearchGuidelineApi.Guideline[]>([]);
/** 成员表格回显数据源:只在打开弹窗时赋值,增删改只通过 emit 单向流到表单值 */
const initialMembers = ref<ResearchApplicationApi.ApplicationMember[]>([]);
const memberTableRef = ref<InstanceType<typeof MemberTable>>();
 
const getTitle = computed(() => {
  if (formType.value === 'create') {
    return $t('ui.actionTitle.create', ['课题申报书']);
  }
  if (formType.value === 'edit') {
    return $t('ui.actionTitle.edit', ['课题申报书']);
  }
  return '课题申报书详情';
});
 
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    labelWidth: 130,
  },
  wrapperClass: 'grid-cols-2',
  layout: 'vertical',
  schema: [],
  showDefaultActions: false,
});
 
/** 只提交后端 SaveReqVO 接受的字段,避免回传 category/reviewAvgScore 等派生字段 */
async function buildSubmitData() {
  const values = (await formApi.getValues()) as ResearchApplicationApi.Application;
  return {
    id: values.id,
    guidelineId: values.guidelineId,
    title: values.title,
    memberId: values.memberId,
    applicantName: values.applicantName,
    workUnit: values.workUnit,
    jobTitle: values.jobTitle,
    contactPhone: values.contactPhone,
    contactEmail: values.contactEmail,
    members: toSubmitMembers(values.members),
    budgetAmount: values.budgetAmount,
    planContent: values.planContent,
    remark: values.remark,
    // 附件为全量替换语义:清空全部附件时必须显式传空数组,
    // 传 null 后端会视为「不动附件」,导致删除操作不生效
    blobIds: values.blobIds ?? [],
  } as ResearchApplicationApi.Application;
}
 
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    try {
      memberTableRef.value?.validate();
    } catch (error) {
      message.error((error as Error).message || '课题组成员校验失败');
      return;
    }
    modalApi.lock();
    const data = await buildSubmitData();
    try {
      await (formType.value === 'create'
        ? createApplication(data)
        : updateApplication(data));
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      guidelines.value = [];
      return;
    }
    const data = modalApi.getData<{ formType: FormType; id?: number }>();
    formType.value = data.formType;
    modalApi.lock();
    try {
      if (data?.id) {
        const application = await getApplication(data.id);
        guidelines.value = await resolveGuidelineOptions(application);
        formApi.setState({
          schema: useFormSchema(formType.value, formApi, guidelines.value),
        });
        formApi.setDisabled(formType.value === 'detail');
        // 成员子表回显:保留原 id 供表格行 key 使用,提交时再剥离
        initialMembers.value = application.members ?? [];
        // 已有附件转成上传组件可识别的文件对象,提交时按 valueKey=id 提取为 blobId
        const blobIds = (application.attachmentList ?? []).map((item) => ({
          uid: String(item.id),
          name: item.name || '',
          url: item.url || '',
          status: 'done',
          id: item.id,
        }));
        await formApi.setValues(
          {
            ...application,
            members: toSubmitMembers(application.members),
            blobIds,
          },
          false,
        );
      } else {
        guidelines.value = await getPublishedGuidelineList();
        formApi.setState({
          schema: useFormSchema(formType.value, formApi, guidelines.value),
        });
        formApi.setDisabled(false);
        initialMembers.value = [];
        await formApi.resetForm();
      }
    } finally {
      modalApi.unlock();
    }
  },
});
 
/**
 * 编辑/详情时指南可能已截止(不在「已发布」列表里),
 * 需要单独取回当前所属指南补进选项,否则下拉显示为空。
 */
async function resolveGuidelineOptions(
  application: ResearchApplicationApi.Application,
): Promise<ResearchGuidelineApi.Guideline[]> {
  const published = await getPublishedGuidelineList();
  const currentId = application.guidelineId;
  if (!currentId || published.some((item) => item.id === currentId)) {
    return published;
  }
  const current = await getGuideline(currentId);
  return [...published, current];
}
</script>
 
<template>
  <Modal
    :title="getTitle"
    class="w-3/4"
    :close-on-click-modal="false"
    :show-confirm-button="formType !== 'detail'"
  >
    <Form class="mx-3">
      <template #members>
        <MemberTable
          ref="memberTableRef"
          :members="initialMembers"
          :disabled="formType === 'detail'"
          class="w-full"
          @update:members="(members) => formApi.setValues({ members }, false)"
        />
      </template>
    </Form>
  </Modal>
</template>