feat(review-frontend): 项目资料按会前会后分组展示

pull/874/head
Codewoc 2026-03-26 08:49:06 +08:00
parent 261173e76e
commit 1ba4d43de4
1 changed files with 74 additions and 21 deletions

View File

@ -37,21 +37,37 @@
<div class="material-card" v-loading="loading">
<div class="material-toolbar">
<div class="material-type-wrap">
<span class="label">类型</span>
<el-select :model-value="materialSummary.agendaType || projectInfo.agendaCategory" disabled class="type-select">
<el-option
v-for="option in REVIEW_AGENDA_CATEGORY_OPTIONS"
:key="option"
:label="option"
:value="option"
/>
</el-select>
</div>
<div class="toolbar-title">上传资料</div>
<el-button link type="primary" @click="handleDownloadTemplate"></el-button>
</div>
<el-table :data="materialSummary.materials" border class="material-table" empty-text="">
<div class="group-title">会前资料</div>
<el-table :data="beforeMeetingMaterials" border class="material-table" empty-text="">
<el-table-column label="资料类型" min-width="220">
<template #default="{ row }">
<span>
{{ row.materialName }}
<span v-if="row.required" class="required-flag">*</span>
</span>
</template>
</el-table-column>
<el-table-column label="资料文件" min-width="300">
<template #default="{ row }">
<span v-if="row.latestFile" class="file-name">{{ row.latestFile.fileName }}</span>
<span v-else class="file-empty">未上传</span>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center">
<template #default="{ row }">
<a class="op-link" @click="triggerUpload(row)">{{ row.latestFile ? '' : '' }}</a>
<a class="op-link" :class="{ disabled: !row.latestFile }" @click="row.latestFile && handleDownload(row.latestFile)">下载</a>
<a class="op-link" @click="openHistory(row)"></a>
</template>
</el-table-column>
</el-table>
<div class="group-title group-after">会后资料</div>
<el-table :data="afterMeetingMaterials" border class="material-table" empty-text="">
<el-table-column label="资料类型" min-width="220">
<template #default="{ row }">
<span>
@ -106,12 +122,11 @@
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getReviewMeeting } from '@/api/review/meeting'
import {
REVIEW_AGENDA_CATEGORY_OPTIONS,
downloadProjectTemplateBundle,
getProjectMaterialHistory,
getProjectMaterialSummary,
@ -156,6 +171,41 @@ const uploadTarget = ref<ReviewMeetingMaterialItemRespVO>()
const historyVisible = ref(false)
const historyList = ref<ReviewMeetingMaterialHistoryRespVO[]>([])
const PHASE_MAPPING: Record<string, Record<string, 'before' | 'after'>> = {
项目立项: {
PROJECT_ARGUMENT_DOC: 'before',
PROJECT_DEFENSE_PPT: 'before',
PROJECT_EXPERT_OPINION_SIGNED: 'after'
},
预验收: {
PRE_PROJECT_CONTRACT: 'before',
PRE_REQUIREMENT_SPEC: 'before',
PRE_ACCEPTANCE_REPORT: 'before',
PRE_USER_FUNCTION_TEST: 'before',
PRE_SERVICE_CHECKLIST: 'before'
},
项目终验: {
FINAL_ACCEPTANCE_DOC: 'before',
FINAL_DEFENSE_PPT: 'before',
FINAL_DRAFT_EXPERT_OPINION: 'before',
FINAL_SIGNED_EXPERT_OPINION: 'after',
FINAL_RECTIFICATION_OPINION: 'after',
FINAL_RECTIFICATION_REPLY: 'after'
}
}
const beforeMeetingMaterials = computed(() => {
const agendaType = materialSummary.value.agendaType
const agendaMap = PHASE_MAPPING[agendaType] || {}
return materialSummary.value.materials.filter((item) => agendaMap[item.materialCode] !== 'after')
})
const afterMeetingMaterials = computed(() => {
const agendaType = materialSummary.value.agendaType
const agendaMap = PHASE_MAPPING[agendaType] || {}
return materialSummary.value.materials.filter((item) => agendaMap[item.materialCode] === 'after')
})
const loadSummary = async () => {
loading.value = true
try {
@ -360,18 +410,21 @@ onMounted(async () => {
margin-bottom: 14px;
}
.material-type-wrap {
display: flex;
align-items: center;
gap: 8px;
.toolbar-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.label {
.group-title {
margin: 8px 0 10px;
font-size: 14px;
font-weight: 600;
color: #606266;
}
.type-select {
width: 260px;
.group-after {
margin-top: 18px;
}
.required-flag {