feat: 文件归档功能改为弹窗显示文件列表

- 新增 FileArchiveDialog.vue 弹窗组件,显示项目所有文件
- 弹窗顶部增加下载按钮,点击可打包下载全部文件
- 修改 handleArchive 方法,改为打开弹窗而非直接下载
- 新增 listDownloadUrls API 获取文件列表
This commit is contained in:
吴红兵
2026-03-03 16:15:28 +08:00
parent 4c735f93a0
commit 5baf4061e0
3 changed files with 206 additions and 6 deletions

View File

@@ -347,3 +347,15 @@ export function downloadFileById(fileId: string) {
});
}
/**
* 批量获取文件下载地址列表
* @param purchaseId 采购申请ID
*/
export function listDownloadUrls(purchaseId: string | number) {
return request({
url: '/purchase/purchasingfiles/listDownloadUrls',
method: 'get',
params: { purchaseId }
});
}

View File

@@ -0,0 +1,185 @@
<template>
<el-dialog
v-model="visible"
title="文件归档"
width="900px"
destroy-on-close
append-to-body
>
<template #header>
<div class="dialog-header">
<span class="dialog-title">
<el-icon><FolderOpened /></el-icon>
文件归档 - {{ purchaseNo || purchaseId }}
</span>
<el-button
type="primary"
:loading="downloading"
@click="handleDownloadAll"
>
<el-icon><Download /></el-icon>
下载全部文件
</el-button>
</div>
</template>
<el-table
v-loading="loading"
:data="fileList"
stripe
border
max-height="500px"
class="file-table"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="fileTitle" label="文件名称" min-width="250" show-overflow-tooltip>
<template #default="{ row }">
<div class="file-name">
<el-icon class="file-icon"><Document /></el-icon>
<span>{{ row.fileTitle }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="fileTypeDesc" label="文件类型" width="180" align="center">
<template #default="{ row }">
<el-tag type="info">{{ row.fileTypeDesc || '未知类型' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
icon="Download"
@click="handleDownloadFile(row)"
>
下载
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!loading && fileList.length === 0" class="empty-tip">
<el-empty description="暂无文件" />
</div>
<template #footer>
<el-button @click="visible = false">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { FolderOpened, Download, Document } from '@element-plus/icons-vue'
import { useMessage } from '/@/hooks/message'
import { listDownloadUrls, getArchiveDownloadUrl } from '/@/api/purchase/purchasingrequisition'
import other from '/@/utils/other'
interface FileItem {
id: string
fileTitle: string
fileType: string
fileTypeDesc: string
downloadUrl: string
}
const visible = ref(false)
const loading = ref(false)
const downloading = ref(false)
const purchaseId = ref('')
const purchaseNo = ref('')
const fileList = ref<FileItem[]>([])
const open = async (id: string, no?: string) => {
purchaseId.value = id || ''
purchaseNo.value = no || ''
visible.value = true
await loadFileList()
}
const loadFileList = async () => {
if (!purchaseId.value) {
useMessage().warning('无法获取采购申请ID')
return
}
loading.value = true
try {
const res = await listDownloadUrls(purchaseId.value)
if (res && res.data) {
fileList.value = res.data as FileItem[]
} else {
fileList.value = []
}
} catch (err: any) {
useMessage().error(err?.msg || '获取文件列表失败')
fileList.value = []
} finally {
loading.value = false
}
}
const handleDownloadFile = (row: FileItem) => {
if (row.downloadUrl) {
window.open(row.downloadUrl, '_blank')
} else {
useMessage().warning('文件下载地址不存在')
}
}
const handleDownloadAll = () => {
if (!purchaseId.value) {
useMessage().warning('无法获取采购申请ID')
return
}
downloading.value = true
try {
const url = getArchiveDownloadUrl(purchaseId.value)
const fileName = `归档_${purchaseNo.value || purchaseId.value}.zip`
other.downBlobFile(url, {}, fileName)
} finally {
setTimeout(() => {
downloading.value = false
}, 500)
}
}
defineExpose({
open
})
</script>
<style scoped lang="scss">
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.dialog-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
.file-table {
width: 100%;
}
.file-name {
display: flex;
align-items: center;
gap: 8px;
}
.file-icon {
color: #409eff;
}
.empty-tip {
padding: 40px 0;
}
</style>

View File

@@ -292,6 +292,9 @@
<!-- 实施采购iframe 嵌入 implement.vue供列表与流程页面使用 -->
<ImplementForm ref="implementFormRef" @refresh="getDataList" />
<!-- 文件归档弹窗 -->
<FileArchiveDialog ref="fileArchiveDialogRef" />
<!-- 招标文件审核弹窗 -->
<!-- <DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />-->
@@ -345,7 +348,7 @@
import { ref, reactive, defineAsyncComponent, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj, submitObj, getArchiveDownloadUrl, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl, getDeptMembers, saveRepresentor } from "/@/api/purchase/purchasingrequisition";
import { getPage, delObj, submitObj, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl, getDeptMembers, saveRepresentor, listDownloadUrls } from "/@/api/purchase/purchasingrequisition";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useAuth } from '/@/hooks/auth';
import { getDicts } from '/@/api/admin/dict';
@@ -365,6 +368,7 @@ const ImplementForm = defineAsyncComponent(() => import('./implementForm.vue'));
const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/action-dropdown.vue'));
const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue'));
const FlowCommentTimeline = defineAsyncComponent(() => import('/@/views/jsonflow/comment/timeline.vue'));
const FileArchiveDialog = defineAsyncComponent(() => import('./FileArchiveDialog.vue'));
// const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue'));
// 字典数据和品目树数据
@@ -393,6 +397,7 @@ const currFlowJob = ref<{ id?: number; flowInstId?: number } | null>(null)
const currFlowCommentType = ref<'apply' | 'file'>('apply')
const implementFormRef = ref()
const fileArchiveDialogRef = ref()
/** 采购代表弹窗 */
const representorDialogVisible = ref(false)
@@ -713,16 +718,14 @@ const handleDownloadFileApply = (row: any) => {
other.downBlobFile(url, {}, fileName);
};
/** 文件归档:按文件类型打包下载该申请单下所有附件 */
/** 文件归档:打开弹窗查看文件列表,支持打包下载 */
const handleArchive = (row: any) => {
const id = row?.id ?? row?.purchaseId;
if (id == null || id === '') {
useMessage().warning('无法获取申请单ID');
return;
}
const url = getArchiveDownloadUrl(id);
const fileName = `归档_${row?.purchaseNo || id}.zip`;
other.downBlobFile(url, {}, fileName);
fileArchiveDialogRef.value?.open(String(id), row?.purchaseNo);
};
// 获取字典数据和品目树数据