liu
2026-09-29 982c97ab82f3beb1b4febcf014562eda34b9fc05
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
<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 { useFormSchema } from '../data';
 
defineOptions({ name: 'ResearchApplicationForm' });
 
const emit = defineEmits(['success']);
 
const formType = ref<FormType>('create');
/** 供「所属课题指南」下拉与类别带出使用 */
const guidelines = ref<ResearchGuidelineApi.Guideline[]>([]);
 
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,
    teamMembers: values.teamMembers,
    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;
    }
    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');
        // 已有附件转成上传组件可识别的文件对象,提交时按 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, blobIds }, false);
      } else {
        guidelines.value = await getPublishedGuidelineList();
        formApi.setState({
          schema: useFormSchema(formType.value, formApi, guidelines.value),
        });
        formApi.setDisabled(false);
        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" />
  </Modal>
</template>