<template>
|
<el-dialog
|
title="文档归档"
|
v-model="visible"
|
width="650px"
|
destroy-on-close
|
append-to-body
|
>
|
<div class="archive-container" v-loading="loading">
|
<el-empty v-if="!loading && (!archiveGroups || archiveGroups.length === 0)" description="暂无归档附件" />
|
|
<el-collapse v-else v-model="activeNames">
|
<el-collapse-item
|
v-for="(group, index) in archiveGroups"
|
:key="index"
|
:title="`${group.moduleName} (${group.files ? group.files.length : 0})`"
|
:name="index"
|
>
|
<div class="attachment-list">
|
<div
|
v-for="(file, fileIdx) in group.files"
|
:key="fileIdx"
|
class="attachment-item"
|
>
|
<el-icon class="file-icon"><Document /></el-icon>
|
<a
|
:href="getRealUrl(file.url)"
|
target="_blank"
|
class="file-link"
|
:title="file.name"
|
>
|
{{ file.name }}
|
</a>
|
<el-button link type="primary" class="download-btn" @click="handleDownload(file.url, file.name)">下载</el-button>
|
</div>
|
</div>
|
</el-collapse-item>
|
</el-collapse>
|
</div>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="visible = false">关闭</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { ref, watch } from 'vue'
|
import { getProjectAttachments } from '@/api/projectManagement/project'
|
import { Document } from '@element-plus/icons-vue'
|
|
const props = defineProps({
|
modelValue: {
|
type: Boolean,
|
default: false
|
},
|
projectId: {
|
type: [Number, String],
|
default: null
|
}
|
})
|
|
const emit = defineEmits(['update:modelValue'])
|
|
const visible = ref(false)
|
const loading = ref(false)
|
const archiveGroups = ref([])
|
const activeNames = ref([])
|
|
watch(() => props.modelValue, (val) => {
|
visible.value = val
|
if (val && props.projectId) {
|
fetchAttachments()
|
} else {
|
archiveGroups.value = []
|
activeNames.value = []
|
}
|
})
|
|
watch(() => visible.value, (val) => {
|
emit('update:modelValue', val)
|
})
|
|
const fetchAttachments = async () => {
|
loading.value = true
|
try {
|
const res = await getProjectAttachments(props.projectId)
|
if (res.code === 200) {
|
archiveGroups.value = res.data || []
|
// 默认展开所有面板
|
activeNames.value = archiveGroups.value.map((_, index) => index)
|
}
|
} catch (error) {
|
console.error('获取附件失败', error)
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 获取真实地址(处理可能的相对路径前缀)
|
const getRealUrl = (url) => {
|
if (!url) return '#'
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
return url
|
}
|
return import.meta.env.VITE_APP_BASE_API + url
|
}
|
|
// 提取文件名(兼容旧逻辑)
|
const getFileName = (url) => {
|
if (!url) return '未知文件'
|
const parts = url.split('/')
|
const name = parts[parts.length - 1]
|
// 处理可能的时间戳前缀
|
const nameParts = name.split('_')
|
return nameParts.length > 1 ? nameParts.slice(1).join('_') : name
|
}
|
|
const handleDownload = (url, name) => {
|
const realUrl = getRealUrl(url)
|
const a = document.createElement('a')
|
a.style.display = 'none'
|
a.href = realUrl
|
a.download = name || getFileName(url)
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
}
|
</script>
|
|
<style scoped>
|
.archive-container {
|
min-height: 200px;
|
}
|
.attachment-list {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
padding: 5px 0;
|
}
|
.attachment-item {
|
display: flex;
|
align-items: center;
|
padding: 8px 12px;
|
background-color: #f8f9fa;
|
border-radius: 4px;
|
transition: background-color 0.3s;
|
}
|
.attachment-item:hover {
|
background-color: #f0f2f5;
|
}
|
.file-icon {
|
font-size: 16px;
|
color: #909399;
|
margin-right: 8px;
|
}
|
.file-link {
|
flex: 1;
|
color: #606266;
|
text-decoration: none;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
margin-right: 15px;
|
}
|
.file-link:hover {
|
color: #409EFF;
|
text-decoration: underline;
|
}
|
.download-btn {
|
margin-left: auto;
|
}
|
</style>
|