v1流程下修复所有bug
parent
c66abdf33e
commit
949725ffc6
|
|
@ -159,3 +159,8 @@ export const cancelAcceptance = (acceptanceId: number, reason: string) => {
|
||||||
export const getTodoList = () => {
|
export const getTodoList = () => {
|
||||||
return request.get({ url: '/project/acceptance/todo' })
|
return request.get({ url: '/project/acceptance/todo' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取验收状态列表
|
||||||
|
export const getStatusList = () => {
|
||||||
|
return request.get<{ code: string; name: string }[]>({ url: '/project/acceptance/status-list' })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,60 @@
|
||||||
<template>
|
<template>
|
||||||
<ContentWrap>
|
<div class="p-4 bg-gray-50 min-h-screen">
|
||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<!-- 头部信息 -->
|
<!-- 头部 Hero Section -->
|
||||||
<!-- 头部信息 -->
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||||
<div class="bg-white p-5 mb-6 rounded-lg shadow-sm border border-gray-100">
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||||
<div class="flex justify-between items-start mb-8">
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl font-bold text-gray-800 flex items-center mb-3">
|
<div class="flex items-center gap-3 mb-2">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 tracking-tight">
|
||||||
{{ projectInfo.projectName || '项目验收' }}
|
{{ projectInfo.projectName || '项目验收' }}
|
||||||
<el-tag v-if="acceptance.acceptanceType === 'PRE'" type="info" effect="dark" class="ml-3 rounded-full px-3">预验收</el-tag>
|
|
||||||
<el-tag v-else-if="acceptance.acceptanceType === 'FINAL'" type="primary" effect="dark" class="ml-3 rounded-full px-3">终验</el-tag>
|
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-gray-500 text-sm flex items-center gap-4 bg-gray-50 px-4 py-2 rounded-md">
|
<el-tag
|
||||||
<span><span class="font-medium text-gray-700">项目编号:</span>{{ projectInfo.projectCode }}</span>
|
v-if="acceptance.acceptanceType === 'PRE'"
|
||||||
<el-divider direction="vertical" />
|
type="info"
|
||||||
<span><span class="font-medium text-gray-700">验收ID:</span>{{ acceptance.id }}</span>
|
effect="plain"
|
||||||
<el-divider direction="vertical" />
|
class="!rounded-full !px-3 font-medium border-gray-300"
|
||||||
<span><span class="font-medium text-gray-700">轮次:</span>第 {{ acceptance.round }} 轮</span>
|
>
|
||||||
|
预验收
|
||||||
|
</el-tag>
|
||||||
|
<el-tag
|
||||||
|
v-else-if="acceptance.acceptanceType === 'FINAL'"
|
||||||
|
type="primary"
|
||||||
|
effect="plain"
|
||||||
|
class="!rounded-full !px-3 font-medium border-blue-200"
|
||||||
|
>
|
||||||
|
终验
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Icon icon="ep:collection-tag" class="text-gray-400" />
|
||||||
|
<span>项目编号:<span class="text-gray-700 font-medium">{{ projectInfo.projectCode }}</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="hidden md:block w-px h-3.5 bg-gray-300"></div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Icon icon="ep:hash" class="text-gray-400" />
|
||||||
|
<span>验收ID:<span class="text-gray-700 font-medium">{{ acceptance.id }}</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="hidden md:block w-px h-3.5 bg-gray-300"></div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Icon icon="ep:refresh" class="text-gray-400" />
|
||||||
|
<span>轮次:<span class="text-gray-700 font-medium">R{{ acceptance.round }}</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<el-tag :type="getStatusType(acceptance.status)" size="large" effect="plain" class="text-base px-6 py-1.5 font-bold rounded-lg border-2">
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<el-tag :type="getStatusType(acceptance.status)" size="large" effect="dark" class="!px-4 !py-1.5 !text-base !font-semibold !rounded-lg shadow-sm">
|
||||||
{{ getStatusLabel(acceptance.status) }}
|
{{ getStatusLabel(acceptance.status) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 流程步骤 -->
|
<!-- 流程步骤 -->
|
||||||
<el-steps :active="activeStep" finish-status="success" align-center class="mb-2">
|
<div class="px-4 py-2 bg-gray-50 rounded-lg border border-gray-100">
|
||||||
|
<el-steps :active="activeStep" finish-status="success" align-center class="custom-steps">
|
||||||
<el-step title="提交材料" description="待提交/完善材料" />
|
<el-step title="提交材料" description="待提交/完善材料" />
|
||||||
<el-step title="初步审核" description="对口人/组长审核" />
|
<el-step title="初步审核" description="对口人/组长审核" />
|
||||||
<el-step title="终验申请" description="确认进入终验" />
|
<el-step title="终验申请" description="确认进入终验" />
|
||||||
|
|
@ -35,96 +62,113 @@
|
||||||
<el-step title="归档完成" description="流程结束" />
|
<el-step title="归档完成" description="流程结束" />
|
||||||
</el-steps>
|
</el-steps>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-divider />
|
<!-- 主要内容区域 -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
|
||||||
<el-tabs v-model="activeTab">
|
<!-- 左侧:详细信息 (占据 2/3 宽度) -->
|
||||||
|
<div class="lg:col-span-2 space-y-6">
|
||||||
|
<!-- Tab切换 -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden min-h-[500px]">
|
||||||
|
<el-tabs v-model="activeTab" class="custom-tabs">
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<el-tab-pane label="基本信息" name="info">
|
<el-tab-pane label="基本信息" name="info">
|
||||||
<el-descriptions :column="2" border>
|
<div class="p-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-12">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h3 class="text-base font-bold text-gray-900 flex items-center gap-2 pb-2 border-b border-gray-100">
|
||||||
|
<Icon icon="ep:user" /> 人员信息
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-[80px_1fr] gap-y-3 text-sm">
|
||||||
|
<span class="text-gray-500 text-right">项目联络人:</span>
|
||||||
|
<span class="font-medium text-gray-800">{{ liaisonUserName || '-' }}</span>
|
||||||
|
<span class="text-gray-500 text-right">对口人:</span>
|
||||||
|
<span class="font-medium text-gray-800">{{ serviceUserName || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-descriptions-item label="项目联络人">{{ liaisonUserName || '-' }}</el-descriptions-item>
|
<div class="space-y-4">
|
||||||
<el-descriptions-item label="对口人">{{ serviceUserName || '-' }}</el-descriptions-item>
|
<h3 class="text-base font-bold text-gray-900 flex items-center gap-2 pb-2 border-b border-gray-100">
|
||||||
<el-descriptions-item label="开始时间">{{ formatDate(acceptance.startTime) }}</el-descriptions-item>
|
<Icon icon="ep:timer" /> 时间及流程
|
||||||
<el-descriptions-item label="完成时间">{{ formatDate(acceptance.endTime) || '-' }}</el-descriptions-item>
|
</h3>
|
||||||
<el-descriptions-item label="流程实例ID">{{ acceptance.processInstanceId || '-' }}</el-descriptions-item>
|
<div class="grid grid-cols-[100px_1fr] gap-y-3 text-sm">
|
||||||
<el-descriptions-item label="创建时间">{{ formatDate(acceptance.createTime) }}</el-descriptions-item>
|
<span class="text-gray-500 text-right">开始时间:</span>
|
||||||
</el-descriptions>
|
<span class="font-medium text-gray-800">{{ formatDate(acceptance.startTime) }}</span>
|
||||||
|
<span class="text-gray-500 text-right">流程实例ID:</span>
|
||||||
|
<a class="text-blue-600 font-mono underline cursor-pointer hover:text-blue-800" :title="acceptance.processInstanceId">{{ acceptance.processInstanceId || '-' }}</a>
|
||||||
|
<span class="text-gray-500 text-right">创建时间:</span>
|
||||||
|
<span class="font-medium text-gray-800">{{ formatDate(acceptance.createTime) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 验收材料 -->
|
<!-- 验收材料 -->
|
||||||
<el-tab-pane label="验收材料" name="materials">
|
<el-tab-pane label="验收材料" name="materials">
|
||||||
|
<div class="p-6">
|
||||||
|
<!-- 联络人视图:显示当前阶段材料 -->
|
||||||
|
<template v-if="isLiaisonUser">
|
||||||
<div class="mb-6" v-if="canUploadMaterial">
|
<div class="mb-6" v-if="canUploadMaterial">
|
||||||
<el-alert
|
<el-alert
|
||||||
title="待办任务:上传验收材料"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
description="请完成下方所有必填材料的上传,确认无误后点击底部的【提交材料】按钮进入审核流程。"
|
class="!bg-blue-50 !border-blue-100 !text-blue-800"
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
|
||||||
<template v-for="(item, index) in materials" :key="index">
|
|
||||||
<el-card
|
|
||||||
shadow="hover"
|
|
||||||
class="relative transition-all duration-300 hover:shadow-lg hover:-translate-y-1 border-gray-200"
|
|
||||||
:body-style="{ padding: '16px' }"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-start">
|
<template #title>
|
||||||
<!-- 文件图标 -->
|
<span class="font-bold text-base">待办任务:上传验收材料</span>
|
||||||
<div class="w-12 h-12 flex items-center justify-center rounded-lg mr-4 shrink-0"
|
|
||||||
:class="getFileIconColor(item.fileName)">
|
|
||||||
<Icon :icon="getFileIcon(item.fileName)" size="28" color="#fff" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<div class="flex items-center justify-between mb-1">
|
|
||||||
<span class="font-bold text-gray-800 truncate text-[15px]" :title="item.materialName">
|
|
||||||
{{ item.materialName }}
|
|
||||||
</span>
|
|
||||||
<el-tag v-if="item.isRequired" type="danger" effect="plain" size="small" class="ml-2 px-1">必填</el-tag>
|
|
||||||
<el-tag v-else type="info" effect="plain" size="small" class="ml-2 px-1">可选</el-tag>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 上传状态/文件名 -->
|
|
||||||
<div class="h-[24px] mb-3 flex items-center">
|
|
||||||
<template v-if="item.fileUrl">
|
|
||||||
<el-tooltip :content="item.fileName" placement="top" :show-after="500">
|
|
||||||
<span class="text-xs text-blue-600 truncate underline cursor-pointer hover:text-blue-800"
|
|
||||||
@click="handlePreview(item)">
|
|
||||||
{{ item.fileName }}
|
|
||||||
</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</template>
|
</template>
|
||||||
<span v-else class="text-xs text-gray-400 italic">暂未上传文件</span>
|
<template #default>
|
||||||
|
<p class="mt-1 text-sm text-blue-600">请完成下方所有必填材料的上传,确认无误后点击底部的【提交材料】按钮进入审核流程。</p>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="flex items-center justify-between mt-2 pt-3 border-t border-gray-100">
|
<template v-for="(item, index) in materials" :key="index">
|
||||||
<div class="flex gap-2">
|
<div class="group relative border border-gray-200 rounded-xl p-4 transition-all duration-300 hover:shadow-md hover:border-blue-200 hover:bg-blue-50/20 bg-white">
|
||||||
<el-button
|
<div class="flex items-start gap-4">
|
||||||
v-if="item.fileUrl"
|
<!-- 文件图标 -->
|
||||||
link
|
<div class="w-12 h-12 flex items-center justify-center rounded-lg shrink-0 transition-transform group-hover:scale-105"
|
||||||
size="small"
|
:class="[getFileIconColor(item.fileName), 'bg-opacity-10']">
|
||||||
type="primary"
|
<Icon :icon="getFileIcon(item.fileName)" size="26" :class="item.fileUrl ? '' : 'grayscale opacity-50'" />
|
||||||
@click="handlePreview(item)"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:view" class="mr-1"/> 预览
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="item.fileUrl"
|
|
||||||
link
|
|
||||||
size="small"
|
|
||||||
type="success"
|
|
||||||
@click="handleDownload(item)"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:download" class="mr-1"/> 下载
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 上传按钮 -->
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<h4 class="font-semibold text-gray-800 truncate pr-2 text-[15px] group-hover:text-blue-700" :title="item.materialName">{{ item.materialName }}</h4>
|
||||||
|
<span v-if="item.isRequired" class="text-[10px] font-bold text-red-500 bg-red-50 px-1.5 py-0.5 rounded border border-red-100 shrink-0">必填</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 mb-3 h-5">
|
||||||
|
<template v-if="item.fileUrl">
|
||||||
|
<div class="flex items-center gap-1 text-xs text-gray-500 truncate">
|
||||||
|
<Icon icon="ep:document" class="text-gray-400" />
|
||||||
|
<span class="truncate underline cursor-pointer hover:text-blue-600" @click="handlePreview(item)">{{ item.fileName }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else class="text-xs text-gray-400 italic">暂未上传</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex items-center justify-end gap-2 pt-2 border-t border-gray-50 mt-2 opacity-60 group-hover:opacity-100 transition-opacity">
|
||||||
|
<!-- 预览 -->
|
||||||
|
<el-tooltip content="预览" placement="top" v-if="item.fileUrl">
|
||||||
|
<button @click="handlePreview(item)" class="p-2 rounded-full hover:bg-blue-50 text-gray-500 hover:text-blue-600 transition-all border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:view" size="20" />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 下载 -->
|
||||||
|
<el-tooltip content="下载" placement="top" v-if="item.fileUrl">
|
||||||
|
<button @click="handleDownload(item)" class="p-2 rounded-full hover:bg-green-50 text-gray-500 hover:text-green-600 transition-all border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:download" size="20" />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 上传/重新上传 -->
|
||||||
<div v-if="canUploadMaterial">
|
<div v-if="canUploadMaterial">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="uploadUrl"
|
:action="uploadUrl"
|
||||||
|
|
@ -132,96 +176,296 @@
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:on-success="(res, file) => handleUploadSuccess(res, file, item)"
|
:on-success="(res, file) => handleUploadSuccess(res, file, item)"
|
||||||
>
|
>
|
||||||
<el-button
|
<el-tooltip :content="item.fileUrl ? '重新上传' : '上传文件'" placement="top">
|
||||||
size="small"
|
<button class="p-2 rounded-full hover:bg-blue-50 text-gray-500 hover:text-blue-600 transition-all border-none outline-none focus:outline-none">
|
||||||
:type="item.fileUrl ? 'primary' : 'primary'"
|
<Icon :icon="item.fileUrl ? 'ep:refresh' : 'ep:upload'" size="20" />
|
||||||
:plain="!!item.fileUrl"
|
</button>
|
||||||
class="!rounded-md"
|
</el-tooltip>
|
||||||
>
|
|
||||||
<Icon icon="ep:upload" class="mr-1"/>
|
|
||||||
{{ item.fileUrl ? '重新上传' : '上传材料' }}
|
|
||||||
</el-button>
|
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 状态角标 -->
|
<!-- Corner Status Mark -->
|
||||||
<div class="absolute top-0 right-0">
|
<div v-if="item.fileUrl" class="absolute -top-[1px] -right-[1px] w-0 h-0 border-t-[32px] border-r-[32px] border-t-transparent border-r-green-500 rounded-tr-xl">
|
||||||
<div v-if="item.fileUrl" class="bg-green-100 text-green-600 text-[10px] px-2 py-0.5 rounded-bl-lg font-medium">已完成</div>
|
<Icon icon="ep:check" class="absolute -right-[26px] -top-[26px] text-white text-xs"/>
|
||||||
<div v-else class="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded-bl-lg">待上传</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 其他角色视图:分组显示预验收和终验材料 -->
|
||||||
|
<template v-else>
|
||||||
|
<!-- 预验收材料区域 -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h3 class="text-lg font-bold mb-4">
|
||||||
|
<span class="inline-block px-4 py-2 rounded-lg bg-blue-100 text-blue-800 w-40 text-center">预验收材料</span>
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<template v-for="(item, index) in preMaterials" :key="'pre-' + index">
|
||||||
|
<div class="group relative border border-gray-200 rounded-xl p-4 transition-all duration-300 hover:shadow-md hover:border-blue-200 hover:bg-blue-50/20 bg-white">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<!-- 文件图标 -->
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center rounded-lg shrink-0 transition-transform group-hover:scale-105"
|
||||||
|
:class="[getFileIconColor(item.fileName), 'bg-opacity-10']">
|
||||||
|
<Icon :icon="getFileIcon(item.fileName)" size="26" :class="item.fileUrl ? '' : 'grayscale opacity-50'" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<h4 class="font-semibold text-gray-800 truncate pr-2 text-[15px] group-hover:text-blue-700" :title="item.materialName">{{ item.materialName }}</h4>
|
||||||
|
<span v-if="item.isRequired" class="text-[10px] font-bold text-red-500 bg-red-50 px-1.5 py-0.5 rounded border border-red-100 shrink-0">必填</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 mb-3 h-5">
|
||||||
|
<template v-if="item.fileUrl">
|
||||||
|
<div class="flex items-center gap-1 text-xs text-gray-500 truncate">
|
||||||
|
<Icon icon="ep:document" class="text-gray-400" />
|
||||||
|
<span class="truncate underline cursor-pointer hover:text-blue-600" @click="handlePreview(item)">{{ item.fileName }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else class="text-xs text-gray-400 italic">暂未上传</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex items-center justify-end gap-2 pt-2 border-t border-gray-50 mt-2 opacity-60 group-hover:opacity-100 transition-opacity">
|
||||||
|
<!-- 预览 -->
|
||||||
|
<el-tooltip content="预览" placement="top" v-if="item.fileUrl">
|
||||||
|
<button @click="handlePreview(item)" class="p-2 rounded-full hover:bg-blue-50 text-gray-500 hover:text-blue-600 transition-all border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:view" size="20" />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 下载 -->
|
||||||
|
<el-tooltip content="下载" placement="top" v-if="item.fileUrl">
|
||||||
|
<button @click="handleDownload(item)" class="p-2 rounded-full hover:bg-green-50 text-gray-500 hover:text-green-600 transition-all border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:download" size="20" />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 历史版本 -->
|
||||||
|
<el-tooltip :content="'历史版本 (' + item.versionCount + ')'" placement="top" v-if="item.versionCount > 1">
|
||||||
|
<button @click="showVersionHistory(item)" class="p-2 rounded-full hover:bg-orange-50 text-gray-500 hover:text-orange-600 transition-all relative border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:clock" size="20" />
|
||||||
|
<!-- 小红点提示 -->
|
||||||
|
<span class="absolute top-0.5 right-0.5 w-1.5 h-1.5 bg-orange-500 rounded-full border border-white"></span>
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Corner Status Mark -->
|
||||||
|
<div v-if="item.fileUrl" class="absolute -top-[1px] -right-[1px] w-0 h-0 border-t-[32px] border-r-[32px] border-t-transparent border-r-green-500 rounded-tr-xl">
|
||||||
|
<Icon icon="ep:check" class="absolute -right-[26px] -top-[26px] text-white text-xs"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="preMaterials.length === 0" class="col-span-2 text-center py-8 text-gray-400">
|
||||||
|
暂无预验收材料
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 终验材料区域 -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold mb-4">
|
||||||
|
<span class="inline-block px-4 py-2 rounded-lg bg-purple-100 text-purple-800 w-40 text-center">终验材料</span>
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<template v-for="(item, index) in finalMaterials" :key="'final-' + index">
|
||||||
|
<div class="group relative border border-gray-200 rounded-xl p-4 transition-all duration-300 hover:shadow-md hover:border-blue-200 hover:bg-blue-50/20 bg-white">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<!-- 文件图标 -->
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center rounded-lg shrink-0 transition-transform group-hover:scale-105"
|
||||||
|
:class="[getFileIconColor(item.fileName), 'bg-opacity-10']">
|
||||||
|
<Icon :icon="getFileIcon(item.fileName)" size="26" :class="item.fileUrl ? '' : 'grayscale opacity-50'" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<h4 class="font-semibold text-gray-800 truncate pr-2 text-[15px] group-hover:text-blue-700" :title="item.materialName">{{ item.materialName }}</h4>
|
||||||
|
<span v-if="item.isRequired" class="text-[10px] font-bold text-red-500 bg-red-50 px-1.5 py-0.5 rounded border border-red-100 shrink-0">必填</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 mb-3 h-5">
|
||||||
|
<template v-if="item.fileUrl">
|
||||||
|
<div class="flex items-center gap-1 text-xs text-gray-500 truncate">
|
||||||
|
<Icon icon="ep:document" class="text-gray-400" />
|
||||||
|
<span class="truncate underline cursor-pointer hover:text-blue-600" @click="handlePreview(item)">{{ item.fileName }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else class="text-xs text-gray-400 italic">暂未上传</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex items-center justify-end gap-2 pt-2 border-t border-gray-50 mt-2 opacity-60 group-hover:opacity-100 transition-opacity">
|
||||||
|
<!-- 预览 -->
|
||||||
|
<el-tooltip content="预览" placement="top" v-if="item.fileUrl">
|
||||||
|
<button @click="handlePreview(item)" class="p-2 rounded-full hover:bg-blue-50 text-gray-500 hover:text-blue-600 transition-all border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:view" size="20" />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 下载 -->
|
||||||
|
<el-tooltip content="下载" placement="top" v-if="item.fileUrl">
|
||||||
|
<button @click="handleDownload(item)" class="p-2 rounded-full hover:bg-green-50 text-gray-500 hover:text-green-600 transition-all border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:download" size="20" />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 历史版本 -->
|
||||||
|
<el-tooltip :content="'历史版本 (' + item.versionCount + ')'" placement="top" v-if="item.versionCount > 1">
|
||||||
|
<button @click="showVersionHistory(item)" class="p-2 rounded-full hover:bg-orange-50 text-gray-500 hover:text-orange-600 transition-all relative border-none outline-none focus:outline-none">
|
||||||
|
<Icon icon="ep:clock" size="20" />
|
||||||
|
<!-- 小红点提示 -->
|
||||||
|
<span class="absolute top-0.5 right-0.5 w-1.5 h-1.5 bg-orange-500 rounded-full border border-white"></span>
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Corner Status Mark -->
|
||||||
|
<div v-if="item.fileUrl" class="absolute -top-[1px] -right-[1px] w-0 h-0 border-t-[32px] border-r-[32px] border-t-transparent border-r-green-500 rounded-tr-xl">
|
||||||
|
<Icon icon="ep:check" class="absolute -right-[26px] -top-[26px] text-white text-xs"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="finalMaterials.length === 0" class="col-span-2 text-center py-8 text-gray-400">
|
||||||
|
暂无终验材料
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 审批记录 -->
|
<!-- 审批记录 -->
|
||||||
<el-tab-pane label="审批记录" name="opinions">
|
<el-tab-pane label="审批记录" name="opinions">
|
||||||
<el-timeline>
|
<div class="p-6">
|
||||||
|
<el-timeline class="pl-2">
|
||||||
<el-timeline-item
|
<el-timeline-item
|
||||||
v-for="(opinion, index) in opinions"
|
v-for="(opinion, index) in opinions"
|
||||||
:key="index"
|
:key="index"
|
||||||
:type="getOpinionType(opinion.result)"
|
:type="getOpinionType(opinion.result)"
|
||||||
|
:color="getOpinionColor(opinion.result)"
|
||||||
:timestamp="formatDate(opinion.createTime)"
|
:timestamp="formatDate(opinion.createTime)"
|
||||||
placement="top"
|
placement="top"
|
||||||
|
size="large"
|
||||||
>
|
>
|
||||||
<el-card>
|
<div class="bg-white border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow relative">
|
||||||
<h4 class="mb-2">
|
<!-- Arrow -->
|
||||||
{{ opinion.reviewerRole || '审核人' }}:{{ opinion.reviewerName || '未知' }}
|
<div class="absolute w-3 h-3 bg-white border-l border-b border-gray-200 transform rotate-45 -left-[7px] top-4"></div>
|
||||||
<el-tag v-if="opinion.isLeader" type="warning" size="small" class="ml-1">组长</el-tag>
|
|
||||||
<el-tag :type="getOpinionType(opinion.result)" size="small" class="ml-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-semibold text-gray-900">{{ opinion.reviewerRole || '审核人' }}</span>
|
||||||
|
<span class="text-gray-500 text-sm">- {{ opinion.reviewerName || '未知' }}</span>
|
||||||
|
<el-tag v-if="opinion.isLeader" type="warning" size="small" effect="dark" class="rounded-full">组长</el-tag>
|
||||||
|
</div>
|
||||||
|
<el-tag :type="getOpinionType(opinion.result)" effect="plain" class="font-bold uppercase tracking-wider">
|
||||||
{{ getOpinionLabel(opinion.result) }}
|
{{ getOpinionLabel(opinion.result) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</h4>
|
</div>
|
||||||
<p class="text-gray-600">{{ opinion.opinion || '无意见' }}</p>
|
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed mb-3 bg-gray-50 p-2 rounded">
|
||||||
|
{{ opinion.opinion || '无意见' }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div v-if="opinion.signatureUrl" class="border-t border-gray-100 pt-2 flex justify-end">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="opinion.signatureUrl"
|
|
||||||
:src="opinion.signatureUrl"
|
:src="opinion.signatureUrl"
|
||||||
style="width: 150px; height: 60px; margin-top: 8px"
|
class="h-10 w-auto opacity-80 hover:opacity-100"
|
||||||
fit="contain"
|
fit="contain"
|
||||||
/>
|
/>
|
||||||
</el-card>
|
</div>
|
||||||
|
</div>
|
||||||
</el-timeline-item>
|
</el-timeline-item>
|
||||||
<el-empty v-if="opinions.length === 0" description="暂无审批记录" />
|
<el-empty v-if="opinions.length === 0" description="暂无审批记录" :image-size="100" />
|
||||||
</el-timeline>
|
</el-timeline>
|
||||||
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 右侧:操作区 (占据 1/3 宽度) -->
|
||||||
<div class="mt-6 flex justify-end gap-2" v-if="showActions">
|
<div class="lg:col-span-1 space-y-6">
|
||||||
<el-button @click="goBack">返回</el-button>
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 sticky top-4">
|
||||||
|
<h3 class="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||||
|
<Icon icon="ep:operation" class="text-blue-500" /> 快捷操作
|
||||||
|
</h3>
|
||||||
|
<div class="space-y-3 flex flex-col">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canSubmitMaterial"
|
v-if="canSubmitMaterial"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
class="!w-full !justify-start !text-left !py-5"
|
||||||
@click="handleSubmitMaterial"
|
@click="handleSubmitMaterial"
|
||||||
>
|
>
|
||||||
提交材料
|
<Icon icon="ep:upload-filled" class="mr-2 text-xl" />
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="font-bold">提交材料</span>
|
||||||
|
<span class="text-xs opacity-80 font-normal">确认所有必填材料已上传</span>
|
||||||
|
</div>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="canAudit"
|
||||||
|
type="success"
|
||||||
|
size="large"
|
||||||
|
class="!w-full !justify-start !text-left !py-5"
|
||||||
|
@click="openAuditDialog"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:checked" class="mr-2 text-xl" />
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="font-bold">审核通过/驳回</span>
|
||||||
|
<span class="text-xs opacity-80 font-normal">对提交内容进行审批</span>
|
||||||
|
</div>
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canSubmitRectify"
|
v-if="canSubmitRectify"
|
||||||
type="warning"
|
type="warning"
|
||||||
|
size="large"
|
||||||
|
class="!w-full !justify-start !text-left !py-5"
|
||||||
@click="handleSubmitRectify"
|
@click="handleSubmitRectify"
|
||||||
>
|
>
|
||||||
提交整改
|
<Icon icon="ep:refresh-right" class="mr-2 text-xl" />
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="font-bold">提交整改</span>
|
||||||
|
<span class="text-xs opacity-80 font-normal">整改完成后再次提交</span>
|
||||||
|
</div>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canSubmitFinal"
|
v-if="canSubmitFinal"
|
||||||
type="success"
|
type="primary"
|
||||||
|
plain
|
||||||
|
size="large"
|
||||||
|
class="!w-full !justify-start !text-left !py-5"
|
||||||
@click="handleSubmitFinal"
|
@click="handleSubmitFinal"
|
||||||
>
|
>
|
||||||
提交终验申请
|
<Icon icon="ep:flag" class="mr-2 text-xl" />
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="font-bold">提交终验申请</span>
|
||||||
|
<span class="text-xs opacity-80 font-normal">初审通过,进入终验</span>
|
||||||
|
</div>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
|
||||||
v-if="canAudit"
|
<el-divider class="!my-4" />
|
||||||
type="primary"
|
|
||||||
@click="openAuditDialog"
|
<el-button @click="goBack" class="!w-full" plain>
|
||||||
>
|
<Icon icon="ep:back" class="mr-1" /> 返回列表
|
||||||
<Icon icon="ep:check" class="mr-1" /> 审核
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 审核对话框 -->
|
<!-- 审核对话框 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
|
@ -229,38 +473,111 @@
|
||||||
title="验收审核"
|
title="验收审核"
|
||||||
width="500px"
|
width="500px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
|
class="rounded-xl overflow-hidden"
|
||||||
>
|
>
|
||||||
|
<div class="p-2">
|
||||||
<el-form
|
<el-form
|
||||||
ref="auditFormRef"
|
ref="auditFormRef"
|
||||||
:model="auditForm"
|
:model="auditForm"
|
||||||
:rules="auditFormRules"
|
:rules="auditFormRules"
|
||||||
label-width="100px"
|
label-width="100px"
|
||||||
|
label-position="top"
|
||||||
>
|
>
|
||||||
<el-form-item label="审核结果" prop="result">
|
<el-form-item label="审核结果" prop="result">
|
||||||
<el-radio-group v-model="auditForm.result">
|
<div class="flex w-full gap-4">
|
||||||
<el-radio-button value="PASS">
|
<div
|
||||||
<Icon icon="ep:check" class="mr-1" /> 通过
|
class="flex-1 border rounded-lg p-4 cursor-pointer text-center transition-all hover:shadow-md"
|
||||||
</el-radio-button>
|
:class="auditForm.result === 'PASS' ? 'border-green-500 bg-green-50 text-green-700' : 'border-gray-200 text-gray-500 hover:border-green-200'"
|
||||||
<el-radio-button value="REJECT">
|
@click="auditForm.result = 'PASS'"
|
||||||
<Icon icon="ep:close" class="mr-1" /> 驳回
|
>
|
||||||
</el-radio-button>
|
<Icon icon="ep:circle-check-filled" class="text-3xl mb-2" :class="auditForm.result === 'PASS' ? 'text-green-500' : 'text-gray-300'" />
|
||||||
</el-radio-group>
|
<div class="font-bold">通过</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex-1 border rounded-lg p-4 cursor-pointer text-center transition-all hover:shadow-md"
|
||||||
|
:class="auditForm.result === 'REJECT' ? 'border-red-500 bg-red-50 text-red-700' : 'border-gray-200 text-gray-500 hover:border-red-200'"
|
||||||
|
@click="auditForm.result = 'REJECT'"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:circle-close-filled" class="text-3xl mb-2" :class="auditForm.result === 'REJECT' ? 'text-red-500' : 'text-gray-300'" />
|
||||||
|
<div class="font-bold">驳回</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="审核意见" prop="opinion">
|
<el-form-item label="审核意见" prop="opinion">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="auditForm.opinion"
|
v-model="auditForm.opinion"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="4"
|
:rows="4"
|
||||||
:placeholder="auditForm.result === 'REJECT' ? '请填写驳回理由...' : '请填写审核意见...'"
|
:placeholder="auditForm.result === 'REJECT' ? '请务必填写驳回理由...' : '请填写审核意见(可选)...'"
|
||||||
|
class="!bg-gray-50"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2 pt-2">
|
||||||
<el-button @click="auditDialogVisible = false">取消</el-button>
|
<el-button @click="auditDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleAudit">确认提交</el-button>
|
<el-button type="primary" @click="handleAudit" class="px-6">确认提交</el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</ContentWrap>
|
|
||||||
|
<!-- 历史版本对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="versionHistoryVisible"
|
||||||
|
:title="'历史版本 - ' + currentMaterialName"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
class="rounded-xl overflow-hidden"
|
||||||
|
>
|
||||||
|
<div class="p-4 max-h-[60vh] overflow-y-auto">
|
||||||
|
<el-timeline>
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="(version, index) in currentVersions"
|
||||||
|
:key="version.id"
|
||||||
|
:timestamp="formatDate(version.createTime)"
|
||||||
|
placement="top"
|
||||||
|
:type="index === 0 ? 'primary' : ''"
|
||||||
|
:hollow="index !== 0"
|
||||||
|
>
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-100 hover:shadow-sm transition-shadow">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold text-gray-800">V{{ currentVersions.length - index }}</span>
|
||||||
|
<el-tag v-if="index === 0" size="small" type="success" effect="dark" class="rounded-full">最新</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<el-button size="small" text bg type="primary" @click="handlePreview(version)">
|
||||||
|
<Icon icon="ep:view" class="mr-1" /> 预览
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" text bg type="success" @click="handleDownload(version)">
|
||||||
|
<Icon icon="ep:download" class="mr-1" /> 下载
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-600 grid grid-cols-2 gap-2 mt-2">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Icon icon="ep:document" class="text-gray-400" />
|
||||||
|
<span class="truncate" :title="version.fileName">{{ version.fileName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Icon icon="ep:data-line" class="text-gray-400" />
|
||||||
|
<span>{{ formatFileSize(version.fileSize) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end pt-2">
|
||||||
|
<el-button @click="versionHistoryVisible = false">关闭</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
@ -280,7 +597,8 @@ const router = useRouter()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const activeTab = ref('info')
|
// 从路由参数读取默认tab,默认显示基本信息tab
|
||||||
|
const activeTab = ref((route.query.tab as string) || 'info')
|
||||||
|
|
||||||
// 验收信息
|
// 验收信息
|
||||||
const acceptance = ref<AcceptanceApi.AcceptanceVO>({} as AcceptanceApi.AcceptanceVO)
|
const acceptance = ref<AcceptanceApi.AcceptanceVO>({} as AcceptanceApi.AcceptanceVO)
|
||||||
|
|
@ -289,11 +607,21 @@ const projectInfo = ref<any>({})
|
||||||
// 用户名称
|
// 用户名称
|
||||||
const liaisonUserName = ref('')
|
const liaisonUserName = ref('')
|
||||||
const serviceUserName = ref('')
|
const serviceUserName = ref('')
|
||||||
// 验收材料
|
// 验收材料(联络人使用)
|
||||||
const materials = ref<any[]>([])
|
const materials = ref<any[]>([])
|
||||||
|
// 预验收材料(其他角色使用)
|
||||||
|
const preMaterials = ref<any[]>([])
|
||||||
|
// 终验材料(其他角色使用)
|
||||||
|
const finalMaterials = ref<any[]>([])
|
||||||
// 审批意见
|
// 审批意见
|
||||||
const opinions = ref<any[]>([])
|
const opinions = ref<any[]>([])
|
||||||
|
|
||||||
|
// 判断当前用户是否为项目联络人
|
||||||
|
const isLiaisonUser = computed(() => {
|
||||||
|
const currentUserId = useUserStore().getUser.id
|
||||||
|
return projectInfo.value.liaisonUserId === currentUserId
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// 上传配置
|
// 上传配置
|
||||||
const uploadUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
|
const uploadUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
|
||||||
|
|
@ -331,12 +659,18 @@ const getOpinionType = (result: string) => {
|
||||||
return 'info'
|
return 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOpinionColor = (result: string) => {
|
||||||
|
if (result === 'PASS') return '#67C23A'
|
||||||
|
if (result === 'REJECT') return '#F56C6C'
|
||||||
|
return '#909399'
|
||||||
|
}
|
||||||
|
|
||||||
// 权限判断
|
// 权限判断
|
||||||
// 权限判断
|
// 权限判断
|
||||||
const canUploadMaterial = computed(() => {
|
const canUploadMaterial = computed(() => {
|
||||||
const currentUserId = useUserStore().getUser.id
|
const currentUserId = useUserStore().getUser.id
|
||||||
// 必须是联络人,且状态符合
|
// 必须是联络人,且状态符合:待提交材料(05)、预验收整改(11)、待终验申请(30)、终验整改(60)
|
||||||
return projectInfo.value.liaisonUserId === currentUserId && ['05', '11', '60'].includes(acceptance.value.status)
|
return projectInfo.value.liaisonUserId === currentUserId && ['05', '11', '30', '60'].includes(acceptance.value.status)
|
||||||
})
|
})
|
||||||
|
|
||||||
const canSubmitMaterial = computed(() => {
|
const canSubmitMaterial = computed(() => {
|
||||||
|
|
@ -358,7 +692,7 @@ const showActions = computed(() => {
|
||||||
return canSubmitMaterial.value || canSubmitRectify.value || canSubmitFinal.value || canAudit.value
|
return canSubmitMaterial.value || canSubmitRectify.value || canSubmitFinal.value || canAudit.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否可以审核 (对口人初审: 10, 专家组长审核: 20)
|
// 是否可以审核 (对口人初审: 10, 专家组长审核: 20, 管理员终验初审: 40)
|
||||||
const canAudit = computed(() => {
|
const canAudit = computed(() => {
|
||||||
const currentUserId = useUserStore().getUser.id
|
const currentUserId = useUserStore().getUser.id
|
||||||
const status = acceptance.value.status
|
const status = acceptance.value.status
|
||||||
|
|
@ -370,6 +704,10 @@ const canAudit = computed(() => {
|
||||||
if (status === '20') {
|
if (status === '20') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// 状态 40: 管理员终验初审,显示审核按钮(后端会校验权限)
|
||||||
|
if (status === '40') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -385,6 +723,27 @@ const auditFormRules = {
|
||||||
opinion: [{ required: true, message: '请填写审核意见', trigger: 'blur' }]
|
opinion: [{ required: true, message: '请填写审核意见', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 历史版本对话框
|
||||||
|
const versionHistoryVisible = ref(false)
|
||||||
|
const currentVersions = ref<any[]>([])
|
||||||
|
const currentMaterialName = ref('')
|
||||||
|
|
||||||
|
/** 显示历史版本 */
|
||||||
|
const showVersionHistory = (item: any) => {
|
||||||
|
currentMaterialName.value = item.materialName
|
||||||
|
currentVersions.value = item.versions || []
|
||||||
|
versionHistoryVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 格式化文件大小 */
|
||||||
|
const formatFileSize = (bytes: number) => {
|
||||||
|
if (!bytes) return '0 B'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
||||||
|
}
|
||||||
|
|
||||||
/** 打开审核对话框 */
|
/** 打开审核对话框 */
|
||||||
const openAuditDialog = () => {
|
const openAuditDialog = () => {
|
||||||
auditForm.value = { result: '', opinion: '' }
|
auditForm.value = { result: '', opinion: '' }
|
||||||
|
|
@ -398,11 +757,22 @@ const handleAudit = async () => {
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await AcceptanceApi.auditPreAcceptance({
|
const auditData = {
|
||||||
acceptanceId: acceptance.value.id,
|
acceptanceId: acceptance.value.id,
|
||||||
result: auditForm.value.result,
|
result: auditForm.value.result,
|
||||||
opinion: auditForm.value.opinion
|
opinion: auditForm.value.opinion
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// 根据状态调用不同的审核接口
|
||||||
|
const status = acceptance.value.status
|
||||||
|
if (status === '40') {
|
||||||
|
// 管理员终验初审
|
||||||
|
await AcceptanceApi.auditFinalAdmin(auditData)
|
||||||
|
} else {
|
||||||
|
// 预验收审核(对口人初审10、组长审核20)
|
||||||
|
await AcceptanceApi.auditPreAcceptance(auditData)
|
||||||
|
}
|
||||||
|
|
||||||
message.success('审核提交成功')
|
message.success('审核提交成功')
|
||||||
auditDialogVisible.value = false
|
auditDialogVisible.value = false
|
||||||
await getDetail()
|
await getDetail()
|
||||||
|
|
@ -427,17 +797,18 @@ const getDetail = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取验收材料
|
// 获取验收材料
|
||||||
// 1. 获取材料定义
|
|
||||||
const defs = await AcceptanceMaterialDefApi.getListByType(acceptance.value.acceptanceType)
|
|
||||||
// 2. 获取已上传材料
|
// 2. 获取已上传材料
|
||||||
const uploads = await AcceptanceMaterialApi.getListByAcceptanceId(id) as unknown as any[]
|
const uploads = await AcceptanceMaterialApi.getListByAcceptanceId(id) as unknown as any[]
|
||||||
|
|
||||||
// 3. 合并数据
|
// 材料合并辅助函数
|
||||||
materials.value = defs.map((def: any) => {
|
const mergeMaterials = (defs: any[]) => {
|
||||||
|
return defs.map((def: any) => {
|
||||||
// 查找该材料代码对应的所有上传记录
|
// 查找该材料代码对应的所有上传记录
|
||||||
const allUploads = uploads.filter((u: any) => u.materialCode === def.materialCode)
|
const allUploads = uploads.filter((u: any) => u.materialCode === def.materialCode)
|
||||||
// 取最新的一条(按ID倒序,ID越大越新)
|
// 按ID倒序排序(最新的在前)
|
||||||
const upload = allUploads.length > 0 ? allUploads.sort((a: any, b: any) => b.id - a.id)[0] : undefined
|
const sortedUploads = allUploads.sort((a: any, b: any) => b.id - a.id)
|
||||||
|
// 取最新的一条
|
||||||
|
const upload = sortedUploads.length > 0 ? sortedUploads[0] : undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...def,
|
...def,
|
||||||
|
|
@ -447,9 +818,34 @@ const getDetail = async () => {
|
||||||
fileName: upload?.fileName,
|
fileName: upload?.fileName,
|
||||||
fileSize: upload?.fileSize,
|
fileSize: upload?.fileSize,
|
||||||
uploadId: upload?.id,
|
uploadId: upload?.id,
|
||||||
acceptanceId: id
|
acceptanceId: id,
|
||||||
|
// 新增:保存所有历史版本
|
||||||
|
versions: sortedUploads, // 包含所有上传记录
|
||||||
|
versionCount: sortedUploads.length // 版本数量
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断当前用户是否为联络人
|
||||||
|
const currentUserId = useUserStore().getUser.id
|
||||||
|
const isLiaison = projectInfo.value.liaisonUserId === currentUserId
|
||||||
|
|
||||||
|
if (isLiaison) {
|
||||||
|
// 联络人:只显示当前阶段材料
|
||||||
|
// 1. 获取材料定义(状态30时需要显示终验材料)
|
||||||
|
const materialType = acceptance.value.status === '30' ? 'FINAL' : acceptance.value.acceptanceType
|
||||||
|
const defs = await AcceptanceMaterialDefApi.getListByType(materialType)
|
||||||
|
// 3. 合并数据
|
||||||
|
materials.value = mergeMaterials(defs)
|
||||||
|
} else {
|
||||||
|
// 其他角色:显示预验收和终验两个阶段的材料
|
||||||
|
// 1. 获取预验收和终验材料定义
|
||||||
|
const preDefs = await AcceptanceMaterialDefApi.getListByType('PRE')
|
||||||
|
const finalDefs = await AcceptanceMaterialDefApi.getListByType('FINAL')
|
||||||
|
// 3. 分别合并数据
|
||||||
|
preMaterials.value = mergeMaterials(preDefs)
|
||||||
|
finalMaterials.value = mergeMaterials(finalDefs)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取审批意见列表
|
// 获取审批意见列表
|
||||||
// 获取审批意见列表
|
// 获取审批意见列表
|
||||||
|
|
@ -558,9 +954,16 @@ const handleSubmitRectify = async () => {
|
||||||
/** 提交终验申请 */
|
/** 提交终验申请 */
|
||||||
const handleSubmitFinal = async () => {
|
const handleSubmitFinal = async () => {
|
||||||
try {
|
try {
|
||||||
await message.confirm('确定提交终验申请吗?')
|
// 校验终验必填材料(前端预校验,后端也会校验)
|
||||||
|
const missing = materials.value.filter((m: any) => m.isRequired && !m.fileUrl)
|
||||||
|
if (missing.length > 0) {
|
||||||
|
message.warning(`请先上传终验必填材料:${missing.map((m: any) => m.materialName).join('、')}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.confirm('确定提交终验申请吗?提交后将进入终验审核流程。')
|
||||||
await AcceptanceApi.submitFinalAcceptance({ acceptanceId: acceptance.value.id })
|
await AcceptanceApi.submitFinalAcceptance({ acceptanceId: acceptance.value.id })
|
||||||
message.success('提交成功')
|
message.success('终验申请提交成功')
|
||||||
await getDetail()
|
await getDetail()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -616,14 +1019,36 @@ onMounted(() => {
|
||||||
|
|
||||||
/* 覆盖 el-steps 进行中状态的颜色为绿色 */
|
/* 覆盖 el-steps 进行中状态的颜色为绿色 */
|
||||||
:deep(.el-step__head.is-process) {
|
:deep(.el-step__head.is-process) {
|
||||||
color: #67c23a;
|
color: #3b82f6;
|
||||||
border-color: #67c23a;
|
border-color: #3b82f6;
|
||||||
}
|
}
|
||||||
:deep(.el-step__title.is-process) {
|
:deep(.el-step__title.is-process) {
|
||||||
color: #67c23a;
|
color: #3b82f6;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
:deep(.el-step__description.is-process) {
|
:deep(.el-step__description.is-process) {
|
||||||
color: #67c23a;
|
color: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Tabs Styling */
|
||||||
|
:deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
height: 50px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item.is-active) {
|
||||||
|
color: #2563eb;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 2px solid #2563eb;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__nav-wrap::after) {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="p-4">
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -6,7 +7,7 @@
|
||||||
:model="queryParams"
|
:model="queryParams"
|
||||||
ref="queryFormRef"
|
ref="queryFormRef"
|
||||||
:inline="true"
|
:inline="true"
|
||||||
label-width="80px"
|
label-width="68px"
|
||||||
>
|
>
|
||||||
<el-form-item label="项目ID" prop="projectId">
|
<el-form-item label="项目ID" prop="projectId">
|
||||||
<el-input
|
<el-input
|
||||||
|
|
@ -14,7 +15,7 @@
|
||||||
placeholder="请输入项目ID"
|
placeholder="请输入项目ID"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
class="!w-180px"
|
class="!w-200px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="验收类型" prop="acceptanceType">
|
<el-form-item label="验收类型" prop="acceptanceType">
|
||||||
|
|
@ -22,7 +23,7 @@
|
||||||
v-model="queryParams.acceptanceType"
|
v-model="queryParams.acceptanceType"
|
||||||
placeholder="请选择验收类型"
|
placeholder="请选择验收类型"
|
||||||
clearable
|
clearable
|
||||||
class="!w-180px"
|
class="!w-200px"
|
||||||
>
|
>
|
||||||
<el-option label="预验收" value="PRE" />
|
<el-option label="预验收" value="PRE" />
|
||||||
<el-option label="终验" value="FINAL" />
|
<el-option label="终验" value="FINAL" />
|
||||||
|
|
@ -33,14 +34,14 @@
|
||||||
v-model="queryParams.status"
|
v-model="queryParams.status"
|
||||||
placeholder="请选择验收状态"
|
placeholder="请选择验收状态"
|
||||||
clearable
|
clearable
|
||||||
class="!w-180px"
|
class="!w-200px"
|
||||||
>
|
>
|
||||||
<el-option label="待审核" value="10" />
|
<el-option
|
||||||
<el-option label="审核中" value="20" />
|
v-for="item in statusList"
|
||||||
<el-option label="待会议" value="30" />
|
:key="item.code"
|
||||||
<el-option label="待整改" value="40" />
|
:label="item.name"
|
||||||
<el-option label="已通过" value="80" />
|
:value="item.code"
|
||||||
<el-option label="已驳回" value="90" />
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="轮次" prop="round">
|
<el-form-item label="轮次" prop="round">
|
||||||
|
|
@ -48,22 +49,17 @@
|
||||||
v-model="queryParams.round"
|
v-model="queryParams.round"
|
||||||
:min="1"
|
:min="1"
|
||||||
placeholder="轮次"
|
placeholder="轮次"
|
||||||
|
controls-position="right"
|
||||||
class="!w-120px"
|
class="!w-120px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="创建时间" prop="createTime">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="queryParams.createTime"
|
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
type="datetimerange"
|
|
||||||
start-placeholder="开始日期"
|
|
||||||
end-placeholder="结束日期"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
<el-button type="primary" plain @click="handleQuery">
|
||||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
<Icon icon="ep:search" class="mr-1" />搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">
|
||||||
|
<Icon icon="ep:refresh" class="mr-1" />重置
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
plain
|
plain
|
||||||
|
|
@ -71,84 +67,100 @@
|
||||||
:loading="exportLoading"
|
:loading="exportLoading"
|
||||||
v-hasPermi="['project:acceptance:export']"
|
v-hasPermi="['project:acceptance:export']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:download" />导出
|
<Icon icon="ep:download" class="mr-1" />导出
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap class="mt-4">
|
||||||
<el-table v-loading="loading" :data="list">
|
<el-table
|
||||||
<el-table-column label="验收ID" align="center" prop="id" width="80" />
|
v-loading="loading"
|
||||||
<el-table-column label="项目ID" align="center" prop="projectId" width="80" />
|
:data="list"
|
||||||
<el-table-column label="验收类型" align="center" prop="acceptanceType" width="100">
|
:header-cell-style="{ background: '#f8fbfc', color: '#606266', height: '50px' }"
|
||||||
|
:row-style="{ height: '60px' }"
|
||||||
|
>
|
||||||
|
<el-table-column label="ID" align="center" prop="id" width="80" class-name="font-mono text-gray-500" />
|
||||||
|
<el-table-column label="轮次" align="center" prop="round" width="80">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.acceptanceType === 'PRE'" type="info">预验收</el-tag>
|
<span class="font-bold text-gray-600 bg-gray-100 px-2 py-1 rounded">R{{ scope.row.round }}</span>
|
||||||
<el-tag v-else-if="scope.row.acceptanceType === 'FINAL'" type="primary">终验</el-tag>
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="项目ID" align="center" prop="projectId" width="100" />
|
||||||
|
|
||||||
|
<el-table-column label="验收类型" align="center" prop="acceptanceType" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.acceptanceType === 'PRE'" type="info" effect="plain" class="!border-gray-300 !text-gray-600">
|
||||||
|
<span class="dot bg-gray-400 mr-1"></span>预验收
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.acceptanceType === 'FINAL'" type="primary" effect="plain" class="!border-blue-200 !text-blue-600">
|
||||||
|
<span class="dot bg-blue-500 mr-1"></span>终验
|
||||||
|
</el-tag>
|
||||||
<el-tag v-else>{{ scope.row.acceptanceType }}</el-tag>
|
<el-tag v-else>{{ scope.row.acceptanceType }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
|
||||||
|
<el-table-column label="状态" align="center" prop="status" width="140">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag :type="getStatusType(scope.row.status)">{{ getStatusLabel(scope.row.status) }}</el-tag>
|
<div class="flex items-center justify-center">
|
||||||
|
<el-tag
|
||||||
|
:type="getStatusType(scope.row.status)"
|
||||||
|
effect="light"
|
||||||
|
round
|
||||||
|
class="!border-0 !px-3 font-medium"
|
||||||
|
>
|
||||||
|
{{ getStatusLabel(scope.row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="轮次" align="center" prop="round" width="80" />
|
|
||||||
<el-table-column label="流程实例ID" align="center" prop="processInstanceId" :show-overflow-tooltip="true" />
|
<el-table-column label="时间信息" min-width="250">
|
||||||
<el-table-column
|
|
||||||
label="启动时间"
|
|
||||||
align="center"
|
|
||||||
prop="startTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
label="完成时间"
|
|
||||||
align="center"
|
|
||||||
prop="endTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
label="创建时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180"
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<div class="flex flex-col gap-1 text-xs text-gray-500">
|
||||||
link
|
<div class="flex items-center">
|
||||||
type="primary"
|
<span class="w-16 text-right mr-2">启动时间:</span>
|
||||||
@click="handleView(scope.row)"
|
<span class="text-gray-700">{{ scope.row.startTime ? dateFormatter(scope.row, null, scope.row.startTime) : '-' }}</span>
|
||||||
>
|
</div>
|
||||||
查看
|
<div v-if="scope.row.endTime" class="flex items-center">
|
||||||
|
<span class="w-16 text-right mr-2">完成时间:</span>
|
||||||
|
<span class="text-gray-700">{{ scope.row.endTime ? dateFormatter(scope.row, null, scope.row.endTime) : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" align="center" width="260" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="handleView(scope.row)">
|
||||||
|
<Icon icon="ep:view" class="mr-1" /> 查看
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="canAudit(scope.row)"
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openAuditForm(scope.row)"
|
@click="openAuditForm(scope.row)"
|
||||||
v-if="canAudit(scope.row)"
|
v-hasRole="['acceptance-admin']"
|
||||||
>
|
>
|
||||||
审核
|
<Icon icon="ep:check" class="mr-1" /> 审核
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="handleForceArchive(scope.row)"
|
@click="handleForceArchive(scope.row)"
|
||||||
v-hasPermi="['project:acceptance:force-archive']"
|
v-hasPermi="['project:acceptance:force-archive']"
|
||||||
|
v-hasRole="['acceptance-admin']"
|
||||||
>
|
>
|
||||||
强制归档
|
<Icon icon="ep:folder-checked" class="mr-1" /> 归档
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleCancel(scope.row)"
|
@click="handleCancel(scope.row)"
|
||||||
v-hasPermi="['project:acceptance:cancel']"
|
v-hasRole="['acceptance-admin']"
|
||||||
>
|
>
|
||||||
取消
|
<Icon icon="ep:circle-close" class="mr-1" /> 取消
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
@ -164,8 +176,18 @@
|
||||||
|
|
||||||
<!-- 审核表单弹窗 -->
|
<!-- 审核表单弹窗 -->
|
||||||
<AcceptanceAuditForm ref="auditFormRef" @success="getList" />
|
<AcceptanceAuditForm ref="auditFormRef" @success="getList" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
|
|
@ -193,22 +215,48 @@ const queryParams = reactive({
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
// 状态映射
|
// 状态列表(从后端获取)
|
||||||
const statusMap: Record<string, { label: string; type: string }> = {
|
const statusList = ref<{ code: string; name: string }[]>([])
|
||||||
'10': { label: '待审核', type: 'info' },
|
const statusMap = ref<Record<string, string>>({})
|
||||||
'20': { label: '审核中', type: 'warning' },
|
|
||||||
'30': { label: '待会议', type: '' },
|
/** 获取状态列表 */
|
||||||
'40': { label: '待整改', type: 'danger' },
|
const getStatusList = async () => {
|
||||||
'80': { label: '已通过', type: 'success' },
|
try {
|
||||||
'90': { label: '已驳回', type: 'danger' }
|
const data = await AcceptanceApi.getStatusList()
|
||||||
|
statusList.value = data
|
||||||
|
// 构建 statusMap
|
||||||
|
statusMap.value = {}
|
||||||
|
data.forEach((item: { code: string; name: string }) => {
|
||||||
|
statusMap.value[item.code] = item.name
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取状态列表失败', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusLabel = (status: string) => {
|
const getStatusLabel = (status: string) => {
|
||||||
return statusMap[status]?.label || status
|
return statusMap.value[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态类型映射(用于tag颜色)
|
||||||
|
const statusTypeMap: Record<string, string> = {
|
||||||
|
'00': 'info',
|
||||||
|
'05': 'warning',
|
||||||
|
'10': 'primary',
|
||||||
|
'11': 'danger',
|
||||||
|
'20': 'primary',
|
||||||
|
'30': 'info',
|
||||||
|
'40': 'primary',
|
||||||
|
'50': 'info',
|
||||||
|
'60': 'danger',
|
||||||
|
'61': 'warning',
|
||||||
|
'62': 'warning',
|
||||||
|
'98': 'info',
|
||||||
|
'99': 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusType = (status: string) => {
|
const getStatusType = (status: string) => {
|
||||||
return statusMap[status]?.type || 'info'
|
return statusTypeMap[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否可以审核 */
|
/** 判断是否可以审核 */
|
||||||
|
|
@ -297,7 +345,8 @@ const handleExport = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
await getStatusList()
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -81,22 +81,27 @@ const getList = async () => {
|
||||||
const auditFormRef = ref()
|
const auditFormRef = ref()
|
||||||
const handleProcess = (row: AcceptanceApi.AcceptanceTodoVO) => {
|
const handleProcess = (row: AcceptanceApi.AcceptanceTodoVO) => {
|
||||||
// 根据任务类型打开不同的处理界面
|
// 根据任务类型打开不同的处理界面
|
||||||
|
// taskType 来自 BPMN 的 taskDefinitionKey
|
||||||
const taskType = row.taskType
|
const taskType = row.taskType
|
||||||
if (['LIAISON_REVIEW', 'LEADER_REVIEW', 'ADMIN_REVIEW', 'EXPERT_CHECK'].includes(taskType)) {
|
|
||||||
// 打开审核表单
|
// 审核类任务:打开审核弹窗
|
||||||
|
if (['task_pre_liaison_review', 'task_pre_leader_review', 'task_expert_check', 'task_rectify_review', 'task_meeting_review'].includes(taskType)) {
|
||||||
auditFormRef.value.open({
|
auditFormRef.value.open({
|
||||||
id: row.acceptanceId,
|
id: row.acceptanceId,
|
||||||
acceptanceType: row.acceptanceType,
|
acceptanceType: row.acceptanceType,
|
||||||
status: row.status
|
status: row.status
|
||||||
}, getAuditType(taskType))
|
}, getAuditType(taskType))
|
||||||
} else if (taskType === 'RECTIFY_SUBMIT' || taskType === 'task_pre_rectify' || taskType === 'task_final_rectify') {
|
}
|
||||||
// 整改任务,跳转到详情页的材料tab让用户完善材料
|
// 管理员终验初审:跳转到详情页
|
||||||
|
else if (taskType === 'task_final_admin_review') {
|
||||||
|
router.push({ path: '/project/acceptance/detail/' + row.acceptanceId, query: { tab: 'info' } })
|
||||||
|
}
|
||||||
|
// 材料提交类任务:跳转到详情页的材料tab
|
||||||
|
else if (['task_liaison_submit', 'task_pre_rectify', 'task_final_rectify', 'task_final_apply'].includes(taskType)) {
|
||||||
router.push({ path: '/project/acceptance/detail/' + row.acceptanceId, query: { tab: 'materials' } })
|
router.push({ path: '/project/acceptance/detail/' + row.acceptanceId, query: { tab: 'materials' } })
|
||||||
} else if (taskType === 'LIAISON_SUBMIT' || taskType === 'task_liaison_submit') {
|
}
|
||||||
// 联络人提交材料,跳转到详情页的材料tab
|
|
||||||
router.push({ path: '/project/acceptance/detail/' + row.acceptanceId, query: { tab: 'materials' } })
|
|
||||||
} else {
|
|
||||||
// 默认查看详情
|
// 默认查看详情
|
||||||
|
else {
|
||||||
handleView(row)
|
handleView(row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,12 +109,16 @@ const handleProcess = (row: AcceptanceApi.AcceptanceTodoVO) => {
|
||||||
/** 获取审核类型 */
|
/** 获取审核类型 */
|
||||||
const getAuditType = (taskType: string) => {
|
const getAuditType = (taskType: string) => {
|
||||||
switch (taskType) {
|
switch (taskType) {
|
||||||
case 'LIAISON_REVIEW':
|
case 'task_pre_liaison_review': // 对口人预验收初审
|
||||||
case 'LEADER_REVIEW':
|
case 'task_pre_leader_review': // 预验收组长审核
|
||||||
return 'pre-audit'
|
return 'pre-audit'
|
||||||
case 'ADMIN_REVIEW':
|
case 'task_final_admin_review': // 管理员终验初审
|
||||||
return 'final-admin'
|
return 'final-admin'
|
||||||
case 'EXPERT_CHECK':
|
case 'task_meeting_review': // 会议评审
|
||||||
|
return 'meeting-review'
|
||||||
|
case 'task_rectify_review': // 整改审核
|
||||||
|
return 'rectify-review'
|
||||||
|
case 'task_expert_check': // 专家复核
|
||||||
return 'expert-check'
|
return 'expert-check'
|
||||||
default:
|
default:
|
||||||
return 'pre-audit'
|
return 'pre-audit'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue