feat(meeting): 新增项目详情页 ProjectDetail.vue + 路由
- 新增 ProjectDetail.vue:el-descriptions 展示项目基本信息 + 文件资料表格 - 支持上传/下载/删除资料文件,复用 project API - 项目基本信息通过 router state 从列表页携带(无需额外接口) - remaining.ts 新增 ReviewProjectDetail 路由 - ProjectList.vue 更新 goToDetail 携带完整 row 数据到 state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>pull/874/head
parent
db5253b3e6
commit
75be833554
|
|
@ -771,6 +771,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
activeMenu: '/review/meeting'
|
||||
},
|
||||
component: () => import('@/views/review/meeting/MeetingEdit.vue')
|
||||
},
|
||||
{
|
||||
path: 'review-meeting/project/:meetingId(\\d+)/detail/:projectId(\\d+)',
|
||||
name: 'ReviewProjectDetail',
|
||||
meta: {
|
||||
title: '项目详情',
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/review/meeting'
|
||||
},
|
||||
component: () => import('@/views/review/meeting/ProjectDetail.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 项目基本信息 -->
|
||||
<div class="detail-header">
|
||||
<span class="page-title">项目详情</span>
|
||||
</div>
|
||||
|
||||
<el-descriptions :column="3" border size="small" class="project-info">
|
||||
<el-descriptions-item label="项目标题" :span="3">
|
||||
<span class="project-title-text">{{ projectInfo.projectTitle || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="会中序号">{{ projectInfo.seqNo ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="起止时间">
|
||||
{{ projectInfo.startTime && projectInfo.endTime ? `${projectInfo.startTime} - ${projectInfo.endTime}` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="议程分类">{{ projectInfo.agendaCategory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="汇报人">{{ projectInfo.reporter || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报告单位">{{ projectInfo.reporterUnit || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主持人">{{ projectInfo.host || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属会议" :span="2">{{ projectInfo.meetingName || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 项目资料 -->
|
||||
<div class="section-header">
|
||||
<span class="section-title">项目资料</span>
|
||||
<el-upload
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:show-file-list="false"
|
||||
multiple
|
||||
accept=".doc,.docx,.xls,.xlsx,.pdf,.ppt,.pptx"
|
||||
>
|
||||
<el-button type="primary" plain size="small">上传资料</el-button>
|
||||
</el-upload>
|
||||
<el-text type="info" size="small">支持 doc、docx、xls、xlsx、pdf、ppt、pptx,单文件不超过 50MB</el-text>
|
||||
</div>
|
||||
|
||||
<el-table :data="fileList" v-loading="fileLoading" border size="small">
|
||||
<el-table-column label="文件名" prop="fileName" show-overflow-tooltip min-width="220" />
|
||||
<el-table-column label="大小" width="90" align="right">
|
||||
<template #default="{ row }">{{ formatFileSize(row.fileSize) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" prop="fileType" width="70" align="center" />
|
||||
<el-table-column label="上传人" prop="creator" width="90" />
|
||||
<el-table-column label="上传时间" prop="createTime" width="165" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleDownload(row)">下载</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!fileLoading && fileList.length === 0" description="暂无资料文件" :image-size="60" />
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<div class="form-footer">
|
||||
<el-button @click="handleBack">返回</el-button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import {
|
||||
getMeetingFileList,
|
||||
uploadMeetingFile,
|
||||
deleteMeetingFile,
|
||||
type ReviewMeetingFileRespVO
|
||||
} from '@/api/review/project'
|
||||
|
||||
defineOptions({ name: 'ReviewProjectDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const reviewMeetingId = Number(route.params.meetingId)
|
||||
const reviewMeetingProjectId = Number(route.params.projectId)
|
||||
|
||||
// 从 history state 读取项目基本信息(由 ProjectList.vue 跳转时携带)
|
||||
const state = window.history.state || {}
|
||||
const projectInfo = ref({
|
||||
projectTitle: state.projectTitle as string | undefined,
|
||||
seqNo: state.seqNo as number | undefined,
|
||||
startTime: state.startTime as string | undefined,
|
||||
endTime: state.endTime as string | undefined,
|
||||
agendaCategory: state.agendaCategory as string | undefined,
|
||||
reporter: state.reporter as string | undefined,
|
||||
reporterUnit: state.reporterUnit as string | undefined,
|
||||
host: state.host as string | undefined,
|
||||
meetingName: state.meetingName as string | undefined
|
||||
})
|
||||
|
||||
const fileList = ref<ReviewMeetingFileRespVO[]>([])
|
||||
const fileLoading = ref(false)
|
||||
|
||||
const loadFiles = async () => {
|
||||
fileLoading.value = true
|
||||
try {
|
||||
fileList.value = await getMeetingFileList(reviewMeetingProjectId)
|
||||
} finally {
|
||||
fileLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = async (uploadFile: UploadFile) => {
|
||||
if (!uploadFile.raw) return
|
||||
if (uploadFile.raw.size > 50 * 1024 * 1024) {
|
||||
ElMessage.error('文件大小不能超过 50MB')
|
||||
return
|
||||
}
|
||||
fileLoading.value = true
|
||||
try {
|
||||
await uploadMeetingFile(reviewMeetingId, reviewMeetingProjectId, uploadFile.raw)
|
||||
ElMessage.success('上传成功')
|
||||
await loadFiles()
|
||||
} catch {
|
||||
ElMessage.error('上传失败')
|
||||
} finally {
|
||||
fileLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = (row: ReviewMeetingFileRespVO) => {
|
||||
window.open(row.fileUrl, '_blank')
|
||||
}
|
||||
|
||||
const handleDelete = async (row: ReviewMeetingFileRespVO) => {
|
||||
await ElMessageBox.confirm(`确定要删除文件「${row.fileName}」吗?`, '提示', { type: 'warning' })
|
||||
await deleteMeetingFile(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (!bytes) return '-'
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadFiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.page-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.project-info {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.project-title-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.form-footer {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -236,7 +236,17 @@ const goToDetail = (row: ReviewMeetingProjectRespVO) => {
|
|||
router.push({
|
||||
name: 'ReviewProjectDetail',
|
||||
params: { meetingId: reviewMeetingId, projectId: row.id },
|
||||
query: { projectTitle: row.projectTitle }
|
||||
state: {
|
||||
projectTitle: row.projectTitle,
|
||||
seqNo: row.seqNo,
|
||||
startTime: row.startTime,
|
||||
endTime: row.endTime,
|
||||
agendaCategory: row.agendaCategory,
|
||||
reporter: row.reporter,
|
||||
reporterUnit: row.reporterUnit,
|
||||
host: row.host,
|
||||
meetingName: meetingInfo.value?.name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue