|
|
@@ -347,27 +347,27 @@
|
|
|
</div>
|
|
|
|
|
|
<el-divider content-position="left">附件上传</el-divider>
|
|
|
- <el-form-item label="立项申请表" required>
|
|
|
+ <el-form-item label="立项申请表">
|
|
|
<UploadFile
|
|
|
- v-model="formData.applicationFile"
|
|
|
+ v-model="applicationFile"
|
|
|
:return-object="true"
|
|
|
- :limit="1"
|
|
|
+ :limit="10"
|
|
|
:file-type="['doc', 'docx', 'pdf', 'png', 'jpg', 'jpeg']"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="开题报告" required>
|
|
|
+ <el-form-item label="开题报告">
|
|
|
<UploadFile
|
|
|
- v-model="formData.openingReportFile"
|
|
|
+ v-model="openingReportFile"
|
|
|
:return-object="true"
|
|
|
- :limit="1"
|
|
|
+ :limit="10"
|
|
|
:file-type="['doc', 'docx', 'pdf', 'png', 'jpg', 'jpeg']"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="其他文件">
|
|
|
<UploadFile
|
|
|
- v-model="formData.otherFile"
|
|
|
+ v-model="otherFile"
|
|
|
:return-object="true"
|
|
|
- :limit="3"
|
|
|
+ :limit="10"
|
|
|
:file-type="['doc', 'docx', 'pdf', 'xls', 'xlsx', 'jpg', 'png']"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
@@ -472,6 +472,54 @@ type BudgetDetail = {
|
|
|
sort?: number
|
|
|
}
|
|
|
|
|
|
+// ========== 附件:三个独立 ref,完全不进 formData(与 index2.vue 一致) ==========
|
|
|
+const applicationFile = ref<any[]>([])
|
|
|
+const openingReportFile = ref<any[]>([])
|
|
|
+const otherFile = ref<any[]>([])
|
|
|
+
|
|
|
+/**
|
|
|
+ * 附件归一化:严格照抄 index2.vue
|
|
|
+ * - 不做任何额外字段补全(不加 name / url / fileName 等)
|
|
|
+ * - 输出结构就是 { filePath, type }
|
|
|
+ */
|
|
|
+const normalizeAttachments = (attachments: unknown, type?: string): any[] => {
|
|
|
+ if (!attachments) return []
|
|
|
+ const values = Array.isArray(attachments) ? attachments : [attachments]
|
|
|
+ return values
|
|
|
+ .flatMap((attachment: any) => {
|
|
|
+ if (typeof attachment === 'string') {
|
|
|
+ return attachment
|
|
|
+ .split(',')
|
|
|
+ .map((filePath) => filePath.trim())
|
|
|
+ .filter(Boolean)
|
|
|
+ .map((filePath) => (type ? { filePath, type } : filePath))
|
|
|
+ }
|
|
|
+ if (attachment && typeof attachment === 'object') {
|
|
|
+ const filePath =
|
|
|
+ attachment.filePath || attachment.path || attachment.url || attachment.fileUrl
|
|
|
+ return filePath ? [{ ...attachment, filePath, ...(type ? { type } : {}) }] : []
|
|
|
+ }
|
|
|
+ return []
|
|
|
+ })
|
|
|
+ .filter((attachment) =>
|
|
|
+ typeof attachment === 'string' ? attachment.length > 0 : attachment.filePath
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+/** 合并三个附件 ref 成统一 attachments 数组,带 type 区分来源 */
|
|
|
+const getAttachments = () => [
|
|
|
+ ...normalizeAttachments(applicationFile.value, 'APPLICATION'),
|
|
|
+ ...normalizeAttachments(openingReportFile.value, 'OPENING_REPORT'),
|
|
|
+ ...normalizeAttachments(otherFile.value, 'OTHER')
|
|
|
+]
|
|
|
+
|
|
|
+/** 清空附件 */
|
|
|
+const resetAttachments = () => {
|
|
|
+ applicationFile.value = []
|
|
|
+ openingReportFile.value = []
|
|
|
+ otherFile.value = []
|
|
|
+}
|
|
|
+
|
|
|
// ========== 列表与查询 ==========
|
|
|
const list = ref<any[]>([])
|
|
|
const total = ref(0)
|
|
|
@@ -512,19 +560,19 @@ const resetQuery = () => {
|
|
|
|
|
|
// ========== 状态辅助方法 ==========
|
|
|
const getPreAuditLabel = (status: number) => {
|
|
|
- const map = { 0: '未提交', 1: '审批中', 2: '已通过', 3: '已驳回' }
|
|
|
+ const map: Record<number, string> = { 0: '未提交', 1: '审批中', 2: '已通过', 3: '已驳回' }
|
|
|
return map[status] || '未知'
|
|
|
}
|
|
|
const getPreAuditTagType = (status: number) => {
|
|
|
- const map = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
|
|
|
+ const map: Record<number, string> = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
|
|
|
return map[status] || 'info'
|
|
|
}
|
|
|
const getFormalAuditLabel = (status: number) => {
|
|
|
- const map = { 0: '未提交', 1: '审批中', 2: '已通过', 3: '已驳回' }
|
|
|
+ const map: Record<number, string> = { 0: '未提交', 1: '审批中', 2: '已通过', 3: '已驳回' }
|
|
|
return map[status] || '未知'
|
|
|
}
|
|
|
const getFormalAuditTagType = (status: number) => {
|
|
|
- const map = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
|
|
|
+ const map: Record<number, string> = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
|
|
|
return map[status] || 'info'
|
|
|
}
|
|
|
|
|
|
@@ -533,8 +581,7 @@ const canEdit = (row: any) => row.preAuditStatus === 0 || row.preAuditStatus ===
|
|
|
const canSubmitPre = (row: any) => row.preAuditStatus === 0 || row.preAuditStatus === 3
|
|
|
const canSubmitFormal = (row: any) => row.preAuditStatus === 2 && row.formalAuditStatus === 0
|
|
|
const canApprove = (row: any) => {
|
|
|
- // 这里简化处理,具体以后端返回的当前审批人是不是自己判断
|
|
|
- // 实际项目中应该拿 row.preCurrentApproverId / row.formalCurrentApproverId 与当前用户比对
|
|
|
+ // 简化处理,实际项目应比对当前审批人是否是自己
|
|
|
return row.preAuditStatus === 1 || row.formalAuditStatus === 1
|
|
|
}
|
|
|
|
|
|
@@ -553,6 +600,7 @@ const createBudgetDetail = (): BudgetDetail => ({
|
|
|
budgetDescription: ''
|
|
|
})
|
|
|
|
|
|
+// formData 中【不包含】任何附件字段
|
|
|
const createForm = () => ({
|
|
|
deptId: null as number | null,
|
|
|
projectLevel: '',
|
|
|
@@ -564,9 +612,6 @@ const createForm = () => ({
|
|
|
goalAndSignificance: '',
|
|
|
mainResearchContent: '',
|
|
|
innovationPoint: '',
|
|
|
- applicationFile: '',
|
|
|
- openingReportFile: '',
|
|
|
- otherFile: '',
|
|
|
status: 0,
|
|
|
remark: '',
|
|
|
involveBudget: 0,
|
|
|
@@ -600,14 +645,29 @@ const openForm = async (project?: any) => {
|
|
|
try {
|
|
|
if (project?.id) {
|
|
|
const data = await getProject(project.id)
|
|
|
+
|
|
|
+ // 关键:先把三个附件字段从 data 中摘出来,避免 ...data 污染 formData
|
|
|
+ const {
|
|
|
+ applicationFile: applicationFileData,
|
|
|
+ openingReportFile: openingReportFileData,
|
|
|
+ otherFile: otherFileData,
|
|
|
+ ...restData
|
|
|
+ } = data
|
|
|
+
|
|
|
formData.value = {
|
|
|
...createForm(),
|
|
|
- ...data,
|
|
|
- scheduleList: data.scheduleList?.length ? data.scheduleList : [createSchedule()],
|
|
|
- budgetList: data.budgetList?.length ? data.budgetList : [createBudgetDetail()]
|
|
|
+ ...restData,
|
|
|
+ scheduleList: restData.scheduleList?.length ? restData.scheduleList : [createSchedule()],
|
|
|
+ budgetList: restData.budgetList?.length ? restData.budgetList : [createBudgetDetail()]
|
|
|
}
|
|
|
+
|
|
|
+ // 附件回显到独立 ref(与 index2.vue 一致)
|
|
|
+ applicationFile.value = normalizeAttachments(applicationFileData, 'APPLICATION')
|
|
|
+ openingReportFile.value = normalizeAttachments(openingReportFileData, 'OPENING_REPORT')
|
|
|
+ otherFile.value = normalizeAttachments(otherFileData, 'OTHER')
|
|
|
} else {
|
|
|
formData.value = createForm()
|
|
|
+ resetAttachments()
|
|
|
}
|
|
|
} finally {
|
|
|
formLoading.value = false
|
|
|
@@ -615,7 +675,7 @@ const openForm = async (project?: any) => {
|
|
|
}
|
|
|
|
|
|
const openDetail = async (row: any) => {
|
|
|
- // 详情可以直接复用 openForm,或者单独开只读弹窗
|
|
|
+ // 详情复用 openForm
|
|
|
openForm(row)
|
|
|
}
|
|
|
|
|
|
@@ -644,7 +704,13 @@ const submitForm = async () => {
|
|
|
formData.value.scheduleList.forEach((item, index) => (item.sort = index))
|
|
|
formData.value.budgetList.forEach((item, index) => (item.sort = index))
|
|
|
|
|
|
- const payload: any = { ...formData.value }
|
|
|
+ // 关键:payload 里 formData 不含附件字段;附件通过 attachments 统一提交
|
|
|
+ // 与 index2.vue 完全一致的写法
|
|
|
+ const payload: any = {
|
|
|
+ ...formData.value,
|
|
|
+ attachments: getAttachments()
|
|
|
+ }
|
|
|
+
|
|
|
if (editingId.value) {
|
|
|
payload.id = editingId.value
|
|
|
await updateProject(payload)
|