This commit is contained in:
吴红兵
2026-03-07 18:23:23 +08:00
parent 15b3efe51e
commit 0e54f887cb
4 changed files with 547 additions and 127 deletions

140
auto-imports.d.ts vendored
View File

@@ -1,73 +1,73 @@
// Generated by 'unplugin-auto-import' // Generated by 'unplugin-auto-import'
export {}; export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope']; const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']; const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']; const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']; const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']; const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']; const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']; const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']; const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']; const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']; const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']; const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']; const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']; const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']; const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']; const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']; const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']; const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']; const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']; const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']; const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']; const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']; const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']; const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']; const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']; const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']; const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']; const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']; const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']; const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']; const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']; const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']; const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']; const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']; const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']; const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']; const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']; const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']; const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']; const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']; const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']; const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']; const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']; const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']; const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']; const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']; const resolveComponent: typeof import('vue')['resolveComponent']
const resolveDirective: typeof import('vue')['resolveDirective']; const resolveDirective: typeof import('vue')['resolveDirective']
const setActivePinia: typeof import('pinia')['setActivePinia']; const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']; const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']; const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']; const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']; const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']; const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']; const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']; const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']; const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']; const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']; const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']; const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']; const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']; const useCssVars: typeof import('vue')['useCssVars']
const useLink: typeof import('vue-router')['useLink']; const useLink: typeof import('vue-router')['useLink']
const useRoute: typeof import('vue-router')['useRoute']; const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']; const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']; const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']; const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']; const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']; const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']; const watchSyncEffect: typeof import('vue')['watchSyncEffect']
} }

View File

@@ -416,3 +416,11 @@ export function getSupplementFileType(purchaseType: string) {
params: { purchaseType }, params: { purchaseType },
}); });
} }
export function getSupplementFilesByApplyId(applyId: string) {
return request({
url: '/purchase/purchasingfiles/listByType',
method: 'get',
params: { purchaseId: applyId },
});
}

View File

