更新采购申请
This commit is contained in:
179
src/api/purchase/docProcess.ts
Normal file
179
src/api/purchase/docProcess.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 招标文件处理统一API
|
||||
* 整合招标代理和审核部门的接口调用
|
||||
*/
|
||||
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取列表数据(根据模式调用不同接口)
|
||||
* @param mode 模式:agent-招标代理,audit-审核部门
|
||||
* @param params 分页参数
|
||||
*/
|
||||
export function getDocProcessList(mode: string, params?: any) {
|
||||
const url = mode === 'agent'
|
||||
? '/purchase/purchasingdoc/agent/list'
|
||||
: '/purchase/purchasingdoc/audit/page';
|
||||
return request({
|
||||
url,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取采购需求文件列表
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function getRequirementFiles(applyId: number | string) {
|
||||
return request({
|
||||
url: `/purchase/purchasingdoc/agent/requirement/${applyId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取招标文件列表
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function getDocList(applyId: number | string) {
|
||||
return request({
|
||||
url: `/purchase/purchasingdoc/list/${applyId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传招标文件(招标代理)
|
||||
* @param data 文件数据
|
||||
*/
|
||||
export function uploadDoc(data: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingdoc/upload',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新上传招标文件
|
||||
* @param data 文件数据
|
||||
*/
|
||||
export function reuploadDoc(data: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingdoc/reupload',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取招标文件下载地址
|
||||
* @param id 招标文件ID
|
||||
*/
|
||||
export function getDocDownloadUrl(id: number | string) {
|
||||
return `/purchase/purchasingdoc/download/${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载招标文件(返回blob)
|
||||
* @param id 招标文件ID
|
||||
*/
|
||||
export function downloadDocById(id: number | string) {
|
||||
return request({
|
||||
url: `/purchase/purchasingdoc/download/${id}`,
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件ID下载采购附件(返回blob)
|
||||
* @param fileId 文件ID
|
||||
*/
|
||||
export function downloadFileById(fileId: string | number) {
|
||||
return request({
|
||||
url: '/purchase/purchasingfiles/downloadById',
|
||||
method: 'get',
|
||||
params: { fileId },
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认无误
|
||||
* @param data 审核信息
|
||||
*/
|
||||
export function confirmDoc(data: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingdoc/confirm',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退回修改
|
||||
* @param data 审核信息
|
||||
*/
|
||||
export function returnDoc(data: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingdoc/return',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认流程结束
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function completeDoc(applyId: number | string) {
|
||||
return request({
|
||||
url: '/purchase/purchasingdoc/complete',
|
||||
method: 'post',
|
||||
params: { applyId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审核记录
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function getAuditRecords(applyId: number | string) {
|
||||
return request({
|
||||
url: `/purchase/purchasingdoc/audit-records/${applyId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可执行操作
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function getAvailableActions(applyId: number | string) {
|
||||
return request({
|
||||
url: `/purchase/purchasingdoc/actions/${applyId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取采购申请附件列表
|
||||
* @param purchaseId 采购申请ID
|
||||
*/
|
||||
export function getApplyFiles(purchaseId: string | number) {
|
||||
return request({
|
||||
url: '/purchase/purchasingfiles/applyFiles',
|
||||
method: 'post',
|
||||
params: { purchaseId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件上传地址
|
||||
*/
|
||||
export function getFileUploadUrl() {
|
||||
const baseUrl = import.meta.env.VITE_API_URL || '';
|
||||
return `${baseUrl}/purchase/purchasingfiles/upload`;
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取采购文件列表(含历史版本)
|
||||
* 获取招标文件列表(含历史版本)
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function getDocList(applyId: number | string) {
|
||||
@@ -29,7 +29,7 @@ export function getDocList(applyId: number | string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传采购文件(招标代理)
|
||||
* 上传招标文件(招标代理)
|
||||
* @param data 文件信息
|
||||
*/
|
||||
export function uploadDoc(data: any) {
|
||||
@@ -41,7 +41,7 @@ export function uploadDoc(data: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新上传采购文件
|
||||
* 重新上传招标文件
|
||||
* @param data 文件信息
|
||||
*/
|
||||
export function reuploadDoc(data: any) {
|
||||
@@ -53,13 +53,25 @@ export function reuploadDoc(data: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取采购文件下载地址
|
||||
* @param id 采购文件ID
|
||||
* 获取招标文件下载地址
|
||||
* @param id 招标文件ID
|
||||
*/
|
||||
export function getDocDownloadUrl(id: number | string) {
|
||||
return `/purchase/purchasingdoc/download/${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载招标文件(返回blob)
|
||||
* @param id 招标文件ID
|
||||
*/
|
||||
export function downloadDocById(id: number | string) {
|
||||
return request({
|
||||
url: `/purchase/purchasingdoc/download/${id}`,
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认无误
|
||||
* @param data 审核信息
|
||||
@@ -128,4 +140,16 @@ export function getAvailableActions(applyId: number | string) {
|
||||
url: '/purchase/purchasingdoc/actions/' + applyId,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核人员分页查询招标文件列表(只查询有招标文件记录的申请)
|
||||
* @param params 分页和筛选参数
|
||||
*/
|
||||
export function getDocAuditPage(params?: any) {
|
||||
return request({
|
||||
url: '/purchase/purchasingdoc/audit/page',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
@@ -176,9 +176,9 @@ export function getContracts(params?: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 实施采购:上传采购文件并关联到申请单(可同时保存采购代表人方式与人员)
|
||||
* 实施采购:上传招标文件并关联到申请单(可同时保存采购代表人方式与人员)
|
||||
* @param id 采购申请ID
|
||||
* @param fileIds 已上传的采购文件ID列表(fileType=130)
|
||||
* @param fileIds 已上传的招标文件ID列表(fileType=130)
|
||||
* @param implementType 实施采购方式 1:自行组织采购 2:委托代理采购
|
||||
* @param representorTeacherNo 需求部门初审-指定采购代表人(单人)
|
||||
* @param representors 需求部门初审-部门多人逗号分隔
|
||||
@@ -198,7 +198,7 @@ export function implementApply(
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起采购文件审批流程(需已实施采购并上传采购文件)
|
||||
* 发起招标文件审批流程(需已实施采购并上传招标文件)
|
||||
* @param id 采购申请ID
|
||||
* @param representorTeacherNo 需求部门初审-指定采购代表人(单人,用户ID或工号)
|
||||
* @param representors 需求部门初审-部门多人由系统抽取(多人,用户ID或工号逗号分隔)
|
||||
@@ -256,7 +256,7 @@ export function getApplyTemplateDownloadUrl(id: string | number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件审批表:导出采购文件审批表 Word 文档(fileapply.docx 模板)
|
||||
* 下载文件审批表:导出招标文件审批表 Word 文档(fileapply.docx 模板)
|
||||
* @param id 采购申请ID
|
||||
*/
|
||||
export function getFileApplyTemplateDownloadUrl(id: string | number) {
|
||||
@@ -300,7 +300,7 @@ export function getAgentApplyDetail(applyId: number | string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 招标代理上传采购文件
|
||||
* 招标代理上传招标文件
|
||||
* @param data 文件数据
|
||||
*/
|
||||
export function uploadAgentDoc(data: any) {
|
||||
@@ -312,7 +312,7 @@ export function uploadAgentDoc(data: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 招标代理重新上传采购文件
|
||||
* 招标代理重新上传招标文件
|
||||
* @param data 文件数据
|
||||
*/
|
||||
export function reuploadAgentDoc(data: any) {
|
||||
@@ -324,7 +324,7 @@ export function reuploadAgentDoc(data: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取采购文件列表
|
||||
* 获取招标文件列表
|
||||
* @param applyId 采购申请ID
|
||||
*/
|
||||
export function getDocList(applyId: number | string) {
|
||||
|
||||
@@ -99,14 +99,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
isAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/purchase/purchasingrequisition/add',
|
||||
name: 'purchasingrequisition.add',
|
||||
component: () => import('/@/views/purchase/purchasingrequisition/add.vue'),
|
||||
meta: {
|
||||
isAuth: false, // 不需要认证,纯页面展示
|
||||
},
|
||||
},
|
||||
|
||||
...staticRoutesFlow
|
||||
];
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
photo: '',
|
||||
time: 0,
|
||||
roles: [],
|
||||
roleCodes: [],
|
||||
authBtnList: [],
|
||||
},
|
||||
}),
|
||||
@@ -134,6 +135,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
user: res.data.sysUser,
|
||||
time: new Date().getTime(),
|
||||
roles: res.data.roles,
|
||||
roleCodes: res.data.roleCodes || [],
|
||||
authBtnList: res.data.permissions,
|
||||
};
|
||||
this.userInfos = userInfo;
|
||||
|
||||
1
src/types/pinia.d.ts
vendored
1
src/types/pinia.d.ts
vendored
@@ -8,6 +8,7 @@ declare interface UserInfosState<T = any> {
|
||||
authBtnList: string[];
|
||||
photo: string;
|
||||
roles: string[];
|
||||
roleCodes: string[];
|
||||
time: number;
|
||||
userName: string;
|
||||
[key: string]: T;
|
||||
|
||||
@@ -440,7 +440,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="viewImplementPurchaseFiles.length" class="mb16">
|
||||
<div class="view-label mb8">采购文件</div>
|
||||
<div class="view-label mb8">招标文件</div>
|
||||
<el-table :data="viewImplementPurchaseFiles" border size="small" max-height="240">
|
||||
<el-table-column type="index" label="版本" width="70" align="center">
|
||||
<template #default="{ $index }">V{{ $index + 1 }}</template>
|
||||
@@ -632,7 +632,7 @@ const dataForm = reactive({
|
||||
agentId: '',
|
||||
agentName: '',
|
||||
});
|
||||
/** 查看时展示的采购文件列表(实施采购上传的 type=130) */
|
||||
/** 查看时展示的招标文件列表(实施采购上传的 type=130) */
|
||||
const viewImplementPurchaseFiles = ref<{ id: string; fileTitle?: string; createTime?: string; remark?: string }[]>([]);
|
||||
const categoryTreeData = ref<any[]>([]);
|
||||
const categoryCodePath = ref<string[]>([]); // 级联选择器的路径数组
|
||||
@@ -652,7 +652,7 @@ const loading = ref(false);
|
||||
const helpDialogVisible = ref(false);
|
||||
|
||||
// 文件类型映射(对应数据库 file_type 字段)
|
||||
// 10:商务洽谈纪要 20:市场采购纪要 30:网上商城采购相关材料 40:可行性论证报告 50:会议记录 60:其他材料 70:单一来源专家论证表 90:进口产品专家论证表 100:政府采购意向表 110:履约验收单 120:采购需求表 130:采购文件
|
||||
// 10:商务洽谈纪要 20:市场采购纪要 30:网上商城采购相关材料 40:可行性论证报告 50:会议记录 60:其他材料 70:单一来源专家论证表 90:进口产品专家论证表 100:政府采购意向表 110:履约验收单 120:采购需求表 130:招标文件
|
||||
const FILE_TYPE_MAP: Record<string, string> = {
|
||||
businessNegotiationTable: '10', // 商务洽谈纪要
|
||||
marketPurchaseMinutes: '20', // 市场采购纪要
|
||||
@@ -1286,10 +1286,10 @@ async function loadDetail(applyId: string | number) {
|
||||
});
|
||||
}
|
||||
});
|
||||
// 查看时展示实施采购的采购文件列表(type=130)
|
||||
// 查看时展示实施采购的招标文件列表(type=130)
|
||||
const purchaseFiles = fileList.filter((f: any) => String(f.fileType) === '130').map((f: any) => ({
|
||||
id: f.id,
|
||||
fileTitle: f.fileTitle || f.file_title || '采购文件',
|
||||
fileTitle: f.fileTitle || f.file_title || '招标文件',
|
||||
createTime: f.createTime || f.create_time,
|
||||
remark: f.remark,
|
||||
}));
|
||||
@@ -1318,13 +1318,13 @@ function downloadImplementFile(file: { id?: string; remark?: string; fileTitle?:
|
||||
// 优先使用文件ID下载
|
||||
if (file?.id) {
|
||||
const url = `/purchase/purchasingfiles/downloadById?fileId=${encodeURIComponent(file.id)}`;
|
||||
other.downBlobFile(url, {}, file.fileTitle || '采购文件');
|
||||
other.downBlobFile(url, {}, file.fileTitle || '招标文件');
|
||||
return;
|
||||
}
|
||||
// 兼容旧数据:使用文件路径下载
|
||||
if (file?.remark) {
|
||||
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(file.remark)}&fileTitle=${encodeURIComponent(file.fileTitle || '采购文件')}`;
|
||||
other.downBlobFile(url, {}, file.fileTitle || '采购文件');
|
||||
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(file.remark)}&fileTitle=${encodeURIComponent(file.fileTitle || '招标文件')}`;
|
||||
other.downBlobFile(url, {}, file.fileTitle || '招标文件');
|
||||
return;
|
||||
}
|
||||
useMessage().warning('无法获取文件信息');
|
||||
|
||||
@@ -1,397 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="'处理项目 - ' + (projectInfo.purchaseNo || '')"
|
||||
width="900px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="agent-doc-dialog">
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<!-- Tab 1: 项目基本信息 -->
|
||||
<el-tab-pane label="项目信息" name="info">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="采购编号">{{ projectInfo.purchaseNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">{{ projectInfo.projectName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="文件状态">
|
||||
<el-tag :type="getStatusType(projectInfo.status)">{{ getStatusLabel(projectInfo.status) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 2: 采购需求文件 -->
|
||||
<el-tab-pane label="采购需求文件" name="requirement">
|
||||
<el-table :data="requirementFiles" v-loading="requirementLoading" stripe>
|
||||
<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 label="操作" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="Download" @click="handleDownload(scope.row)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!requirementLoading && requirementFiles.length === 0" description="暂无采购需求文件" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 3: 上传招标文件 -->
|
||||
<el-tab-pane label="上传招标文件" name="upload">
|
||||
<div class="upload-section">
|
||||
<el-alert type="info" :closable="false" class="mb-4">
|
||||
<template #title>
|
||||
<span>当前状态:<el-tag :type="getStatusType(projectInfo.status)" size="small">{{ getStatusLabel(projectInfo.status) }}</el-tag></span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<!-- 已上传的采购文件列表 -->
|
||||
<div v-if="uploadedDocList.length > 0" class="uploaded-docs mb-4">
|
||||
<div class="section-title">已上传的采购文件</div>
|
||||
<el-table :data="uploadedDocList" stripe size="small">
|
||||
<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" align="center" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="160" align="center" />
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 文件上传区域 -->
|
||||
<div v-if="canUpload" class="upload-area">
|
||||
<div class="section-title">{{ projectInfo.status === 'RETURNED' ? '重新上传招标文件' : '上传招标文件' }}</div>
|
||||
<el-upload
|
||||
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"
|
||||
accept=".doc,.docx,.pdf"
|
||||
class="upload-demo">
|
||||
<el-button type="primary">选择文件</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 .doc, .docx, .pdf 格式,文件大小不超过 50MB</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<div class="action-buttons mt-4">
|
||||
<el-button
|
||||
type="success"
|
||||
:loading="uploadSubmitting"
|
||||
:disabled="fileList.length === 0"
|
||||
@click="handleUploadSubmit">
|
||||
{{ projectInfo.status === 'RETURNED' ? '重新上传并提交' : '上传并提交' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert v-if="!canUpload" type="warning" :closable="false" class="mt-4">
|
||||
<template #title>
|
||||
<span v-if="projectInfo.status === 'ASSET_REVIEWING'">文件审核中,请等待审核结果</span>
|
||||
<span v-else-if="projectInfo.status === 'DEPT_REVIEWING'">文件审核中,请等待审核结果</span>
|
||||
<span v-else-if="projectInfo.status === 'AUDIT_REVIEWING'">文件审核中,请等待审核结果</span>
|
||||
<span v-else-if="projectInfo.status === 'ASSET_CONFIRMING'">文件审核中,请等待确认</span>
|
||||
<span v-else-if="projectInfo.status === 'COMPLETED'">文件审核已完成</span>
|
||||
<span v-else>当前状态不允许上传</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="AgentDocDialog">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { Session } from '/@/utils/storage'
|
||||
import { getAgentRequirementFiles, getAgentApplyDetail, getDocList, downloadFileById, uploadAgentDoc, reuploadAgentDoc } from '/@/api/purchase/purchasingrequisition'
|
||||
import type { UploadInstance, UploadProps, UploadUserFile } from 'element-plus'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const visible = ref(false)
|
||||
const activeTab = ref('info')
|
||||
const projectInfo = ref<any>({})
|
||||
const requirementFiles = ref<any[]>([])
|
||||
const requirementLoading = ref(false)
|
||||
const uploadSubmitting = ref(false)
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
const uploadedDocList = ref<any[]>([])
|
||||
const docListLoading = ref(false)
|
||||
|
||||
// 上传配置 - 使用采购文件上传接口
|
||||
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: projectInfo.value.applyId || ''
|
||||
}))
|
||||
|
||||
// 是否可以上传
|
||||
const canUpload = computed(() => {
|
||||
const status = projectInfo.value.status
|
||||
return status === 'PENDING_UPLOAD' || status === 'RETURNED'
|
||||
})
|
||||
|
||||
const open = async (row: any) => {
|
||||
projectInfo.value = { ...row }
|
||||
visible.value = true
|
||||
activeTab.value = 'info'
|
||||
fileList.value = []
|
||||
uploadedDocList.value = []
|
||||
await loadProjectDetail()
|
||||
await loadRequirementFiles()
|
||||
await loadUploadedDocList()
|
||||
}
|
||||
|
||||
const loadProjectDetail = async () => {
|
||||
try {
|
||||
const res = await getAgentApplyDetail(projectInfo.value.applyId)
|
||||
if (res?.data) {
|
||||
projectInfo.value = { ...projectInfo.value, ...res.data }
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('加载项目详情失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const loadRequirementFiles = async () => {
|
||||
requirementLoading.value = true
|
||||
try {
|
||||
const res = await getAgentRequirementFiles(projectInfo.value.applyId)
|
||||
requirementFiles.value = res?.data || []
|
||||
} catch (e: any) {
|
||||
requirementFiles.value = []
|
||||
} finally {
|
||||
requirementLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadUploadedDocList = async () => {
|
||||
docListLoading.value = true
|
||||
try {
|
||||
const res = await getDocList(projectInfo.value.applyId)
|
||||
uploadedDocList.value = res?.data || []
|
||||
} catch (e: any) {
|
||||
uploadedDocList.value = []
|
||||
} finally {
|
||||
docListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = 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 baseUrl = import.meta.env.VITE_API_URL || ''
|
||||
const token = Session.getToken()
|
||||
const tenantId = Session.getTenant() || '1'
|
||||
const url = `${baseUrl}/purchase/purchasingdoc/download/${row.id}?Authorization=Bearer ${token}&TENANT-ID=${tenantId}`
|
||||
window.open(url, '_blank')
|
||||
} 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: projectInfo.value.applyId,
|
||||
fileName: fileData.fileTitle || uploadFile.name,
|
||||
filePath: fileData.remark || fileData.filePath
|
||||
}
|
||||
let submitRes
|
||||
if (projectInfo.value.status === 'RETURNED') {
|
||||
submitRes = await reuploadAgentDoc(submitData)
|
||||
} else {
|
||||
submitRes = await uploadAgentDoc(submitData)
|
||||
}
|
||||
if (submitRes?.code === 0 || submitRes?.code === 200) {
|
||||
useMessage().success('招标文件提交成功')
|
||||
emit('refresh')
|
||||
await loadProjectDetail()
|
||||
await loadUploadedDocList()
|
||||
fileList.value = []
|
||||
} 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 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] || '-'
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.agent-doc-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.uploaded-docs {
|
||||
background-color: #f5f7fa;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
background-color: #fff;
|
||||
padding: 12px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,166 +0,0 @@
|
||||
<template>
|
||||
<div class="modern-page-container">
|
||||
<div class="page-wrapper">
|
||||
<!-- 搜索表单卡片 -->
|
||||
<el-card v-show="showSearch" class="search-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon"><Search /></el-icon>
|
||||
筛选条件
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="采购编号" prop="purchaseNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.purchaseNo"
|
||||
placeholder="请输入采购编号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="state.queryForm.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 内容卡片 -->
|
||||
<el-card class="content-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon"><DocumentCopy /></el-icon>
|
||||
招标代理工作台
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="purchaseNo" label="采购编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="文件状态" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusLabel(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="View" @click="handleView(scope.row)">
|
||||
处理
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-if="state.pagination && state.pagination.total && state.pagination.total > 0"
|
||||
:total="state.pagination.total"
|
||||
:current="state.pagination.current"
|
||||
:size="state.pagination.size"
|
||||
@sizeChange="sizeChangeHandle"
|
||||
@currentChange="currentChangeHandle"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 处理弹窗 -->
|
||||
<AgentDocDialog ref="agentDocDialogRef" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingAgentDoc">
|
||||
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { getAgentPendingList } from "/@/api/purchase/purchasingrequisition";
|
||||
import { Search, DocumentCopy, List } from '@element-plus/icons-vue'
|
||||
|
||||
// 引入组件
|
||||
const AgentDocDialog = defineAsyncComponent(() => import('./AgentDocDialog.vue'));
|
||||
|
||||
const agentDocDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: getAgentPendingList,
|
||||
queryForm: {
|
||||
purchaseNo: '',
|
||||
projectName: '',
|
||||
},
|
||||
createdIsNeed: true
|
||||
});
|
||||
|
||||
const { getDataList, tableStyle, sizeChangeHandle, currentChangeHandle } = useTable(state);
|
||||
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
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 handleView = (row: any) => {
|
||||
agentDocDialogRef.value?.open(row);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 页面加载时的初始化逻辑
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
@@ -1,323 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="采购文件审核"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
@close="handleClose">
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 项目信息 -->
|
||||
<el-tab-pane label="项目信息" name="info">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="采购编号">{{ applyInfo.purchaseNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">{{ applyInfo.projectName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="需求部门">{{ applyInfo.deptName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预算金额">{{ applyInfo.budget ? Number(applyInfo.budget).toLocaleString() + '元' : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="招标代理">{{ applyInfo.agentName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">
|
||||
<el-tag :type="getStatusType(applyInfo.docAuditStatus)">
|
||||
{{ getStatusLabel(applyInfo.docAuditStatus) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 采购需求文件 -->
|
||||
<el-tab-pane label="采购需求文件" name="requirement">
|
||||
<el-table :data="requirementFiles" stripe v-loading="requirementLoading">
|
||||
<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-tab-pane>
|
||||
|
||||
<!-- 采购文件 -->
|
||||
<el-tab-pane label="采购文件" name="doc">
|
||||
<div class="doc-header">
|
||||
<el-button v-if="canUpload" type="primary" icon="Upload" @click="handleUpload">
|
||||
{{ applyInfo.docAuditStatus === 'RETURNED' ? '重新上传' : '上传文件' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="docList" stripe v-loading="docLoading">
|
||||
<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="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-tab-pane>
|
||||
|
||||
<!-- 审核记录 -->
|
||||
<el-tab-pane label="审核记录" name="audit">
|
||||
<AuditRecordList :apply-id="applyInfo.id" 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">
|
||||
import { ref, computed, defineAsyncComponent } from 'vue'
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message'
|
||||
import { getObj, getApplyFiles } from '/@/api/purchase/purchasingrequisition'
|
||||
import { getDocList, uploadDoc, reuploadDoc, confirmDoc, returnDoc, completeDoc, getAvailableActions, getDocDownloadUrl } from '/@/api/purchase/purchasingdoc'
|
||||
import other from '/@/utils/other'
|
||||
|
||||
const AuditRecordList = defineAsyncComponent(() => import('./AuditRecordList.vue'));
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const visible = ref(false)
|
||||
const activeTab = ref('info')
|
||||
const applyInfo = 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 returnDialogVisible = ref(false)
|
||||
const returnRemark = ref('')
|
||||
|
||||
const canUpload = computed(() => availableActions.value.includes('upload'))
|
||||
const canConfirm = computed(() => availableActions.value.includes('confirm'))
|
||||
const canReturn = computed(() => availableActions.value.includes('return'))
|
||||
const canComplete = computed(() => availableActions.value.includes('complete'))
|
||||
|
||||
const open = async (row: any) => {
|
||||
visible.value = true
|
||||
activeTab.value = 'info'
|
||||
applyInfo.value = {}
|
||||
requirementFiles.value = []
|
||||
docList.value = []
|
||||
returnRemark.value = ''
|
||||
|
||||
// 加载详情
|
||||
try {
|
||||
const res = await getObj(row.id)
|
||||
applyInfo.value = res.data || res
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '加载项目信息失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 加载可执行操作
|
||||
try {
|
||||
const actionsRes = await getAvailableActions(row.id)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} catch (e) {
|
||||
availableActions.value = []
|
||||
}
|
||||
|
||||
// 加载采购需求文件
|
||||
loadRequirementFiles()
|
||||
// 加载采购文件
|
||||
loadDocList()
|
||||
}
|
||||
|
||||
const loadRequirementFiles = async () => {
|
||||
if (!applyInfo.value.id) return
|
||||
requirementLoading.value = true
|
||||
try {
|
||||
const res = await getApplyFiles(applyInfo.value.id)
|
||||
const files = res.data || res || []
|
||||
// 过滤采购需求文件(fileType=120)
|
||||
requirementFiles.value = files.filter((f: any) => f.fileType === '120')
|
||||
} catch (e) {
|
||||
requirementFiles.value = []
|
||||
} finally {
|
||||
requirementLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadDocList = async () => {
|
||||
if (!applyInfo.value.id) return
|
||||
docLoading.value = true
|
||||
try {
|
||||
const res = await getDocList(applyInfo.value.id)
|
||||
docList.value = res.data || []
|
||||
} catch (e) {
|
||||
docList.value = []
|
||||
} finally {
|
||||
docLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpload = () => {
|
||||
// 触发文件上传
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '*/*'
|
||||
input.onchange = async (e: any) => {
|
||||
const file = e.target.files[0]
|
||||
if (!file) return
|
||||
// TODO: 实现文件上传到OSS,然后调用上传接口
|
||||
useMessage().info('文件上传功能需要配合OSS实现')
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
const handleDownloadRequirement = (row: any) => {
|
||||
if (row.remark) {
|
||||
const url = `/purchase/purchasingfiles/download?fileName=${encodeURIComponent(row.remark)}&fileTitle=${encodeURIComponent(row.fileTitle)}`
|
||||
other.downBlobFile(url, {}, row.fileTitle)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadDoc = (row: any) => {
|
||||
const url = getDocDownloadUrl(row.id)
|
||||
other.downBlobFile(url, {}, row.fileName)
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要确认该采购文件无误吗?')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await confirmDoc({ applyId: applyInfo.value.id })
|
||||
useMessage().success('确认成功')
|
||||
emit('refresh')
|
||||
loadDocList()
|
||||
auditRecordListRef.value?.refresh()
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyInfo.value.id)
|
||||
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: applyInfo.value.id, remark: returnRemark.value })
|
||||
useMessage().success('退回成功')
|
||||
returnDialogVisible.value = false
|
||||
emit('refresh')
|
||||
loadDocList()
|
||||
auditRecordListRef.value?.refresh()
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyInfo.value.id)
|
||||
availableActions.value = actionsRes.data || []
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '退回失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleComplete = async () => {
|
||||
try {
|
||||
await useMessageBox().confirm('确定要确认流程结束吗?')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await completeDoc(applyInfo.value.id)
|
||||
useMessage().success('流程已结束')
|
||||
emit('refresh')
|
||||
loadDocList()
|
||||
auditRecordListRef.value?.refresh()
|
||||
// 重新加载可执行操作
|
||||
const actionsRes = await getAvailableActions(applyInfo.value.id)
|
||||
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;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { getAuditRecords } from '/@/api/purchase/purchasingdoc'
|
||||
import { getAuditRecords } from '/@/api/purchase/docProcess'
|
||||
|
||||
const props = defineProps<{
|
||||
applyId: number | string
|
||||
@@ -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>
|
||||
@@ -26,7 +26,8 @@
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审核状态" prop="docAuditStatus">
|
||||
<!-- 审核状态筛选 - 仅审核模式显示 -->
|
||||
<el-form-item v-if="mode === 'audit'" label="审核状态" prop="docAuditStatus">
|
||||
<el-select
|
||||
v-model="state.queryForm.docAuditStatus"
|
||||
placeholder="请选择审核状态"
|
||||
@@ -53,8 +54,10 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon"><DocumentChecked /></el-icon>
|
||||
采购文件审核
|
||||
<el-icon class="title-icon">
|
||||
<component :is="titleIcon" />
|
||||
</el-icon>
|
||||
{{ pageTitle }}
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
|
||||
@@ -78,28 +81,33 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="purchaseNo" label="采购编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="budget" label="预算金额(元)" width="120" align="right">
|
||||
<!-- 需求部门 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip />
|
||||
<!-- 预算金额 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="budget" label="预算金额(元)" width="120" align="right">
|
||||
<template #default="scope">
|
||||
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="docAuditStatus" label="审核状态" width="140" align="center">
|
||||
<!-- 文件状态 -->
|
||||
<el-table-column :prop="statusProp" label="文件状态" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.docAuditStatus)">
|
||||
{{ getStatusLabel(scope.row.docAuditStatus) }}
|
||||
<el-tag :type="getStatusType(getRowStatus(scope.row))">
|
||||
{{ getStatusLabel(getRowStatus(scope.row)) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentDocVersion" label="当前版本" width="100" align="center">
|
||||
<!-- 当前版本 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="currentDocVersion" label="当前版本" width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.currentDocVersion || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 操作 -->
|
||||
<el-table-column label="操作" align="center" fixed="right" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="View" @click="handleAudit(scope.row)">
|
||||
审核
|
||||
<el-button type="primary" link icon="View" @click="handleProcess(scope.row)">
|
||||
{{ actionLabel }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -117,32 +125,73 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 审核弹窗 -->
|
||||
<DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />
|
||||
<!-- 处理弹窗 -->
|
||||
{{mode}}
|
||||
<DocProcessDialog ref="docProcessDialogRef" :mode="mode" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingDocAudit">
|
||||
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue'
|
||||
<script setup lang="ts" name="PurchasingDocProcess">
|
||||
import { ref, reactive, computed, defineAsyncComponent, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { getPage } from "/@/api/purchase/purchasingrequisition";
|
||||
import { Search, DocumentChecked, List } from '@element-plus/icons-vue'
|
||||
import { getDocProcessList } from "/@/api/purchase/docProcess";
|
||||
import { Search, DocumentCopy, DocumentChecked, List } from '@element-plus/icons-vue'
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
// 引入组件
|
||||
const DocAuditDialog = defineAsyncComponent(() => import('./DocAuditDialog.vue'));
|
||||
const DocProcessDialog = defineAsyncComponent(() => import('./DocProcessDialog.vue'));
|
||||
|
||||
const docAuditDialogRef = ref()
|
||||
const userInfoStore = useUserInfo()
|
||||
const docProcessDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: getPage,
|
||||
queryForm: {
|
||||
// 从用户角色自动判断模式
|
||||
const mode = computed(() => {
|
||||
const roleCodes = userInfoStore.userInfos.roleCodes || [];
|
||||
// 有 PURCHASE_AGENT 角色则显示代理模式
|
||||
if (roleCodes.includes('PURCHASE_AGENT')) {
|
||||
return 'agent';
|
||||
}
|
||||
// 其他情况显示审核模式
|
||||
return 'audit';
|
||||
})
|
||||
|
||||
// 页面标题
|
||||
const pageTitle = computed(() => {
|
||||
return mode.value === 'agent' ? '招标代理工作台' : '招标文件审核'
|
||||
})
|
||||
|
||||
// 标题图标
|
||||
const titleIcon = computed(() => {
|
||||
return mode.value === 'agent' ? DocumentCopy : DocumentChecked
|
||||
})
|
||||
|
||||
// 操作按钮标签
|
||||
const actionLabel = computed(() => {
|
||||
return mode.value === 'agent' ? '处理' : '审核'
|
||||
})
|
||||
|
||||
// 状态字段
|
||||
const statusProp = computed(() => {
|
||||
return mode.value === 'agent' ? 'status' : 'docAuditStatus'
|
||||
})
|
||||
|
||||
// 查询表单
|
||||
const getQueryForm = () => {
|
||||
const base: any = {
|
||||
purchaseNo: '',
|
||||
projectName: '',
|
||||
docAuditStatus: '',
|
||||
},
|
||||
}
|
||||
if (mode.value === 'audit') {
|
||||
base.docAuditStatus = ''
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: (params?: any) => getDocProcessList(mode.value, params),
|
||||
queryForm: getQueryForm(),
|
||||
createdIsNeed: true
|
||||
});
|
||||
|
||||
@@ -153,6 +202,11 @@ const handleReset = () => {
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 获取行状态(兼容两种字段名)
|
||||
const getRowStatus = (row: any) => {
|
||||
return mode.value === 'agent' ? row.status : row.docAuditStatus
|
||||
}
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
'PENDING_UPLOAD': 'info',
|
||||
@@ -179,12 +233,14 @@ const getStatusLabel = (status: string) => {
|
||||
return labelMap[status] || '-';
|
||||
};
|
||||
|
||||
const handleAudit = (row: any) => {
|
||||
docAuditDialogRef.value?.open(row);
|
||||
const handleProcess = (row: any) => {
|
||||
docProcessDialogRef.value?.open(row);
|
||||
};
|
||||
|
||||
// 监听路由参数变化,重新加载数据
|
||||
onMounted(() => {
|
||||
// 页面加载时的初始化逻辑
|
||||
// 重置查询表单
|
||||
state.queryForm = getQueryForm()
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -292,8 +292,8 @@
|
||||
<!-- 实施采购:iframe 嵌入 implement.vue,供列表与流程页面使用 -->
|
||||
<ImplementForm ref="implementFormRef" @refresh="getDataList" />
|
||||
|
||||
<!-- 采购文件审核弹窗 -->
|
||||
<DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />
|
||||
<!-- 招标文件审核弹窗 -->
|
||||
<!-- <DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />-->
|
||||
|
||||
<!-- 采购代表弹窗 -->
|
||||
<el-dialog
|
||||
@@ -365,7 +365,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 DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue'));
|
||||
// const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue'));
|
||||
|
||||
// 字典数据和品目树数据
|
||||
const dictData = ref({
|
||||
@@ -537,7 +537,7 @@ const handleImplement = (row: any) => {
|
||||
implementFormRef.value?.openDialog(row);
|
||||
};
|
||||
|
||||
/** 打开采购文件审核 */
|
||||
/** 打开招标文件审核 */
|
||||
const handleDocAudit = (row: any) => {
|
||||
docAuditDialogRef.value?.open(row);
|
||||
};
|
||||
@@ -639,7 +639,7 @@ const getActionMenuItems = (row: any) => {
|
||||
// },
|
||||
// {
|
||||
// command: 'docAudit',
|
||||
// label: '采购文件审核',
|
||||
// label: '招标文件审核',
|
||||
// icon: DocumentChecked,
|
||||
// visible: () => row?.implementType === '2' && row?.agentId,
|
||||
// },
|
||||
|
||||
Reference in New Issue
Block a user