962 lines
28 KiB
Vue
962 lines
28 KiB
Vue
<template>
|
||
<div class="bidfile-audit-container">
|
||
<!-- 基本信息 -->
|
||
<el-card shadow="never" class="info-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="card-title">
|
||
<el-icon><Document /></el-icon>
|
||
采购申请信息
|
||
</span>
|
||
<el-tag v-if="flowStatus === '0'" type="warning">运行中</el-tag>
|
||
<el-tag v-else-if="flowStatus === '1'" type="success">已完成</el-tag>
|
||
<el-tag v-else-if="flowStatus === '2'" type="info">已作废</el-tag>
|
||
</div>
|
||
</template>
|
||
<!-- 招标代理只能看到有限信息 -->
|
||
<el-descriptions v-if="isAgent" :column="2" border>
|
||
<el-descriptions-item label="采购编号">{{ applyData.purchaseNo || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="项目名称">{{ applyData.projectName || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="招标代理">{{ applyData.agentName || '-' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
<!-- 其他角色可以看到完整信息 -->
|
||
<el-descriptions v-else :column="3" border>
|
||
<el-descriptions-item label="采购编号">{{ applyData.purchaseNo || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="项目名称">{{ applyData.projectName || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="预算金额">{{ applyData.budget ? Number(applyData.budget).toLocaleString() + ' 元' : '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="采购方式">{{ purchaseTypeLabel || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="招标代理">{{ applyData.agentName || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="申请人">{{ applyData.createName || applyData.createBy || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="申请部门" :span="3">{{ applyData.deptName || '-' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-card>
|
||
|
||
<!-- 需求文件(招标代理可见) -->
|
||
<el-card v-if="isAgent" shadow="never" class="file-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="card-title">
|
||
<el-icon><FolderOpened /></el-icon>
|
||
需求文件
|
||
</span>
|
||
</div>
|
||
</template>
|
||
<el-table v-loading="requirementLoading" :data="requirementFiles" stripe border>
|
||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||
<el-table-column prop="fileTitle" label="文件名" min-width="200" show-overflow-tooltip />
|
||
<el-table-column prop="createTime" label="上传时间" width="160" />
|
||
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link icon="Download" @click="handleDownloadRequirement(row)">下载</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-empty v-if="!requirementLoading && requirementFiles.length === 0" description="暂无需求文件" />
|
||
</el-card>
|
||
|
||
<!-- 招标文件信息 -->
|
||
<el-card shadow="never" class="file-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="card-title">
|
||
<el-icon><FolderOpened /></el-icon>
|
||
招标文件
|
||
</span>
|
||
<el-tag v-if="currentVersion" type="success">{{ currentVersion.version }}</el-tag>
|
||
</div>
|
||
</template>
|
||
|
||
<el-tabs v-model="activeTab">
|
||
<!-- 当前版本 -->
|
||
<el-tab-pane label="当前版本" name="current">
|
||
<div v-if="currentVersion" class="version-detail">
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="版本号">
|
||
<el-tag type="primary">{{ currentVersion.version }}</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="上传人">
|
||
{{ currentVersion.uploadUserName || '-' }}
|
||
<el-tag size="small" type="info" style="margin-left: 8px">{{ getRoleLabel(currentVersion.uploadRoleCode) }}</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="文件名" :span="2">
|
||
<el-link type="primary" :href="currentVersion.fileUrl" target="_blank">
|
||
<el-icon><Link /></el-icon>
|
||
{{ currentVersion.fileName }}
|
||
</el-link>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="文件修改意见" :span="2">
|
||
{{ currentVersion.comment || '无' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="上传时间" :span="2">
|
||
{{ currentVersion.createTime || '-' }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<div class="file-actions">
|
||
<el-button type="primary" icon="Download" @click="handleDownload(currentVersion)">下载文件</el-button>
|
||
</div>
|
||
</div>
|
||
<el-empty v-else description="暂无招标文件,请上传" />
|
||
</el-tab-pane>
|
||
|
||
<!-- 版本历史 -->
|
||
<el-tab-pane label="版本历史" name="history">
|
||
<el-table :data="versionHistory" stripe border v-if="versionHistory.length > 0">
|
||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||
<el-table-column prop="version" label="版本" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.isCurrent === '1' ? 'success' : 'info'">{{ row.version }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="fileName" label="文件名" min-width="200" show-overflow-tooltip />
|
||
<el-table-column prop="uploadUserName" label="上传人" width="100" />
|
||
<el-table-column prop="uploadRoleCode" label="角色" width="120">
|
||
<template #default="{ row }">
|
||
{{ getRoleLabel(row.uploadRoleCode) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="comment" label="文件修改意见" min-width="150" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.comment || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="createTime" label="上传时间" width="160" />
|
||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link icon="Download" @click="handleDownload(row)">下载</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-empty v-else description="暂无版本历史" />
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-card>
|
||
|
||
<!-- 上传招标文件(流程中可编辑时显示) -->
|
||
<el-card v-if="showUploadSection" shadow="never" class="upload-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="card-title">
|
||
<el-icon><Upload /></el-icon>
|
||
上传招标文件
|
||
</span>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form ref="uploadFormRef" :model="uploadForm" :rules="uploadRules" label-width="120px">
|
||
<el-form-item label="选择文件" prop="fileId">
|
||
<el-upload
|
||
ref="uploadRef"
|
||
:action="uploadUrl"
|
||
:headers="uploadHeaders"
|
||
:data="uploadData"
|
||
:before-upload="beforeUpload"
|
||
:on-success="handleUploadSuccess"
|
||
:on-error="handleUploadError"
|
||
:on-remove="handleUploadRemove"
|
||
:file-list="fileList"
|
||
accept=".doc,.docx"
|
||
:disabled="isViewMode"
|
||
>
|
||
<el-button type="primary" :disabled="isViewMode">选择文件</el-button>
|
||
<template #tip>
|
||
<div class="el-upload__tip">仅支持 doc、docx 格式,单文件不超过 50MB,上传后自动保存</div>
|
||
</template>
|
||
</el-upload>
|
||
</el-form-item>
|
||
<el-form-item v-if="uploadForm.fileId" label="文件修改意见">
|
||
<el-input
|
||
v-model="uploadForm.comment"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入文件修改意见或备注(可选)"
|
||
:disabled="isViewMode"
|
||
maxlength="500"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item v-if="uploadForm.fileId" label="文件修改意见">
|
||
<el-input
|
||
v-model="uploadForm.comment"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入文件修改意见或备注(可选)"
|
||
:disabled="isViewMode"
|
||
maxlength="500"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 流转去向选择(资产管理处审核时显示) -->
|
||
<el-card v-if="showFlowTargetSection" shadow="never" class="flow-target-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="card-title">
|
||
<el-icon><Guide /></el-icon>
|
||
流转去向
|
||
</span>
|
||
</div>
|
||
</template>
|
||
<el-form label-width="100px">
|
||
<el-form-item label="选择去向" required>
|
||
<el-radio-group v-model="flowTarget" :disabled="isViewMode">
|
||
<el-radio v-for="item in FLOW_TARGET_OPTIONS" :key="item.value" :label="item.value">
|
||
{{ item.label }}
|
||
</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 部门参与人选择(部门负责人审核时显示) -->
|
||
<el-card v-if="showRepresentorSection" shadow="never" class="representor-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span class="card-title">
|
||
<el-icon><User /></el-icon>
|
||
部门参与人
|
||
</span>
|
||
<el-tag v-if="applyData.representorTeacherNo" type="success">已设置</el-tag>
|
||
<el-tag v-else type="warning">待设置</el-tag>
|
||
</div>
|
||
</template>
|
||
|
||
<el-form label-width="120px">
|
||
<el-form-item label="选择方式">
|
||
<el-radio-group v-model="representorSelectMode" :disabled="isViewMode">
|
||
<el-radio label="designate">指定一人</el-radio>
|
||
<el-radio label="random">随机抽取</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<!-- 指定一人 -->
|
||
<el-form-item v-if="representorSelectMode === 'designate'" label="选择参与人">
|
||
<el-select
|
||
v-model="selectedRepresentor"
|
||
placeholder="请选择参与人"
|
||
filterable
|
||
:disabled="isViewMode"
|
||
style="width: 300px"
|
||
>
|
||
<el-option
|
||
v-for="member in deptMembers"
|
||
:key="member.teacherNo"
|
||
:label="`${member.realName} (${member.teacherNo})`"
|
||
:value="member.teacherNo"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<!-- 随机抽取 -->
|
||
<el-form-item v-if="representorSelectMode === 'random'" label="选择候选人">
|
||
<el-select
|
||
v-model="randomCandidates"
|
||
placeholder="请选择参与随机抽取的人员(可多选)"
|
||
multiple
|
||
filterable
|
||
:disabled="isViewMode"
|
||
style="width: 400px"
|
||
>
|
||
<el-option
|
||
v-for="member in deptMembers"
|
||
:key="member.teacherNo"
|
||
:label="`${member.realName} (${member.teacherNo})`"
|
||
:value="member.teacherNo"
|
||
/>
|
||
</el-select>
|
||
<el-button
|
||
type="primary"
|
||
:loading="randomSelectLoading"
|
||
:disabled="randomCandidates.length < 2 || isViewMode"
|
||
style="margin-left: 12px"
|
||
@click="handleRandomSelect"
|
||
>
|
||
随机抽取
|
||
</el-button>
|
||
</el-form-item>
|
||
|
||
<!-- 已选中的参与人 -->
|
||
<el-form-item v-if="currentRepresentor" label="已选中参与人">
|
||
<el-tag type="success" size="large">
|
||
{{ currentRepresentor.realName }} ({{ currentRepresentor.teacherNo }})
|
||
</el-tag>
|
||
</el-form-item>
|
||
|
||
<!-- 参与人身份 -->
|
||
<el-form-item label="参与人身份" required>
|
||
<el-radio-group v-model="representorType" :disabled="isViewMode">
|
||
<el-radio label="purchase_rep">采购代表</el-radio>
|
||
<el-radio label="judge">评委</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<!-- 保存按钮 -->
|
||
<el-form-item>
|
||
<el-button
|
||
type="primary"
|
||
:loading="saveRepresentorLoading"
|
||
:disabled="!canSaveRepresentor || isViewMode"
|
||
@click="handleSaveRepresentor"
|
||
>
|
||
保存参与人信息
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="BidFileAudit">
|
||
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
||
import { useRoute } from 'vue-router';
|
||
import { Document, FolderOpened, Upload, Link, Guide, User } from '@element-plus/icons-vue';
|
||
import { ElMessage } from 'element-plus';
|
||
import { Session } from '/@/utils/storage';
|
||
import { useUserInfo } from '/@/stores/userInfo';
|
||
import other from '/@/utils/other';
|
||
import {
|
||
getBidFileVersionHistory,
|
||
getBidFileCurrentVersion,
|
||
agentUploadBidFile,
|
||
assetUploadBidFile,
|
||
uploadBidFileNewVersion,
|
||
getAgentPurchaseDetail,
|
||
getFlowPurchaseDetail,
|
||
tempStoreBidFile,
|
||
submitBidFile,
|
||
} from '/@/api/purchase/bidfile';
|
||
import { getRequirementFiles } from '/@/api/purchase/purchasingfiles';
|
||
import { currElTabIsSave } from '/@/api/order/order-key-vue';
|
||
import { getDeptMembers, saveRepresentor, randomSelectRepresentor } from '/@/api/purchase/purchasingrequisition';
|
||
|
||
// ==================== Props & Emits ====================
|
||
|
||
const props = defineProps({
|
||
currJob: { type: Object, default: null },
|
||
currElTab: { type: Object, default: null },
|
||
});
|
||
|
||
const emit = defineEmits(['handleJob']);
|
||
|
||
// ==================== 用户角色判断 ====================
|
||
|
||
const userStore = useUserInfo();
|
||
const currentUserRoleCodes = computed(() => userStore.userInfos.roleCodes || []);
|
||
|
||
// ==================== 常量定义 ====================
|
||
|
||
const ROLE_LABEL_MAP: Record<string, string> = {
|
||
PURCHASE_AGENT: '招标代理',
|
||
PURCHASE_ASSET: '资产管理处',
|
||
PURCHASE_DEPT_APPLY: '部门经办人',
|
||
PURCHASE_DEPT_AUDIT: '部门负责人',
|
||
PURCHASE_FILE_AUDIT: '内审部门',
|
||
PURCHASE_CENTER: '采购中心',
|
||
};
|
||
|
||
const PURCHASE_TYPE_MAP: Record<string, string> = {
|
||
'1': '网上商城',
|
||
'2': '市场采购',
|
||
'3': '商务洽谈',
|
||
'5': '询价',
|
||
'6': '单一来源',
|
||
'7': '竞争性谈判',
|
||
'8': '竞争性磋商',
|
||
'9': '公开招标',
|
||
'10': '邀请招标',
|
||
};
|
||
|
||
// 流转去向选项(资产管理处审核时使用)
|
||
const FLOW_TARGET_OPTIONS = [
|
||
{ label: '需求部门负责人', value: '4' },
|
||
{ label: '文件内审部门', value: '2' },
|
||
{ label: '招标代理', value: '1' },
|
||
{ label: '确认定稿', value: '3' },
|
||
] as const;
|
||
|
||
// ==================== 响应式数据 ====================
|
||
|
||
const route = useRoute();
|
||
const loading = ref(false);
|
||
|
||
// 流程相关
|
||
const isFlowEmbed = computed(() => !!props.currJob);
|
||
const isViewMode = computed(() => {
|
||
if (isFlowEmbed.value && props.currJob) {
|
||
return !!props.currJob.hiJob || props.currElTab?.isFormEdit === '0';
|
||
}
|
||
return route.query.mode === 'view';
|
||
});
|
||
|
||
// 当前用户角色判断(从用户store获取)
|
||
const isAgent = computed(() => currentUserRoleCodes.value.includes('PURCHASE_AGENT'));
|
||
const isAsset = computed(() => currentUserRoleCodes.value.includes('PURCHASE_ASSET'));
|
||
const isDeptApply = computed(() => currentUserRoleCodes.value.includes('PURCHASE_DEPT_APPLY'));
|
||
const isDeptAudit = computed(() => currentUserRoleCodes.value.includes('PURCHASE_DEPT_AUDIT'));
|
||
const isFileAudit = computed(() => currentUserRoleCodes.value.includes('PURCHASE_FILE_AUDIT'));
|
||
|
||
// 是否显示上传区域
|
||
const showUploadSection = computed(() => {
|
||
if (isViewMode.value) return false;
|
||
if (isAgent.value ) return true;
|
||
if (isAsset.value || isDeptApply.value || isFileAudit.value) return true;
|
||
return false;
|
||
});
|
||
|
||
// 是否显示流转去向选择区域(仅资产管理处在审核时显示)
|
||
const showFlowTargetSection = computed(() => {
|
||
if (isViewMode.value) return false;
|
||
return isAsset.value && isFlowEmbed.value;
|
||
});
|
||
|
||
// 是否显示部门参与人选择区域(仅部门负责人在审核时显示)
|
||
const showRepresentorSection = computed(() => {
|
||
if (isViewMode.value) return false;
|
||
return isDeptAudit.value && isFlowEmbed.value;
|
||
});
|
||
|
||
// 采购申请数据
|
||
const applyData = ref<any>({});
|
||
const purchaseTypeLabel = computed(() => {
|
||
const type = applyData.value.purchaseType;
|
||
return PURCHASE_TYPE_MAP[type] || type || '-';
|
||
});
|
||
|
||
// 流程状态
|
||
const flowStatus = computed(() => applyData.value.fileFlowStatus || '0');
|
||
|
||
// 招标文件
|
||
const activeTab = ref('current');
|
||
const currentVersion = ref<any>(null);
|
||
const versionHistory = ref<any[]>([]);
|
||
|
||
// 需求文件(招标代理可见)
|
||
const requirementFiles = ref<any[]>([]);
|
||
const requirementLoading = ref(false);
|
||
|
||
// 上传相关
|
||
const uploadFormRef = ref();
|
||
const uploadRef = ref();
|
||
const fileList = ref<any[]>([]);
|
||
const saveLoading = ref(false);
|
||
const tempStoreLoading = ref(false);
|
||
const submitLoading = ref(false);
|
||
const isTempStored = ref(false);
|
||
const uploadForm = reactive({
|
||
fileId: '',
|
||
fileName: '',
|
||
fileUrl: '',
|
||
comment: '',
|
||
});
|
||
const uploadRules = {
|
||
// 招标文件上传为可选,后续可补充上传
|
||
};
|
||
|
||
// 流转去向(资产管理处审核时使用)
|
||
const flowTarget = ref<string>('');
|
||
|
||
// 部门参与人选择相关(部门负责人审核时使用)
|
||
const representorSelectMode = ref<'designate' | 'random'>('designate');
|
||
const deptMembers = ref<any[]>([]);
|
||
const deptMembersLoading = ref(false);
|
||
const selectedRepresentor = ref<string>('');
|
||
const randomCandidates = ref<string[]>([]);
|
||
const currentRepresentor = ref<any>(null);
|
||
const representorType = ref<string>('purchase_rep');
|
||
const saveRepresentorLoading = ref(false);
|
||
const randomSelectLoading = ref(false);
|
||
|
||
const canSaveRepresentor = computed(() => {
|
||
if (representorSelectMode.value === 'designate') {
|
||
return !!selectedRepresentor.value;
|
||
}
|
||
return !!currentRepresentor.value;
|
||
});
|
||
|
||
// ==================== 计算属性 ====================
|
||
|
||
const BID_FILE_TYPE = '130';
|
||
const uploadUrl = computed(() => import.meta.env.VITE_API_URL + '/purchase/purchasingfiles/upload');
|
||
const uploadHeaders = computed(() => ({
|
||
Authorization: 'Bearer ' + Session.getToken(),
|
||
'TENANT-ID': Session.getTenant(),
|
||
}));
|
||
const uploadData = computed(() => ({
|
||
purchaseId: effectivePurchaseId.value,
|
||
fileType: BID_FILE_TYPE,
|
||
}));
|
||
|
||
// 当前采购申请ID
|
||
const effectivePurchaseId = computed(() => {
|
||
if (props.currJob?.orderId) {
|
||
return String(props.currJob.orderId);
|
||
}
|
||
return route.query.id as string || route.query.purchaseId as string || '';
|
||
});
|
||
|
||
// 是否可以提交(非查看模式即可提交,不强制要求上传文件)
|
||
const canSubmitFile = computed(() => {
|
||
return !isViewMode.value;
|
||
});
|
||
|
||
// ==================== 方法定义 ====================
|
||
|
||
// 获取角色标签
|
||
const getRoleLabel = (roleCode: string) => {
|
||
return ROLE_LABEL_MAP[roleCode] || roleCode || '-';
|
||
};
|
||
|
||
// 加载采购申请详情(根据角色调用不同接口)
|
||
const loadApplyData = async () => {
|
||
if (!effectivePurchaseId.value) return;
|
||
|
||
try {
|
||
loading.value = true;
|
||
let res: any;
|
||
|
||
// 招标代理只能看到有限信息
|
||
if (isAgent.value) {
|
||
res = await getAgentPurchaseDetail(effectivePurchaseId.value);
|
||
} else {
|
||
// 其他角色可以看到完整信息
|
||
res = await getFlowPurchaseDetail(effectivePurchaseId.value);
|
||
}
|
||
|
||
if (res.code === 0 && res.data) {
|
||
applyData.value = res.data;
|
||
isTempStored.value = res.data.fileFlowStatus === '-1';
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '加载采购申请失败');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
// 加载招标文件
|
||
const loadBidFiles = async () => {
|
||
if (!effectivePurchaseId.value) return;
|
||
|
||
try {
|
||
const [currentRes, historyRes] = await Promise.all([
|
||
getBidFileCurrentVersion(effectivePurchaseId.value),
|
||
getBidFileVersionHistory(effectivePurchaseId.value),
|
||
]);
|
||
|
||
if (currentRes.code === 0) {
|
||
currentVersion.value = currentRes.data;
|
||
}
|
||
|
||
if (historyRes.code === 0) {
|
||
versionHistory.value = historyRes.data || [];
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '加载招标文件失败');
|
||
}
|
||
};
|
||
|
||
// 加载需求文件(招标代理可见)
|
||
const loadRequirementFiles = async () => {
|
||
if (!isAgent.value || !effectivePurchaseId.value) return;
|
||
|
||
try {
|
||
requirementLoading.value = true;
|
||
const res = await getRequirementFiles(effectivePurchaseId.value);
|
||
if (res.code === 0) {
|
||
requirementFiles.value = res.data || [];
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '加载需求文件失败');
|
||
} finally {
|
||
requirementLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// 下载需求文件(使用downBlobFile携带token)
|
||
const handleDownloadRequirement = (row: any) => {
|
||
if (row.id) {
|
||
const downloadUrl = '/purchase/purchasingfiles/downloadById?fileId=' + row.id;
|
||
other.downBlobFile(downloadUrl, {}, row.fileTitle || '需求文件');
|
||
}
|
||
};
|
||
|
||
// 文件上传前校验
|
||
const beforeUpload = (file: any) => {
|
||
const isLt50M = file.size / 1024 / 1024 < 50;
|
||
if (!isLt50M) {
|
||
ElMessage.error('文件大小不能超过 50MB');
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// 上传成功后自动保存
|
||
const handleUploadSuccess = async (response: any, file: any) => {
|
||
if (response.code === 0) {
|
||
uploadForm.fileId = response.data.id || response.data.fileName;
|
||
uploadForm.fileUrl = response.data.url || response.data.remark;
|
||
uploadForm.fileName = file.name;
|
||
// 自动保存招标文件
|
||
const saved = await saveUploadedFile();
|
||
if (saved) {
|
||
ElMessage.success('招标文件上传并保存成功');
|
||
}
|
||
} else {
|
||
ElMessage.error(response.msg || '文件上传失败');
|
||
fileList.value = [];
|
||
}
|
||
};
|
||
|
||
// 上传失败
|
||
const handleUploadError = () => {
|
||
ElMessage.error('文件上传失败');
|
||
fileList.value = [];
|
||
};
|
||
|
||
// 移除文件
|
||
const handleUploadRemove = () => {
|
||
uploadForm.fileId = '';
|
||
uploadForm.fileUrl = '';
|
||
uploadForm.fileName = '';
|
||
};
|
||
|
||
// 下载招标文件(使用downBlobFile携带token)
|
||
const handleDownload = (row: any) => {
|
||
if (row.fileId) {
|
||
const downloadUrl = '/purchase/purchasingfiles/downloadById?fileId=' + row.fileId;
|
||
other.downBlobFile(downloadUrl, {}, row.fileName || '招标文件');
|
||
}
|
||
};
|
||
|
||
// 手动保存上传的招标文件
|
||
const handleSaveBidFile = async () => {
|
||
if (!uploadForm.fileId) {
|
||
ElMessage.warning('请先上传招标文件');
|
||
return;
|
||
}
|
||
|
||
saveLoading.value = true;
|
||
try {
|
||
const saved = await saveUploadedFile();
|
||
if (saved) {
|
||
ElMessage.success('招标文件保存成功,版本已更新');
|
||
}
|
||
} finally {
|
||
saveLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// 保存上传的招标文件
|
||
const saveUploadedFile = async () => {
|
||
if (!uploadForm.fileId) {
|
||
ElMessage.warning('请先上传招标文件');
|
||
return false;
|
||
}
|
||
|
||
const runJobId = props.currJob?.id || '';
|
||
|
||
const params = {
|
||
purchaseId: effectivePurchaseId.value,
|
||
fileId: uploadForm.fileId,
|
||
fileName: uploadForm.fileName,
|
||
fileUrl: uploadForm.fileUrl,
|
||
comment: uploadForm.comment,
|
||
runJobId,
|
||
};
|
||
|
||
try {
|
||
let res: any;
|
||
|
||
if (isAgent.value) {
|
||
res = await agentUploadBidFile(params);
|
||
} else if (isAsset.value) {
|
||
res = await assetUploadBidFile(params);
|
||
} else {
|
||
res = await uploadBidFileNewVersion(params);
|
||
}
|
||
|
||
if (res.code === 0) {
|
||
await loadBidFiles();
|
||
// 标记为已保存
|
||
if (props.currJob && props.currElTab?.id) {
|
||
currElTabIsSave(props.currJob, props.currElTab.id, true, emit);
|
||
}
|
||
// 清空表单和上传组件,允许继续上传新文件
|
||
uploadForm.fileId = '';
|
||
uploadForm.fileUrl = '';
|
||
uploadForm.fileName = '';
|
||
uploadForm.comment = '';
|
||
fileList.value = [];
|
||
uploadRef.value?.clearFiles();
|
||
return true;
|
||
} else {
|
||
ElMessage.error(res.msg || '保存失败');
|
||
return false;
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '保存招标文件失败');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// ==================== 流程集成 ====================
|
||
|
||
// 流程保存回调 - 审核时自动调用暂存接口
|
||
const handleFlowSave = async () => {
|
||
// 资产管理处审核时,必须选择流转去向
|
||
if (showFlowTargetSection.value && !flowTarget.value) {
|
||
ElMessage.warning('请选择流转去向');
|
||
return false;
|
||
}
|
||
|
||
// 如果有待保存的文件,先保存文件
|
||
if (uploadForm.fileId) {
|
||
const saved = await saveUploadedFile();
|
||
if (!saved) return false;
|
||
}
|
||
|
||
// 自动调用暂存接口(可以没有文件)
|
||
try {
|
||
const params = {
|
||
purchaseId: effectivePurchaseId.value,
|
||
fileId: uploadForm.fileId || '',
|
||
fileName: uploadForm.fileName || '',
|
||
fileUrl: uploadForm.fileUrl || '',
|
||
comment: uploadForm.comment || '',
|
||
flowTarget: flowTarget.value || ''
|
||
};
|
||
|
||
const res = await tempStoreBidFile(params);
|
||
if (res.code === 0) {
|
||
await loadBidFiles();
|
||
// 标记为已保存
|
||
if (props.currJob && props.currElTab?.id) {
|
||
currElTabIsSave(props.currJob, props.currElTab.id, true, emit);
|
||
}
|
||
|
||
|
||
|
||
return true;
|
||
} else {
|
||
ElMessage.error(res.msg || '暂存失败');
|
||
return false;
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '暂存失败');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// 注册流程回调
|
||
const registerFlowCallbacks = () => {
|
||
if (!isFlowEmbed.value) return;
|
||
|
||
if (props.currJob?.resolveSaves) {
|
||
props.currJob.resolveSaves.push(handleFlowSave);
|
||
}
|
||
};
|
||
|
||
// ==================== 部门参与人选择 ====================
|
||
|
||
const loadDeptMembers = async () => {
|
||
if (!showRepresentorSection.value) return;
|
||
|
||
try {
|
||
deptMembersLoading.value = true;
|
||
const res = await getDeptMembers();
|
||
if (res.code === 0 && res.data) {
|
||
deptMembers.value = res.data;
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '加载部门人员失败');
|
||
} finally {
|
||
deptMembersLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleRandomSelect = async () => {
|
||
if (randomCandidates.value.length < 2) {
|
||
ElMessage.warning('请至少选择2名候选人');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
randomSelectLoading.value = true;
|
||
const res = await randomSelectRepresentor(
|
||
effectivePurchaseId.value,
|
||
randomCandidates.value.join(',')
|
||
);
|
||
if (res.code === 0 && res.data) {
|
||
currentRepresentor.value = res.data;
|
||
ElMessage.success(`随机抽取成功:${res.data.realName}`);
|
||
} else {
|
||
ElMessage.error(res.msg || '随机抽取失败');
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '随机抽取失败');
|
||
} finally {
|
||
randomSelectLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleSaveRepresentor = async () => {
|
||
if (!canSaveRepresentor.value) {
|
||
ElMessage.warning('请先选择参与人');
|
||
return;
|
||
}
|
||
|
||
if (!representorType.value) {
|
||
ElMessage.warning('请选择参与人身份');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
saveRepresentorLoading.value = true;
|
||
let res: any;
|
||
|
||
if (representorSelectMode.value === 'designate') {
|
||
res = await saveRepresentor(
|
||
Number(effectivePurchaseId.value),
|
||
selectedRepresentor.value,
|
||
undefined,
|
||
representorType.value
|
||
);
|
||
} else {
|
||
res = await saveRepresentor(
|
||
Number(effectivePurchaseId.value),
|
||
currentRepresentor.value?.teacherNo,
|
||
randomCandidates.value.join(','),
|
||
representorType.value
|
||
);
|
||
}
|
||
|
||
if (res.code === 0) {
|
||
ElMessage.success('保存参与人信息成功');
|
||
await loadApplyData();
|
||
} else {
|
||
ElMessage.error(res.msg || '保存失败');
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.msg || '保存失败');
|
||
} finally {
|
||
saveRepresentorLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// ==================== 生命周期 ====================
|
||
|
||
onMounted(async () => {
|
||
await loadApplyData();
|
||
await loadBidFiles();
|
||
if (isAgent.value) {
|
||
await loadRequirementFiles();
|
||
}
|
||
if (showRepresentorSection.value) {
|
||
await loadDeptMembers();
|
||
}
|
||
registerFlowCallbacks();
|
||
});
|
||
|
||
watch(
|
||
() => props.currJob,
|
||
async (newVal) => {
|
||
if (newVal) {
|
||
await loadApplyData();
|
||
await loadBidFiles();
|
||
if (isAgent.value) {
|
||
await loadRequirementFiles();
|
||
}
|
||
if (showRepresentorSection.value) {
|
||
await loadDeptMembers();
|
||
}
|
||
registerFlowCallbacks();
|
||
}
|
||
},
|
||
{ immediate: false }
|
||
);
|
||
|
||
watch(
|
||
() => route.query.id,
|
||
async (newId) => {
|
||
if (newId && !isFlowEmbed.value) {
|
||
await loadApplyData();
|
||
await loadBidFiles();
|
||
if (isAgent.value) {
|
||
await loadRequirementFiles();
|
||
}
|
||
}
|
||
},
|
||
{ immediate: false }
|
||
);
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.bidfile-audit-container {
|
||
padding: 16px;
|
||
min-height: 100%;
|
||
background: var(--el-fill-color-lighter);
|
||
}
|
||
|
||
.info-card,
|
||
.file-card,
|
||
.upload-card {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.card-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.version-detail {
|
||
padding: 16px 0;
|
||
}
|
||
|
||
.file-actions {
|
||
margin-top: 16px;
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
:deep(.el-descriptions__label) {
|
||
font-weight: 500;
|
||
}
|
||
|
||
:deep(.el-upload__tip) {
|
||
color: var(--el-text-color-secondary);
|
||
font-size: 12px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.save-tip {
|
||
margin-left: 12px;
|
||
color: var(--el-color-warning);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.flow-target-card {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.flow-target-card :deep(.el-radio-group) {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.flow-target-card :deep(.el-radio) {
|
||
height: auto;
|
||
line-height: 1.5;
|
||
}
|
||
</style> |