feat: 前端集成更新材料功能和PDF预览

- 列表页添加更新材料按钮(更多操作中)
- 添加updateFiles API接口
- 文件归档弹窗添加PDF预览功能

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
吴红兵
2026-03-03 22:44:39 +08:00
parent 36ad8f14e4
commit d84b938dc5
3 changed files with 95 additions and 6 deletions

View File

@@ -359,3 +359,15 @@ export function listDownloadUrls(purchaseId: string | number) {
}); });
} }
/**
* 更新采购材料(部门申请人或负责人重新上传文件)
* @param data 包含purchaseId和fileIds的对象
*/
export function updateFiles(data: { purchaseId: string; fileIds: string[] }) {
return request({
url: '/purchase/purchasingfiles/updateFiles',
method: 'post',
data
});
}

View File

@@ -32,7 +32,7 @@
class="file-table" class="file-table"
> >
<el-table-column type="index" label="序号" width="60" align="center" /> <el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="fileTitle" label="文件名称" min-width="250" show-overflow-tooltip> <el-table-column prop="fileTitle" label="文件名称" min-width="220" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<div class="file-name"> <div class="file-name">
<el-icon class="file-icon"><Document /></el-icon> <el-icon class="file-icon"><Document /></el-icon>
@@ -40,13 +40,21 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="fileTypeDesc" label="文件类型" width="180" align="center"> <el-table-column prop="fileTypeDesc" label="文件类型" width="160" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag type="info">{{ row.fileTypeDesc || '未知类型' }}</el-tag> <el-tag type="info">{{ row.fileTypeDesc || '未知类型' }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right"> <el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button
type="primary"
link
icon="View"
@click="handlePreview(row)"
>
预览
</el-button>
<el-button <el-button
type="primary" type="primary"
link link
@@ -67,6 +75,26 @@
<el-button @click="visible = false">关闭</el-button> <el-button @click="visible = false">关闭</el-button>
</template> </template>
</el-dialog> </el-dialog>
<!-- PDF预览弹窗 -->
<el-dialog
v-model="previewVisible"
title="文件预览"
width="90%"
top="5vh"
destroy-on-close
append-to-body
class="preview-dialog"
>
<div class="preview-container">
<iframe
v-if="previewUrl"
:src="previewUrl"
class="preview-iframe"
frameborder="0"
/>
</div>
</el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -87,6 +115,8 @@ interface FileItem {
const visible = ref(false) const visible = ref(false)
const loading = ref(false) const loading = ref(false)
const downloading = ref(false) const downloading = ref(false)
const previewVisible = ref(false)
const previewUrl = ref('')
const purchaseId = ref('') const purchaseId = ref('')
const purchaseNo = ref('') const purchaseNo = ref('')
const fileList = ref<FileItem[]>([]) const fileList = ref<FileItem[]>([])
@@ -119,6 +149,29 @@ const loadFileList = async () => {
} }
} }
const isPdfFile = (fileName: string): boolean => {
if (!fileName) return false
const ext = fileName.toLowerCase().split('.').pop()
return ext === 'pdf'
}
const handlePreview = (row: FileItem) => {
if (!row.downloadUrl) {
useMessage().warning('文件预览地址不存在')
return
}
if (!isPdfFile(row.fileTitle)) {
useMessage().info('仅支持PDF格式文件预览将为您下载文件')
handleDownloadFile(row)
return
}
// 使用iframe预览PDF通过后端下载接口获取文件流
previewUrl.value = row.downloadUrl
previewVisible.value = true
}
const handleDownloadFile = (row: FileItem) => { const handleDownloadFile = (row: FileItem) => {
if (row.downloadUrl) { if (row.downloadUrl) {
window.open(row.downloadUrl, '_blank') window.open(row.downloadUrl, '_blank')
@@ -182,4 +235,14 @@ defineExpose({
.empty-tip { .empty-tip {
padding: 40px 0; padding: 40px 0;
} }
.preview-container {
width: 100%;
height: calc(90vh - 120px);
}
.preview-iframe {
width: 100%;
height: 100%;
}
</style> </style>

View File

@@ -295,6 +295,9 @@
<!-- 文件归档弹窗 --> <!-- 文件归档弹窗 -->
<FileArchiveDialog ref="fileArchiveDialogRef" /> <FileArchiveDialog ref="fileArchiveDialogRef" />
<!-- 更新材料弹窗 -->
<UpdateFilesDialog ref="updateFilesDialogRef" @refresh="getDataList" />
<!-- 招标文件审核弹窗 --> <!-- 招标文件审核弹窗 -->
<!-- <DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />--> <!-- <DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />-->
@@ -348,12 +351,12 @@
import { ref, reactive, defineAsyncComponent, onMounted, computed } from 'vue' import { ref, reactive, defineAsyncComponent, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj, submitObj, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl, getDeptMembers, saveRepresentor, listDownloadUrls } from "/@/api/purchase/purchasingrequisition"; import { getPage, delObj, submitObj, getApplyTemplateDownloadUrl, getFileApplyTemplateDownloadUrl, getDeptMembers, saveRepresentor, listDownloadUrls, updateFiles } from "/@/api/purchase/purchasingrequisition";
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from "/@/hooks/message";
import { useAuth } from '/@/hooks/auth'; import { useAuth } from '/@/hooks/auth';
import { getDicts } from '/@/api/admin/dict'; import { getDicts } from '/@/api/admin/dict';
import { getTree } from '/@/api/purchase/purchasingcategory'; import { getTree } from '/@/api/purchase/purchasingcategory';
import { List, Document, DocumentCopy, Search, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked, Edit, Delete, Upload, FolderOpened, Download, User } from '@element-plus/icons-vue' import { List, Document, DocumentCopy, Search, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked, Edit, Delete, Upload, FolderOpened, Download, User, RefreshRight } from '@element-plus/icons-vue'
import other from '/@/utils/other' import other from '/@/utils/other'
import { Session } from '/@/utils/storage' import { Session } from '/@/utils/storage'
@@ -369,6 +372,7 @@ const ActionDropdown = defineAsyncComponent(() => import('/@/components/tools/ac
const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue')); const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue'));
const FlowCommentTimeline = defineAsyncComponent(() => import('/@/views/jsonflow/comment/timeline.vue')); const FlowCommentTimeline = defineAsyncComponent(() => import('/@/views/jsonflow/comment/timeline.vue'));
const FileArchiveDialog = defineAsyncComponent(() => import('./FileArchiveDialog.vue')); const FileArchiveDialog = defineAsyncComponent(() => import('./FileArchiveDialog.vue'));
const UpdateFilesDialog = defineAsyncComponent(() => import('./UpdateFilesDialog.vue'));
// const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue')); // const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue'));
// 字典数据和品目树数据 // 字典数据和品目树数据
@@ -398,6 +402,7 @@ const currFlowCommentType = ref<'apply' | 'file'>('apply')
const implementFormRef = ref() const implementFormRef = ref()
const fileArchiveDialogRef = ref() const fileArchiveDialogRef = ref()
const updateFilesDialogRef = ref()
/** 采购代表弹窗 */ /** 采购代表弹窗 */
const representorDialogVisible = ref(false) const representorDialogVisible = ref(false)
@@ -636,6 +641,12 @@ const getActionMenuItems = (row: any) => {
icon: User, icon: User,
visible: () => isDeptAuditRole.value, visible: () => isDeptAuditRole.value,
}, },
{
command: 'updateFiles',
label: '更新材料',
icon: RefreshRight,
visible: () => isCompleted && hasAuth('purchase_purchasingapply_edit'),
},
// { // {
// command: 'downloadFileApply', // command: 'downloadFileApply',
// label: '下载文件审批表', // label: '下载文件审批表',
@@ -691,6 +702,9 @@ const handleMoreCommand = (command: string, row: any) => {
case 'representor': case 'representor':
openRepresentorDialog(row); openRepresentorDialog(row);
break; break;
case 'updateFiles':
handleUpdateFiles(row);
break;
} }
}; };