更新采购申请
This commit is contained in:
@@ -0,0 +1,520 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose">
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 采购需求文件 -->
|
||||
<el-tab-pane label="采购需求文件" name="requirement">
|
||||
<el-table :data="requirementFiles" stripe v-loading="requirementLoading">
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="fileTitle" label="文件名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="fileType" label="文件类型" width="120">
|
||||
<template #default="scope">
|
||||
{{ getFileTypeLabel(scope.row.fileType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="Download" @click="handleDownloadRequirement(scope.row)">
|
||||
下载
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!requirementLoading && requirementFiles.length === 0" description="暂无采购需求文件" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 招标文件 -->
|
||||
<el-tab-pane label="招标文件" name="doc">
|
||||
<div class="doc-header">
|
||||
<!-- 状态显示 -->
|
||||
<el-tag :type="getStatusType(statusField)" class="mr-4">
|
||||
{{ getStatusLabel(statusField) }}
|
||||
</el-tag>
|
||||
<!-- 上传按钮 - 仅招标代理且可上传时显示 -->
|
||||
<el-upload
|
||||
v-if="canUpload"
|
||||
ref="uploadRef"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:data="uploadData"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:before-upload="beforeUpload"
|
||||
:on-change="handleFileChange"
|
||||
:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:show-file-list="false"
|
||||
accept=".doc,.docx,.pdf">
|
||||
<el-button type="primary" icon="Upload">
|
||||
{{ isReturned ? '重新上传' : '上传文件' }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<!-- 提交按钮 - 有文件时显示 -->
|
||||
<el-button
|
||||
v-if="canUpload && fileList.length > 0"
|
||||
type="success"
|
||||
:loading="uploadSubmitting"
|
||||
@click="handleUploadSubmit"
|
||||
class="ml-2">
|
||||
{{ isReturned ? '重新上传并提交' : '上传并提交' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<el-table :data="docList" stripe v-loading="docLoading">
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="fileName" label="文件名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="version" label="版本" width="80" align="center" />
|
||||
<el-table-column prop="uploadByName" label="上传人" width="100" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="160" />
|
||||
<el-table-column label="状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)" size="small">
|
||||
{{ getStatusLabel(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="Download" @click="handleDownloadDoc(scope.row)">
|
||||
下载
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!docLoading && docList.length === 0" description="暂无招标文件" />
|
||||
|
||||
<!-- 状态提示 -->
|
||||
<el-alert v-if="mode === 'agent' && !canUpload" type="warning" :closable="false" class="mt-4">
|
||||
<template #title>
|
||||
<span v-if="isReviewing">文件审核中,请等待审核结果</span>
|
||||
<span v-else-if="isConfirming">文件审核中,请等待确认</span>
|
||||
<span v-else-if="isCompleted">文件审核已完成</span>
|
||||
<span v-else>当前状态不允许上传</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 审核记录 -->
|
||||
<el-tab-pane label="审核记录" name="audit">
|
||||
<AuditRecordList :apply-id="applyId" ref="auditRecordListRef" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 操作区域 -->
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button v-if="canConfirm" type="success" @click="handleConfirm">确认无误</el-button>
|
||||
<el-button v-if="canReturn" type="warning" @click="handleReturn">退回修改</el-button>
|
||||
<el-button v-if="canComplete" type="primary" @click="handleComplete">确认流程结束</el-button>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 退回原因弹窗 -->
|
||||
<el-dialog v-model="returnDialogVisible" title="退回原因" width="400px" append-to-body>
|
||||
<el-form>
|
||||
<el-form-item label="退回原因">
|
||||
<el-input v-model="returnRemark" type="textarea" :rows="3" placeholder="请输入退回原因" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="returnDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitReturn">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DocProcessDialog">
|
||||
import { ref, computed, defineAsyncComponent } from 'vue'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import {
|
||||
getRequirementFiles,
|
||||
getDocList,
|
||||
uploadDoc,
|
||||
reuploadDoc,
|
||||
confirmDoc,
|
||||
returnDoc,
|
||||
completeDoc,
|
||||
getAvailableActions,
|
||||
downloadDocById,
|
||||
downloadFileById
|
||||
} from '/@/api/purchase/docProcess'
|
||||
import type { UploadInstance, UploadProps, UploadUserFile } from 'element-plus'
|
||||
|
||||
const AuditRecordList = defineAsyncComponent(() => import('./AuditRecordList.vue'))
|
||||
|
||||
const props = defineProps<{
|
||||
mode: 'agent' | 'audit'
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const visible = ref(false)
|
||||
const activeTab = ref('requirement')
|
||||
const applyId = ref<string | number>('')
|
||||
const rowData = ref<any>({})
|
||||
const requirementFiles = ref<any[]>([])
|
||||
const requirementLoading = ref(false)
|
||||
const docList = ref<any[]>([])
|
||||
const docLoading = ref(false)
|
||||
const auditRecordListRef = ref()
|
||||
const availableActions = ref<string[]>([])
|
||||
|
||||
// 上传相关
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
const uploadSubmitting = ref(false)
|
||||
|
||||
// 退回相关
|
||||
const returnDialogVisible = ref(false)
|
||||
const returnRemark = ref('')
|
||||
|
||||
// 弹窗标题
|
||||
const dialogTitle = computed(() => {
|
||||
return props.mode === 'agent' ? `处理项目 - ${rowData.value.purchaseNo || ''}` : '招标文件审核'
|
||||
})
|
||||
|
||||
// 状态字段(两个模式使用不同字段名)
|
||||
const statusField = computed(() => {
|
||||
return props.mode === 'agent' ? rowData.value.status : rowData.value.docAuditStatus
|
||||
})
|
||||
|
||||
// 是否可以上传
|
||||
const canUpload = computed(() => {
|
||||
if (props.mode !== 'agent') return false
|
||||
const status = statusField.value
|
||||
return status === 'PENDING_UPLOAD' || status === 'RETURNED'
|
||||
})
|
||||
|
||||
// 是否可确认
|
||||
const canConfirm = computed(() => availableActions.value.includes('confirm'))
|
||||
|
||||
// 是否可退回
|
||||
const canReturn = computed(() => availableActions.value.includes('return'))
|
||||
|
||||
// 是否可完成
|
||||
const canComplete = computed(() => availableActions.value.includes('complete'))
|
||||
|
||||
// 状态快捷判断
|
||||
const isReturned = computed(() => statusField.value === 'RETURNED')
|
||||
const isReviewing = computed(() => ['ASSET_REVIEWING', 'DEPT_REVIEWING', 'AUDIT_REVIEWING'].includes(statusField.value))
|
||||
const isConfirming = computed(() => statusField.value === 'ASSET_CONFIRMING')
|
||||
const isCompleted = computed(() => statusField.value === 'COMPLETED')
|
||||
|
||||
// 上传配置
|
||||
const uploadAction = computed(() => {
|
||||
const baseUrl = import.meta.env.VITE_API_URL || ''
|
||||
return `${baseUrl}/purchase/purchasingfiles/upload`
|
||||
})
|
||||
|
||||
const uploadHeaders = computed(() => {
|
||||
const token = Session.getToken()
|
||||
return {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'TENANT-ID': Session.getTenant() || '1'
|
||||
}
|
||||
})
|
||||
|
||||
const uploadData = computed(() => ({
|
||||
fileType: '130', // 招标文件类型
|
||||
purchaseId: applyId.value || ''
|
||||
}))
|
||||
|
||||
const open = async (row: any) => {
|
||||
visible.value = true
|
||||
activeTab.value = 'requirement'
|
||||
rowData.value = { ...row }
|
||||
requirementFiles.value = []
|
||||
docList.value = []
|
||||
returnRemark.value = ''
|
||||
fileList.value = []
|
||||
|
||||
// 获取申请ID(兼容 id 和 applyId 两种字段名)
|
||||
applyId.value = row.applyId || row.id
|
||||
|
||||
// 加载可执行操作
|
||||
try {
|
||||
const actionsRes = await getAvailableActions(applyId.value)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} catch (e) {
|
||||
availableActions.value = []
|
||||
}
|
||||
|
||||
// 加载采购需求文件
|
||||
loadRequirementFiles()
|
||||
// 加载招标文件
|
||||
loadDocList()
|
||||
}
|
||||
|
||||
const loadRequirementFiles = async () => {
|
||||
if (!applyId.value) return
|
||||
requirementLoading.value = true
|
||||
try {
|
||||
const res = await getRequirementFiles(applyId.value)
|
||||
requirementFiles.value = res.data || []
|
||||
} catch (e) {
|
||||
requirementFiles.value = []
|
||||
} finally {
|
||||
requirementLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadDocList = async () => {
|
||||
if (!applyId.value) return
|
||||
docLoading.value = true
|
||||
try {
|
||||
const res = await getDocList(applyId.value)
|
||||
docList.value = res.data || []
|
||||
} catch (e) {
|
||||
docList.value = []
|
||||
} finally {
|
||||
docLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadRequirement = async (row: any) => {
|
||||
try {
|
||||
const res = await downloadFileById(row.id)
|
||||
const fileName = row.fileName || row.fileTitle || 'download'
|
||||
const blob = new Blob([res])
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadDoc = async (row: any) => {
|
||||
try {
|
||||
const res = await downloadDocById(row.id)
|
||||
const fileName = row.fileName || 'download'
|
||||
const blob = new Blob([res])
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
const allowedTypes = ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/pdf']
|
||||
const allowedExtensions = ['.doc', '.docx', '.pdf']
|
||||
const fileExt = rawFile.name.substring(rawFile.name.lastIndexOf('.')).toLowerCase()
|
||||
|
||||
if (!allowedTypes.includes(rawFile.type) && !allowedExtensions.includes(fileExt)) {
|
||||
useMessage().error('只能上传 .doc, .docx, .pdf 格式的文件')
|
||||
return false
|
||||
}
|
||||
|
||||
if (rawFile.size / 1024 / 1024 > 50) {
|
||||
useMessage().error('文件大小不能超过 50MB')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const handleFileChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
|
||||
fileList.value = uploadFiles
|
||||
}
|
||||
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = async (response: any, uploadFile: any) => {
|
||||
if (response?.code === 0 || response?.code === 200) {
|
||||
const fileData = response.data
|
||||
try {
|
||||
const submitData = {
|
||||
applyId: applyId.value,
|
||||
fileName: fileData.fileTitle || uploadFile.name,
|
||||
filePath: fileData.remark || fileData.filePath
|
||||
}
|
||||
let submitRes
|
||||
if (isReturned.value) {
|
||||
submitRes = await reuploadDoc(submitData)
|
||||
} else {
|
||||
submitRes = await uploadDoc(submitData)
|
||||
}
|
||||
if (submitRes?.code === 0 || submitRes?.code === 200) {
|
||||
useMessage().success('招标文件提交成功')
|
||||
emit('refresh')
|
||||
await loadDocList()
|
||||
fileList.value = []
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyId.value)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} else {
|
||||
useMessage().error(submitRes?.msg || '提交失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '提交失败')
|
||||
} finally {
|
||||
uploadSubmitting.value = false
|
||||
}
|
||||
} else {
|
||||
useMessage().error(response?.msg || '上传失败')
|
||||
uploadSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleUploadError: UploadProps['onError'] = (error: any) => {
|
||||
useMessage().error('文件上传失败:' + (error?.message || '未知错误'))
|
||||
uploadSubmitting.value = false
|
||||
}
|
||||
|
||||
const handleUploadSubmit = async () => {
|
||||
if (fileList.value.length === 0) {
|
||||
useMessage().warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
|
||||
uploadSubmitting.value = true
|
||||
uploadRef.value?.submit()
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要确认该招标文件无误吗?')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await confirmDoc({ applyId: applyId.value })
|
||||
useMessage().success('确认成功')
|
||||
emit('refresh')
|
||||
loadDocList()
|
||||
auditRecordListRef.value?.refresh()
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyId.value)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '确认失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleReturn = () => {
|
||||
returnRemark.value = ''
|
||||
returnDialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitReturn = async () => {
|
||||
try {
|
||||
await returnDoc({ applyId: applyId.value, remark: returnRemark.value })
|
||||
useMessage().success('退回成功')
|
||||
returnDialogVisible.value = false
|
||||
emit('refresh')
|
||||
loadDocList()
|
||||
auditRecordListRef.value?.refresh()
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyId.value)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '退回失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleComplete = async () => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要确认流程结束吗?')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await completeDoc(applyId.value)
|
||||
useMessage().success('流程已结束')
|
||||
emit('refresh')
|
||||
loadDocList()
|
||||
auditRecordListRef.value?.refresh()
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyId.value)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
'PENDING_UPLOAD': 'info',
|
||||
'ASSET_REVIEWING': 'warning',
|
||||
'DEPT_REVIEWING': 'warning',
|
||||
'AUDIT_REVIEWING': 'warning',
|
||||
'ASSET_CONFIRMING': 'primary',
|
||||
'COMPLETED': 'success',
|
||||
'RETURNED': 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getStatusLabel = (status: string) => {
|
||||
const labelMap: Record<string, string> = {
|
||||
'PENDING_UPLOAD': '待上传',
|
||||
'ASSET_REVIEWING': '资产管理处审核中',
|
||||
'DEPT_REVIEWING': '需求部门审核中',
|
||||
'AUDIT_REVIEWING': '内审部门审核中',
|
||||
'ASSET_CONFIRMING': '资产管理处确认中',
|
||||
'COMPLETED': '已完成',
|
||||
'RETURNED': '已退回'
|
||||
}
|
||||
return labelMap[status] || '-'
|
||||
}
|
||||
|
||||
const getFileTypeLabel = (type: string) => {
|
||||
const labelMap: Record<string, string> = {
|
||||
'120': '采购需求表',
|
||||
'130': '招标文件'
|
||||
}
|
||||
return labelMap[type] || type
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.doc-header {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mr-4 {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user