admin-vue3/src/views/review/meeting/ProjectDetail.vue

357 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<ContentWrap>
<!-- 页面标题带返回 -->
<div class="page-title">
<span class="back-btn" @click="handleBack">&#8249;</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>