357 lines
9.1 KiB
Vue
357 lines
9.1 KiB
Vue
<template>
|
||
<ContentWrap>
|
||
<!-- 页面标题带返回 -->
|
||
<div class="page-title">
|
||
<span class="back-btn" @click="handleBack">‹</span>
|
||
项目详情
|
||
</div>
|
||
|
||
<!-- 蓝色信息面板 -->
|
||
<div class="info-panel">
|
||
<div class="info-panel-title">{{ projectInfo.projectTitle || '-' }}</div>
|
||
<div class="info-panel-meta">
|
||
<div class="meta-left">
|
||
<span v-if="projectInfo.startTime || projectInfo.endTime" class="meta-tag">
|
||
{{ projectInfo.startTime || '' }}{{ projectInfo.endTime ? ` - ${projectInfo.endTime}` : '' }}
|
||
</span>
|
||
<span class="meta-item">会中序号:{{ projectInfo.seqNo ?? '-' }}</span>
|
||
</div>
|
||
<div class="meta-right">所属会议:{{ projectInfo.meetingName || '-' }}</div>
|
||
</div>
|
||
<div class="info-panel-footer">
|
||
<div class="info-footer-item">
|
||
<span class="footer-label">议程分类</span>
|
||
<span class="footer-value">{{ projectInfo.agendaCategory || '-' }}</span>
|
||
</div>
|
||
<div class="info-footer-item">
|
||
<span class="footer-label">报告人</span>
|
||
<span class="footer-value">
|
||
{{ projectInfo.reporter || '-' }}
|
||
<em v-if="projectInfo.reporterUnit">{{ projectInfo.reporterUnit }}</em>
|
||
</span>
|
||
</div>
|
||
<div class="info-footer-item">
|
||
<span class="footer-label">主持人</span>
|
||
<span class="footer-value">{{ projectInfo.host || '-' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 上传资料区 -->
|
||
<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"
|
||
>
|
||
<button class="btn-upload">上传资料</button>
|
||
</el-upload>
|
||
<span class="upload-hint">支持 doc、docx、xls、xlsx、pdf、ppt、pptx,单文件不超过 500MB</span>
|
||
</div>
|
||
|
||
<el-table :data="fileList" v-loading="fileLoading" border class="file-table">
|
||
<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 }">
|
||
<a class="op-link" @click="handleDownload(row)">下载</a>
|
||
<a class="op-link op-danger" @click="handleDelete(row)">删除</a>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-empty v-if="!fileLoading && fileList.length === 0" description="暂无资料文件" :image-size="60" />
|
||
|
||
<!-- 底部操作 -->
|
||
<div class="form-footer">
|
||
<button class="btn-default" @click="handleBack">返回</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)
|
||
|
||
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 > 500 * 1024 * 1024) {
|
||
ElMessage.error('文件大小不能超过 500MB')
|
||
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>
|
||
/* ── 页面标题 ── */
|
||
.page-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 22px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 14px;
|
||
border-bottom: 1px solid #e1e7f0;
|
||
}
|
||
.back-btn {
|
||
font-size: 28px;
|
||
color: #295abc;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
margin-right: 2px;
|
||
font-weight: 400;
|
||
}
|
||
.back-btn:hover { opacity: 0.75; }
|
||
|
||
/* ── 蓝色信息面板 ── */
|
||
.info-panel {
|
||
background: linear-gradient(135deg, rgba(41, 90, 188, 0.06) 0%, rgba(41, 90, 188, 0.03) 100%);
|
||
border: 1px solid rgba(41, 90, 188, 0.15);
|
||
border-radius: 8px;
|
||
padding: 18px 20px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.info-panel-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #295abc;
|
||
margin-bottom: 10px;
|
||
}
|
||
.info-panel-meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 14px;
|
||
}
|
||
.meta-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
}
|
||
.meta-tag {
|
||
display: inline-block;
|
||
padding: 2px 10px;
|
||
background-color: rgba(41, 90, 188, 0.1);
|
||
color: #295abc;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
}
|
||
.meta-item {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
.meta-right {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
.info-panel-footer {
|
||
display: flex;
|
||
gap: 32px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid rgba(41, 90, 188, 0.12);
|
||
}
|
||
.info-footer-item {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
}
|
||
.footer-label {
|
||
font-size: 13px;
|
||
color: #999;
|
||
white-space: nowrap;
|
||
}
|
||
.footer-value {
|
||
font-size: 14px;
|
||
color: #333;
|
||
}
|
||
.footer-value em {
|
||
font-style: normal;
|
||
color: #999;
|
||
font-size: 12px;
|
||
margin-left: 6px;
|
||
}
|
||
|
||
/* ── 资料区标题 ── */
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.section-title {
|
||
position: relative;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
padding-left: 10px;
|
||
}
|
||
.section-title::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 2px;
|
||
bottom: 2px;
|
||
width: 3px;
|
||
border-radius: 2px;
|
||
background-color: #295abc;
|
||
}
|
||
|
||
.btn-upload {
|
||
height: 34px;
|
||
padding: 0 14px;
|
||
background-color: #fff;
|
||
border: 1px solid #295abc;
|
||
border-radius: 6px;
|
||
color: #295abc;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.btn-upload:hover {
|
||
background-color: rgba(41, 90, 188, 0.08);
|
||
}
|
||
|
||
.upload-hint {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
/* ── 操作链接 ── */
|
||
.op-link {
|
||
display: inline-block;
|
||
color: #295abc;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
margin: 0 4px;
|
||
transition: opacity 0.2s;
|
||
}
|
||
.op-link:hover { opacity: 0.8; text-decoration: underline; }
|
||
.op-danger { color: #fc4f54; }
|
||
|
||
/* ── 文件表格 ── */
|
||
:deep(.file-table .el-table__header-wrapper th) {
|
||
background-color: #eef2fb;
|
||
color: #333;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
border-color: #e1e7f0;
|
||
}
|
||
:deep(.file-table .el-table__body td) {
|
||
font-size: 14px;
|
||
color: #333;
|
||
border-color: #e1e7f0;
|
||
}
|
||
:deep(.file-table .el-table__body tr:hover > td) {
|
||
background-color: rgba(41, 90, 188, 0.04);
|
||
}
|
||
|
||
/* ── 底部按钮 ── */
|
||
.form-footer {
|
||
margin-top: 24px;
|
||
}
|
||
.btn-default {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 36px;
|
||
padding: 0 20px;
|
||
background-color: #fff;
|
||
border: 1px solid #d5d5d5;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
color: #333;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.btn-default:hover {
|
||
background-color: rgba(41, 90, 188, 0.08);
|
||
border-color: #295abc;
|
||
color: #295abc;
|
||
}
|
||
</style>
|