@@ -0,0 +1,316 @@
<template>
<el-dialog v-model="visible" title="补充材料详情" width="90%" top="5vh" destroy-on-close append-to-body>
<template #header>
<div class="dialog-header">
<span class="dialog-title">
<el-icon><FolderOpened /></el-icon>
补充材料详情 - {{ applyData.purchaseNo || applyId }}
</span>
</div>
</template>
<el-row :gutter="16">
<el-col :span="8">
<el-card shadow="never" class="info-card">
<template #header>
<div class="card-header">
<span class="card-title">采购申请信息</span>
</div>
</template>
<el-descriptions :column="1" border size="small">
<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="补充状态">
<el-tag v-if="applyData.supplementFlowStatus === '1'" type="success">已完成</el-tag>
<el-tag v-else-if="applyData.supplementFlowStatus === '0'" type="warning">审批中</el-tag>
<el-tag v-else-if="applyData.supplementFlowStatus === '-1'" type="info">已暂存</el-tag>
<el-tag v-else type="danger">未补充</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="file-card">
<template #header>
<div class="card-header">
<span class="card-title">补充材料文件</span>
</div>
</template>
<el-alert v-if="requiredFileTypeName" type="info" :closable="false" style="margin-bottom: 12px">
<template #title>
<span>{{ requiredFileTypeName }}</span>
</template>
</el-alert>
<el-table :data="fileList" border stripe size="small" v-if="fileList.length > 0" max-height="300">
<el-table-column type="index" label="序号" width="50" />
<el-table-column prop="name" label="文件名称" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center">
<template #default="scope">
<el-button type="primary" link size="small" icon="View" @click="handlePreview(scope.row)">预览</el-button>
<el-button type="success" link size="small" icon="Download" @click="handleDownload(scope.row)">下载</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-else description="暂无补充材料文件" :image-size="60" />
</el-card>
</el-col>
<el-col :span="16">
<el-card shadow="never" class="flow-card">
<template #header>
<div class="card-header">
<span class="card-title">审批流程</span>
</div>
</template>
<template v-if="applyData.supplementFlowInstId">
<FlowCommentTimeline :key="String(applyData.supplementFlowInstId) + 'supplement'" :curr-job="supplementFlowJob" />
</template>
<el-empty v-else description="暂未发起审批流程" :image-size="80" />
</el-card>
</el-col>
</el-row>
<template #footer>
<el-button @click="visible = false">关闭</el-button>
</template>
<el-dialog v-model="previewVisible" :title="previewTitle" width="80%" top="5vh" destroy-on-close append-to-body>
<div class="preview-container">
<iframe v-if="previewUrl" :src="previewUrl" class="preview-iframe" />
</div>
</el-dialog>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { FolderOpened } from '@element-plus/icons-vue';
import { useMessage } from '/@/hooks/message';
import { getObj, getSupplementFileType, getApplyFiles, previewFileById, downloadFileById } from '/@/api/purchase/purchasingrequisition';
import { getDicts } from '/@/api/admin/dict';
const FlowCommentTimeline = defineAsyncComponent(() => import('/@/views/jsonflow/comment/timeline.vue'));
interface FileItem {
id: string;
name: string;
url: string;
}
const visible = ref(false);
const applyId = ref('');
const applyData = ref<any>({});
const fileList = ref<FileItem[]>([]);
const purchaseTypeLabel = ref('');
const requiredFileTypeName = ref('');
const purchaseTypeDeptList = ref<any[]>([]);
const previewVisible = ref(false);
const previewTitle = ref('');
const previewUrl = ref('');
const PURCHASE_TYPE_DEPT_MAP: Record<string, string> = {
'1': '网上商城',
'2': '市场采购',
'3': '商务洽谈',
'5': '询价',
};
const FILE_TYPE_MAP: Record<string, string> = {
'10': '商务洽谈纪要',
'20': '市场采购纪要',
'30': '网上商城采购材料',
'150': '部门采购询价表',
};
const supplementFlowJob = computed(() => {
if (!applyData.value.supplementFlowInstId) return null;
return {
id: applyData.value.id,
flowInstId: applyData.value.supplementFlowInstId,
};
});
const loadDictData = async () => {
try {
const res = await getDicts('PURCHASE_TYPE_DEPT');
if (res.data && Array.isArray(res.data)) {
purchaseTypeDeptList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code,
}));
if (applyData.value.purchaseType) {
const found = purchaseTypeDeptList.value.find((item: any) => item.value === applyData.value.purchaseType);
if (found) {
purchaseTypeLabel.value = found.label;
}
}
}
} catch (e) {
console.error('加载字典数据失败', e);
}
};
const getPurchaseTypeLabel = () => {
if (applyData.value.purchaseType) {
purchaseTypeLabel.value = PURCHASE_TYPE_DEPT_MAP[applyData.value.purchaseType] || applyData.value.purchaseType;
}
};
const getRequiredFileTypeName = () => {
const purchaseType = applyData.value.purchaseType;
let fileTypeCode = '';
if (purchaseType === '3') {
fileTypeCode = '10';
} else if (purchaseType === '2') {
fileTypeCode = '20';
} else if (purchaseType === '1') {
fileTypeCode = '30';
} else if (purchaseType === '5') {
fileTypeCode = '150';
}
if (fileTypeCode) {
requiredFileTypeName.value = FILE_TYPE_MAP[fileTypeCode] || '补充材料';
}
return fileTypeCode;
};
const open = async (id: string, row?: any) => {
applyId.value = id || '';
visible.value = true;
fileList.value = [];
applyData.value = row || {};
purchaseTypeLabel.value = '';
requiredFileTypeName.value = '';
if (!row || !row.purchaseType) {
try {
const res = await getObj(id);
applyData.value = res?.data || {};
} catch (e) {
console.error('获取采购申请详情失败', e);
useMessage().error('获取采购申请详情失败');
return;
}
}
await loadDictData();
getPurchaseTypeLabel();
const fileTypeCode = getRequiredFileTypeName();
if (applyData.value.id && fileTypeCode) {
try {
const filesRes = await getApplyFiles(applyData.value.id);
const files = filesRes?.data || [];
const matchingFiles = files.filter((f: any) => String(f.fileType) === fileTypeCode);
if (matchingFiles.length > 0) {
fileList.value = matchingFiles.map((f: any) => ({
id: f.id,
name: f.fileTitle || f.fileName || '附件',
url: f.fileUrl,
}));
}
} catch (e) {
console.error('获取文件列表失败', e);
}
}
};
const handlePreview = async (row: FileItem) => {
if (!row.id) {
useMessage().warning('文件ID不存在');
return;
}
try {
const blob = await previewFileById(row.id);
previewUrl.value = window.URL.createObjectURL(blob);
previewTitle.value = row.name || '文件预览';
previewVisible.value = true;
} catch (e) {
console.error('预览失败', e);
useMessage().error('预览失败');
}
};
const handleDownload = async (row: FileItem) => {
if (!row.id) {
useMessage().warning('文件ID不存在');
return;
}
try {
const blob = await downloadFileById(row.id);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = row.name || '附件.pdf';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (e) {
console.error('下载失败', e);
useMessage().error('下载失败');
}
};
const emit = defineEmits<{
(e: 'refresh'): void;
}>();
defineExpose({
open,
});
</script>
<style scoped lang="scss">
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.dialog-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
.info-card,
.file-card,
.flow-card {
margin-bottom: 16px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
font-size: 14px;
font-weight: 500;
}
.preview-container {
width: 100%;
height: 75vh;
}
.preview-iframe {
width: 100%;
height: 100%;
border: none;
}
:deep(.el-card__header) {
padding: 12px 16px;
}
:deep(.el-card__body) {
padding: 12px 16px;
}
</style>

View File

@@ -74,12 +74,16 @@
> >
<el-table-column type="index" label="序号" width="70" align="center"> <el-table-column type="index" label="序号" width="70" align="center">
<template #header> <template #header>
<el-icon><List /></el-icon> <el-icon>
<List />
</el-icon>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="purchaseNo" label="申请单编号" min-width="180"> <el-table-column prop="purchaseNo" label="申请单编号" min-width="180">
<template #header> <template #header>
<el-icon><DocumentCopy /></el-icon> <el-icon>
<DocumentCopy />
</el-icon>
<span style="margin-left: 4px">申请单编号</span> <span style="margin-left: 4px">申请单编号</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -95,32 +99,42 @@
class="copy-btn" class="copy-btn"
@click.stop="copyToClipboard(scope.row.purchaseNo)" @click.stop="copyToClipboard(scope.row.purchaseNo)"
> >
<el-icon><CopyDocument /></el-icon> <el-icon>
<CopyDocument />
</el-icon>
</el-button> </el-button>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="projectName" label="采购项目名称" min-width="200" show-overflow-tooltip> <el-table-column prop="projectName" label="采购项目名称" min-width="200" show-overflow-tooltip>
<template #header> <template #header>
<el-icon><Document /></el-icon> <el-icon>
<Document />
</el-icon>
<span style="margin-left: 4px">采购项目名称</span> <span style="margin-left: 4px">采购项目名称</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="applyDate" label="填报日期" width="120" align="center" show-overflow-tooltip> <el-table-column prop="applyDate" label="填报日期" width="120" align="center" show-overflow-tooltip>
<template #header> <template #header>
<el-icon><Calendar /></el-icon> <el-icon>
<Calendar />
</el-icon>
<span style="margin-left: 4px">填报日期</span> <span style="margin-left: 4px">填报日期</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip> <el-table-column prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip>
<template #header> <template #header>
<el-icon><OfficeBuilding /></el-icon> <el-icon>
<OfficeBuilding />
</el-icon>
<span style="margin-left: 4px">需求部门</span> <span style="margin-left: 4px">需求部门</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="projectType" label="项目类别" min-width="200" align="left" show-overflow-tooltip> <el-table-column prop="projectType" label="项目类别" min-width="200" align="left" show-overflow-tooltip>
<template #header> <template #header>
<el-icon><Collection /></el-icon> <el-icon>
<Collection />
</el-icon>
<span style="margin-left: 4px">项目类别</span> <span style="margin-left: 4px">项目类别</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -139,7 +153,9 @@
</el-table-column> </el-table-column>
<el-table-column prop="budget" label="项目预算(元)" width="130" align="right"> <el-table-column prop="budget" label="项目预算(元)" width="130" align="right">
<template #header> <template #header>
<el-icon><Money /></el-icon> <el-icon>
<Money />
</el-icon>
<span style="margin-left: 4px">项目预算</span> <span style="margin-left: 4px">项目预算</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -148,7 +164,9 @@
</el-table-column> </el-table-column>
<el-table-column prop="isSpecial" label="是否特殊" width="100" align="center"> <el-table-column prop="isSpecial" label="是否特殊" width="100" align="center">
<template #header> <template #header>
<el-icon><Warning /></el-icon> <el-icon>
<Warning />
</el-icon>
<span style="margin-left: 4px">是否特殊</span> <span style="margin-left: 4px">是否特殊</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -161,7 +179,9 @@
</el-table-column> </el-table-column>
<el-table-column prop="isCentralized" label="是否集采" width="100" align="center"> <el-table-column prop="isCentralized" label="是否集采" width="100" align="center">
<template #header> <template #header>
<el-icon><CircleCheck /></el-icon> <el-icon>
<CircleCheck />
</el-icon>
<span style="margin-left: 4px">是否集采</span> <span style="margin-left: 4px">是否集采</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -170,9 +190,11 @@
<span v-else>-</span> <span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="purchaseMode" label="采购形式" width="140" align="center"> <el-table-column prop="purchaseMode" label="采购形式" width="160" align="center">
<template #header> <template #header>
<el-icon><Shop /></el-icon> <el-icon>
<Shop />
</el-icon>
<span style="margin-left: 4px">采购形式</span> <span style="margin-left: 4px">采购形式</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -181,14 +203,25 @@
<el-tag v-else-if="scope.row.purchaseMode === '2'" type="primary">学校统一采购</el-tag> <el-tag v-else-if="scope.row.purchaseMode === '2'" type="primary">学校统一采购</el-tag>
<span v-else>-</span> <span v-else>-</span>
<el-tag v-if="scope.row.purchaseMode === '1' && scope.row.purchaseChannel === '2'" type="warning" size="small" class="entrust-tag" <el-tag v-if="scope.row.purchaseMode === '1' && scope.row.purchaseChannel === '2'" type="warning" size="small" class="entrust-tag"
></el-tag >
</el-tag>
<el-tooltip
v-if="scope.row.purchaseMode === '1' && scope.row.purchaseChannel === '1'"
:content="getSupplementTooltip(scope.row)"
placement="top"
> >
<el-icon :class="['supplement-icon', getSupplementIconClass(scope.row)]" @click.stop="handleShowSupplement(scope.row)">
<component :is="getSupplementIcon(scope.row)" />
</el-icon>
</el-tooltip>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="purchaseType" label="采购方式" width="120" align="center"> <el-table-column prop="purchaseType" label="采购方式" width="120" align="center">
<template #header> <template #header>
<el-icon><Shop /></el-icon> <el-icon>
<Shop />
</el-icon>
<span style="margin-left: 4px">采购方式</span> <span style="margin-left: 4px">采购方式</span>
</template> </template>
<template #default="scope"> <template #default="scope">
@@ -198,29 +231,31 @@
</el-table-column> </el-table-column>
<el-table-column prop="status" label="审核状态" width="100" align="center"> <el-table-column prop="status" label="审核状态" width="100" align="center">
<template #header> <template #header>
<el-icon><InfoFilled /></el-icon> <el-icon>
<InfoFilled />
</el-icon>
<span style="margin-left: 4px">审核状态</span> <span style="margin-left: 4px">审核状态</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-tooltip v-if="scope.row.flowInstId" content="点击查看审批过程" placement="top"> <el-tooltip v-if="scope.row.flowInstId" content="点击查看审批过程" placement="top">
<el-tag v-if="scope.row.status === '-2'" type="info" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)" <el-tag v-if="scope.row.status === '-2'" type="info" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)"
>撤回</el-tag >撤回
> </el-tag>
<el-tag v-else-if="scope.row.status === '-1'" type="warning" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)" <el-tag v-else-if="scope.row.status === '-1'" type="warning" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)"
>暂存</el-tag >暂存
> </el-tag>
<el-tag v-else-if="scope.row.status === '0'" type="primary" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)" <el-tag v-else-if="scope.row.status === '0'" type="primary" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)"
>运行中</el-tag >运行中
> </el-tag>
<el-tag v-else-if="scope.row.status === '1'" type="success" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)" <el-tag v-else-if="scope.row.status === '1'" type="success" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)"
>完成</el-tag >完成
> </el-tag>
<el-tag v-else-if="scope.row.status === '2'" type="danger" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)" <el-tag v-else-if="scope.row.status === '2'" type="danger" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)"
>作废</el-tag >作废
> </el-tag>
<el-tag v-else-if="scope.row.status === '3'" type="info" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)" <el-tag v-else-if="scope.row.status === '3'" type="info" class="status-tag-clickable" @click="handleShowFlowComment(scope.row)"
>终止</el-tag >终止
> </el-tag>
<span v-else>-</span> <span v-else>-</span>
</el-tooltip> </el-tooltip>
<template v-else> <template v-else>
@@ -295,6 +330,9 @@
<!-- 补充材料弹窗 --> <!-- 补充材料弹窗 -->
<SupplementFilesDialog ref="supplementFilesDialogRef" @refresh="getDataList" /> <SupplementFilesDialog ref="supplementFilesDialogRef" @refresh="getDataList" />
<!-- 补充材料查看弹窗 -->
<SupplementViewDialog ref="supplementViewDialogRef" @refresh="getDataList" />
<!-- 招标文件审核弹窗 --> <!-- 招标文件审核弹窗 -->
<!-- <DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />--> <!-- <DocAuditDialog ref="docAuditDialogRef" @refresh="getDataList" />-->
@@ -389,6 +427,8 @@ import {
RefreshRight, RefreshRight,
Shop, Shop,
CopyDocument, CopyDocument,
Select,
CloseBold,
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import other from '/@/utils/other'; import other from '/@/utils/other';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
@@ -409,6 +449,7 @@ const FileArchiveDialog = defineAsyncComponent(() => import('./FileArchiveDialog
const UpdateFilesDialog = defineAsyncComponent(() => import('./UpdateFilesDialog.vue')); const UpdateFilesDialog = defineAsyncComponent(() => import('./UpdateFilesDialog.vue'));
const ContractDialog = defineAsyncComponent(() => import('./contract/ContractDialog.vue')); const ContractDialog = defineAsyncComponent(() => import('./contract/ContractDialog.vue'));
const SupplementFilesDialog = defineAsyncComponent(() => import('./SupplementFilesDialog.vue')); const SupplementFilesDialog = defineAsyncComponent(() => import('./SupplementFilesDialog.vue'));
const SupplementViewDialog = defineAsyncComponent(() => import('./SupplementViewDialog.vue'));
// const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue')); // const DocAuditDialog = defineAsyncComponent(() => import('./docAudit/DocAuditDialog.vue'));
// 字典数据和品目树数据 // 字典数据和品目树数据
@@ -441,6 +482,7 @@ const fileArchiveDialogRef = ref();
const updateFilesDialogRef = ref(); const updateFilesDialogRef = ref();
const contractDialogRef = ref(); const contractDialogRef = ref();
const supplementFilesDialogRef = ref(); const supplementFilesDialogRef = ref();
const supplementViewDialogRef = ref();
/** 采购代表弹窗 */ /** 采购代表弹窗 */
const representorDialogVisible = ref(false); const representorDialogVisible = ref(false);
@@ -561,6 +603,42 @@ const handleShowFileFlowComment = (row: any) => {
showFlowComment.value = true; showFlowComment.value = true;
}; };
const SUPPLEMENT_STATUS_MAP: Record<string, { icon: any; class: string; tooltip: string }> = {
'1': { icon: Select, class: 'supplement-done', tooltip: '补充材料已完成,点击查看' },
'0': { icon: CloseBold, class: 'supplement-running', tooltip: '补充材料审批中,点击查看' },
'-1': { icon: CloseBold, class: 'supplement-pending', tooltip: '补充材料已暂存,点击查看' },
};
const getSupplementIcon = (row: any) => {
const status = row?.supplementFlowStatus;
if (status === '1') return Select;
return CloseBold;
};
const getSupplementIconClass = (row: any) => {
const status = row?.supplementFlowStatus;
if (status === '1') return 'supplement-done';
if (status === '0') return 'supplement-running';
return 'supplement-pending';
};
const getSupplementTooltip = (row: any) => {
const status = row?.supplementFlowStatus;
if (status === '1') return '补充材料已完成,点击查看';
if (status === '0') return '补充材料审批中,点击查看';
if (status === '-1') return '补充材料已暂存,点击查看';
return '未补充材料,点击查看';
};
const handleShowSupplement = (row: any) => {
const id = row?.id ?? row?.purchaseId;
if (!id) {
useMessage().warning('无法获取采购申请ID');
return;
}
supplementViewDialogRef.value?.open(String(id), row);
};
/** /**
* 打开查看对话框 * 打开查看对话框
* @param row - 当前行数据 * @param row - 当前行数据
@@ -699,41 +777,36 @@ const getActionMenuItems = (row: any) => {
icon: Download, icon: Download,
visible: () => isCompleted, visible: () => isCompleted,
}, },
{ // {
command: 'representor', // command: 'representor',
label: '采购代表', // label: '采购代表',
icon: User, // icon: User,
visible: () => isCompleted && isDeptAuditRole.value, // visible: () => isCompleted && isDeptAuditRole.value,
}, // },
{ // {
command: 'updateFiles', // command: 'updateFiles',
label: '更新材料', // label: '更新材料',
icon: RefreshRight, // icon: RefreshRight,
visible: () => (isCompleted || isRunning) && hasAuth('purchase_purchasingapply_edit'), // visible: () => (isCompleted || isRunning) && hasAuth('purchase_purchasingapply_edit'),
}, // },
{ // {
command: 'contract', // command: 'contract',
label: '采购合同', // label: '采购合同',
icon: DocumentChecked, // icon: DocumentChecked,
visible: () => isCompleted, // visible: () => isCompleted,
}, // },
{ {
command: 'supplementFiles', command: 'supplementFiles',
label: '补充材料', label: '补充材料',
icon: Upload, icon: Upload,
visible: () => isCompleted && row?.purchaseMode === '1' && row?.purchaseChannel === '1' && hasAuth('purchase_supplement'), visible: () =>
}, isCompleted && row?.purchaseMode === '1' && row?.purchaseChannel === '1' && !row?.supplementFlowStatus && hasAuth('purchase_supplement'),
{
command: 'submitSupplementFiles',
label: '提交补充材料',
icon: Upload,
visible: () => isCompleted && row?.purchaseMode === '1' && row?.purchaseChannel === '1' && row?.supplementFlowStatus === '-1' && hasAuth('purchase_supplement'),
}, },
// { // {
// command: 'downloadFileApply', // command: 'downloadFileApply',
// label: '下载文件审批表', // label: '下载文件审批表',
// icon: Download, // icon: Download,
// visible: () => true, // visible: () => isCompleted,
// }, // },
// { // {
// command: 'docAudit', // command: 'docAudit',
@@ -1093,5 +1166,28 @@ onMounted(() => {
height: 18px; height: 18px;
line-height: 16px; line-height: 16px;
} }
.supplement-icon {
cursor: pointer;
font-size: 16px;
margin-left: 4px;
transition: transform 0.2s;
&:hover {
transform: scale(1.2);
}
}
.supplement-done {
color: #67c23a;
}
.supplement-running {
color: #e6a23c;
}
.supplement-pending {
color: #f56c6c;
}
} }
</style> </style>