1055 lines
49 KiB
Vue
1055 lines
49 KiB
Vue
<template>
|
||
<div class="p-4 bg-gray-50 min-h-screen">
|
||
<div v-loading="loading">
|
||
<!-- 头部 Hero Section -->
|
||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||
<div>
|
||
<div class="flex items-center gap-3 mb-2">
|
||
<h2 class="text-2xl font-bold text-gray-800 tracking-tight">
|
||
{{ projectInfo.projectName || '项目验收' }}
|
||
</h2>
|
||
<el-tag
|
||
v-if="acceptance.acceptanceType === 'PRE'"
|
||
type="info"
|
||
effect="plain"
|
||
class="!rounded-full !px-3 font-medium border-gray-300"
|
||
>
|
||
预验收
|
||
</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 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) }}
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 流程步骤 -->
|
||
<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-steps>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主要内容区域 -->
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
|
||
<!-- 左侧:详细信息 (占据 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">
|
||
<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>
|
||
|
||
<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:timer" /> 时间及流程
|
||
</h3>
|
||
<div class="grid grid-cols-[100px_1fr] gap-y-3 text-sm">
|
||
<span class="text-gray-500 text-right">开始时间:</span>
|
||
<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 label="验收材料" name="materials">
|
||
<div class="p-6">
|
||
<!-- 联络人视图:显示当前阶段材料 -->
|
||
<template v-if="isLiaisonUser">
|
||
<div class="mb-6" v-if="canUploadMaterial">
|
||
<el-alert
|
||
type="primary"
|
||
:closable="false"
|
||
show-icon
|
||
class="!bg-blue-50 !border-blue-100 !text-blue-800"
|
||
>
|
||
<template #title>
|
||
<span class="font-bold text-base">待办任务:上传验收材料</span>
|
||
</template>
|
||
<template #default>
|
||
<p class="mt-1 text-sm text-blue-600">请完成下方所有必填材料的上传,确认无误后点击底部的【提交材料】按钮进入审核流程。</p>
|
||
</template>
|
||
</el-alert>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<template v-for="(item, index) in materials" :key="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>
|
||
|
||
<!-- 上传/重新上传 -->
|
||
<div v-if="canUploadMaterial">
|
||
<el-upload
|
||
:action="uploadUrl"
|
||
:headers="uploadHeaders"
|
||
:show-file-list="false"
|
||
:on-success="(res, file) => handleUploadSuccess(res, file, item)"
|
||
>
|
||
<el-tooltip :content="item.fileUrl ? '重新上传' : '上传文件'" placement="top">
|
||
<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">
|
||
<Icon :icon="item.fileUrl ? 'ep:refresh' : 'ep:upload'" size="20" />
|
||
</button>
|
||
</el-tooltip>
|
||
</el-upload>
|
||
</div>
|
||
</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>
|
||
</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>
|
||
</template>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<!-- 审批记录 -->
|
||
<el-tab-pane label="审批记录" name="opinions">
|
||
<div class="p-6">
|
||
<el-timeline class="pl-2">
|
||
<el-timeline-item
|
||
v-for="(opinion, index) in opinions"
|
||
:key="index"
|
||
:type="getOpinionType(opinion.result)"
|
||
:color="getOpinionColor(opinion.result)"
|
||
:timestamp="formatDate(opinion.createTime)"
|
||
placement="top"
|
||
size="large"
|
||
>
|
||
<div class="bg-white border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow relative">
|
||
<!-- Arrow -->
|
||
<div class="absolute w-3 h-3 bg-white border-l border-b border-gray-200 transform rotate-45 -left-[7px] top-4"></div>
|
||
|
||
<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) }}
|
||
</el-tag>
|
||
</div>
|
||
|
||
<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
|
||
:src="opinion.signatureUrl"
|
||
class="h-10 w-auto opacity-80 hover:opacity-100"
|
||
fit="contain"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-timeline-item>
|
||
<el-empty v-if="opinions.length === 0" description="暂无审批记录" :image-size="100" />
|
||
</el-timeline>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:操作区 (占据 1/3 宽度) -->
|
||
<div class="lg:col-span-1 space-y-6">
|
||
<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
|
||
v-if="canSubmitMaterial"
|
||
type="primary"
|
||
size="large"
|
||
class="!w-full !justify-start !text-left !py-5"
|
||
@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
|
||
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
|
||
v-if="canSubmitRectify"
|
||
type="warning"
|
||
size="large"
|
||
class="!w-full !justify-start !text-left !py-5"
|
||
@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
|
||
v-if="canSubmitFinal"
|
||
type="primary"
|
||
plain
|
||
size="large"
|
||
class="!w-full !justify-start !text-left !py-5"
|
||
@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-divider class="!my-4" />
|
||
|
||
<el-button @click="goBack" class="!w-full" plain>
|
||
<Icon icon="ep:back" class="mr-1" /> 返回列表
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 审核对话框 -->
|
||
<el-dialog
|
||
v-model="auditDialogVisible"
|
||
title="验收审核"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
class="rounded-xl overflow-hidden"
|
||
>
|
||
<div class="p-2">
|
||
<el-form
|
||
ref="auditFormRef"
|
||
:model="auditForm"
|
||
:rules="auditFormRules"
|
||
label-width="100px"
|
||
label-position="top"
|
||
>
|
||
<el-form-item label="审核结果" prop="result">
|
||
<div class="flex w-full gap-4">
|
||
<div
|
||
class="flex-1 border rounded-lg p-4 cursor-pointer text-center transition-all hover:shadow-md"
|
||
:class="auditForm.result === 'PASS' ? 'border-green-500 bg-green-50 text-green-700' : 'border-gray-200 text-gray-500 hover:border-green-200'"
|
||
@click="auditForm.result = 'PASS'"
|
||
>
|
||
<Icon icon="ep:circle-check-filled" class="text-3xl mb-2" :class="auditForm.result === 'PASS' ? 'text-green-500' : 'text-gray-300'" />
|
||
<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 label="审核意见" prop="opinion">
|
||
<el-input
|
||
v-model="auditForm.opinion"
|
||
type="textarea"
|
||
:rows="4"
|
||
:placeholder="auditForm.result === 'REJECT' ? '请务必填写驳回理由...' : '请填写审核意见(可选)...'"
|
||
class="!bg-gray-50"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<div class="flex justify-end gap-2 pt-2">
|
||
<el-button @click="auditDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="handleAudit" class="px-6">确认提交</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 历史版本对话框 -->
|
||
<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>
|
||
|
||
<script lang="ts" setup>
|
||
import { formatDate } from '@/utils/formatTime'
|
||
import * as AcceptanceApi from '@/api/project/acceptance'
|
||
import * as ProjectApi from '@/api/project/project'
|
||
import * as AcceptanceMaterialApi from '@/api/project/acceptanceMaterial'
|
||
import * as AcceptanceMaterialDefApi from '@/api/project/acceptanceMaterialDef'
|
||
import * as AcceptanceOpinionApi from '@/api/project/acceptanceOpinion'
|
||
import { getAccessToken } from '@/utils/auth'
|
||
import { useUserStore } from '@/store/modules/user'
|
||
|
||
defineOptions({ name: 'AcceptanceDetail' })
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const message = useMessage()
|
||
|
||
const loading = ref(true)
|
||
// 从路由参数读取默认tab,默认显示基本信息tab
|
||
const activeTab = ref((route.query.tab as string) || 'info')
|
||
|
||
// 验收信息
|
||
const acceptance = ref<AcceptanceApi.AcceptanceVO>({} as AcceptanceApi.AcceptanceVO)
|
||
// 项目信息
|
||
const projectInfo = ref<any>({})
|
||
// 用户名称
|
||
const liaisonUserName = ref('')
|
||
const serviceUserName = ref('')
|
||
// 验收材料(联络人使用)
|
||
const materials = ref<any[]>([])
|
||
// 预验收材料(其他角色使用)
|
||
const preMaterials = ref<any[]>([])
|
||
// 终验材料(其他角色使用)
|
||
const finalMaterials = 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 uploadHeaders = { Authorization: 'Bearer ' + getAccessToken() }
|
||
|
||
// 状态映射
|
||
const statusMap: Record<string, { label: string; type: string }> = {
|
||
'00': { label: '草稿', type: 'info' },
|
||
'05': { label: '待提交材料', type: 'warning' },
|
||
'10': { label: '对口人初审中', type: 'primary' },
|
||
'11': { label: '待整改', type: 'danger' },
|
||
'20': { label: '组长审核中', type: 'primary' },
|
||
'30': { label: '待终验申请', type: 'info' },
|
||
'40': { label: '管理员初审中', type: 'primary' }, // 对应 FINAL_ADMIN_REVIEW
|
||
'50': { label: '待会议评审', type: 'info' }, // WAIT_MEETING
|
||
'60': { label: '待整改', type: 'danger' }, // FINAL_RECTIFY
|
||
'61': { label: '整改审核中', type: 'warning' }, // FINAL_RECTIFY_REVIEW
|
||
'62': { label: '待专家复核', type: 'warning' }, // WAIT_EXPERT_CHECK
|
||
'98': { label: '已取消', type: 'info' },
|
||
'99': { label: '已归档', type: 'success' }
|
||
}
|
||
|
||
const getStatusLabel = (status: string) => statusMap[status]?.label || status
|
||
const getStatusType = (status: string) => statusMap[status]?.type || 'info'
|
||
|
||
// 审核结果映射
|
||
const getOpinionLabel = (result: string) => {
|
||
if (result === 'PASS') return '通过'
|
||
if (result === 'REJECT') return '驳回'
|
||
return result || '未知'
|
||
}
|
||
const getOpinionType = (result: string) => {
|
||
if (result === 'PASS') return 'success'
|
||
if (result === 'REJECT') return 'danger'
|
||
return 'info'
|
||
}
|
||
|
||
const getOpinionColor = (result: string) => {
|
||
if (result === 'PASS') return '#67C23A'
|
||
if (result === 'REJECT') return '#F56C6C'
|
||
return '#909399'
|
||
}
|
||
|
||
// 权限判断
|
||
// 权限判断
|
||
const canUploadMaterial = computed(() => {
|
||
const currentUserId = useUserStore().getUser.id
|
||
// 必须是联络人,且状态符合:待提交材料(05)、预验收整改(11)、待终验申请(30)、终验整改(60)
|
||
return projectInfo.value.liaisonUserId === currentUserId && ['05', '11', '30', '60'].includes(acceptance.value.status)
|
||
})
|
||
|
||
const canSubmitMaterial = computed(() => {
|
||
const currentUserId = useUserStore().getUser.id
|
||
return projectInfo.value.liaisonUserId === currentUserId && acceptance.value.status === '05'
|
||
})
|
||
|
||
const canSubmitRectify = computed(() => {
|
||
const currentUserId = useUserStore().getUser.id
|
||
return projectInfo.value.liaisonUserId === currentUserId && ['11', '60'].includes(acceptance.value.status)
|
||
})
|
||
|
||
const canSubmitFinal = computed(() => {
|
||
const currentUserId = useUserStore().getUser.id
|
||
return projectInfo.value.liaisonUserId === currentUserId && acceptance.value.status === '30' // WAIT_FINAL_APPLY
|
||
})
|
||
|
||
const showActions = computed(() => {
|
||
return canSubmitMaterial.value || canSubmitRectify.value || canSubmitFinal.value || canAudit.value
|
||
})
|
||
|
||
// 是否可以审核 (对口人初审: 10, 专家组长审核: 20, 管理员终验初审: 40)
|
||
const canAudit = computed(() => {
|
||
const currentUserId = useUserStore().getUser.id
|
||
const status = acceptance.value.status
|
||
// 状态 10: 对口人初审,只有对口人可审核
|
||
if (status === '10') {
|
||
return projectInfo.value.serviceUserId === currentUserId
|
||
}
|
||
// 状态 20: 专家组长审核,显示审核按钮(后端会校验权限)
|
||
if (status === '20') {
|
||
return true
|
||
}
|
||
// 状态 40: 管理员终验初审,显示审核按钮(后端会校验权限)
|
||
if (status === '40') {
|
||
return true
|
||
}
|
||
return false
|
||
})
|
||
|
||
// 审核对话框
|
||
const auditDialogVisible = ref(false)
|
||
const auditForm = ref({
|
||
result: '',
|
||
opinion: ''
|
||
})
|
||
const auditFormRef = ref()
|
||
const auditFormRules = {
|
||
result: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
|
||
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 = () => {
|
||
auditForm.value = { result: '', opinion: '' }
|
||
auditDialogVisible.value = true
|
||
}
|
||
|
||
/** 提交审核 */
|
||
const handleAudit = async () => {
|
||
if (!auditFormRef.value) return
|
||
const valid = await auditFormRef.value.validate().catch(() => false)
|
||
if (!valid) return
|
||
|
||
try {
|
||
const auditData = {
|
||
acceptanceId: acceptance.value.id,
|
||
result: auditForm.value.result,
|
||
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('审核提交成功')
|
||
auditDialogVisible.value = false
|
||
await getDetail()
|
||
} catch (error) {
|
||
// Error handled by request interceptor
|
||
}
|
||
}
|
||
|
||
/** 获取验收详情 */
|
||
const getDetail = async () => {
|
||
loading.value = true
|
||
try {
|
||
const id = Number(route.params.id)
|
||
acceptance.value = await AcceptanceApi.getAcceptance(id)
|
||
|
||
// 获取项目信息(后端已填充用户名称)
|
||
if (acceptance.value.projectId) {
|
||
projectInfo.value = await ProjectApi.getProject(acceptance.value.projectId)
|
||
// 直接使用后端返回的用户名称
|
||
liaisonUserName.value = projectInfo.value.liaisonUserName || '-'
|
||
serviceUserName.value = projectInfo.value.serviceUserName || '-'
|
||
}
|
||
|
||
// 获取验收材料
|
||
// 2. 获取已上传材料
|
||
const uploads = await AcceptanceMaterialApi.getListByAcceptanceId(id) as unknown as any[]
|
||
|
||
// 材料合并辅助函数
|
||
const mergeMaterials = (defs: any[]) => {
|
||
return defs.map((def: any) => {
|
||
// 查找该材料代码对应的所有上传记录
|
||
const allUploads = uploads.filter((u: any) => u.materialCode === def.materialCode)
|
||
// 按ID倒序排序(最新的在前)
|
||
const sortedUploads = allUploads.sort((a: any, b: any) => b.id - a.id)
|
||
// 取最新的一条
|
||
const upload = sortedUploads.length > 0 ? sortedUploads[0] : undefined
|
||
|
||
return {
|
||
...def,
|
||
materialType: def.requiredFlag ? '必填' : '可选',
|
||
isRequired: def.requiredFlag, // 方便模板使用
|
||
fileUrl: upload?.fileUrl,
|
||
fileName: upload?.fileName,
|
||
fileSize: upload?.fileSize,
|
||
uploadId: upload?.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)
|
||
}
|
||
|
||
// 获取审批意见列表
|
||
// 获取审批意见列表
|
||
const res = await AcceptanceOpinionApi.getListByAcceptanceId(id)
|
||
opinions.value = res.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime())
|
||
} catch (error) {
|
||
console.error(error)
|
||
message.error('获取验收详情失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/** 返回 */
|
||
const goBack = () => {
|
||
router.back()
|
||
}
|
||
|
||
/** 获取文件完整URL */
|
||
const getFileUrl = (url: string) => {
|
||
if (!url) return ''
|
||
if (url.startsWith('http') || url.startsWith('https')) {
|
||
return url
|
||
}
|
||
// 处理相对路径,拼接后端地址
|
||
return import.meta.env.VITE_BASE_URL + url
|
||
}
|
||
|
||
/** 预览文件 */
|
||
const handlePreview = (row: any) => {
|
||
const url = getFileUrl(row.fileUrl)
|
||
if (url) {
|
||
window.open(url, '_blank')
|
||
}
|
||
}
|
||
|
||
/** 下载文件 */
|
||
const handleDownload = (row: any) => {
|
||
const url = getFileUrl(row.fileUrl)
|
||
if (url) {
|
||
const link = document.createElement('a')
|
||
link.href = url
|
||
link.download = row.fileName || row.materialName
|
||
link.click()
|
||
}
|
||
}
|
||
|
||
/** 上传成功 */
|
||
const handleUploadSuccess = async (response: any, file: any, row: any) => {
|
||
if (response.code === 0) {
|
||
try {
|
||
// 调用后端保存材料信息
|
||
await AcceptanceMaterialApi.createAcceptanceMaterial({
|
||
acceptanceId: row.acceptanceId,
|
||
materialCode: row.materialCode,
|
||
fileName: file.name, // 使用原始文件名
|
||
fileUrl: response.data,
|
||
fileSize: file.size || 0,
|
||
fileType: file.name.split('.').pop(),
|
||
version: 1,
|
||
remark: ''
|
||
} as any)
|
||
|
||
message.success('上传成功')
|
||
// 刷新列表
|
||
getDetail()
|
||
} catch (error) {
|
||
message.error('保存材料记录失败')
|
||
}
|
||
} else {
|
||
message.error('上传失败: ' + response.msg)
|
||
}
|
||
}
|
||
|
||
/** 提交材料 */
|
||
const handleSubmitMaterial = async () => {
|
||
try {
|
||
// 校验必填项
|
||
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.submitMaterials(acceptance.value.id)
|
||
message.success('提交成功')
|
||
await getDetail()
|
||
} catch {}
|
||
}
|
||
|
||
/** 提交整改 */
|
||
const handleSubmitRectify = async () => {
|
||
try {
|
||
await message.confirm('确定提交整改吗?')
|
||
if (acceptance.value.acceptanceType === 'PRE') {
|
||
await AcceptanceApi.submitPreRectify(acceptance.value.id)
|
||
} else {
|
||
await AcceptanceApi.submitFinalRectify(acceptance.value.id)
|
||
}
|
||
message.success('提交成功')
|
||
await getDetail()
|
||
} catch {}
|
||
}
|
||
|
||
/** 提交终验申请 */
|
||
const handleSubmitFinal = async () => {
|
||
try {
|
||
// 校验终验必填材料(前端预校验,后端也会校验)
|
||
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 })
|
||
message.success('终验申请提交成功')
|
||
await getDetail()
|
||
} catch {}
|
||
}
|
||
|
||
/** 流程步骤 */
|
||
const activeStep = computed(() => {
|
||
const status = acceptance.value.status
|
||
if (['00', '05'].includes(status)) return 0
|
||
if (['10', '11', '20'].includes(status)) return 1
|
||
if (['30'].includes(status)) return 2
|
||
if (['40', '50', '60', '61', '62'].includes(status)) return 3
|
||
if (['99'].includes(status)) return 4
|
||
return 0
|
||
})
|
||
|
||
/** 获取文件图标 */
|
||
const getFileIcon = (fileName?: string) => {
|
||
if (!fileName) return 'ep:document'
|
||
const ext = fileName.split('.').pop()?.toLowerCase()
|
||
if (['doc', 'docx'].includes(ext)) return 'ep:document'
|
||
if (['xls', 'xlsx'].includes(ext)) return 'ep:data-analysis'
|
||
if (['pdf'].includes(ext)) return 'ep:document-copy'
|
||
if (['jpg', 'jpeg', 'png', 'gif'].includes(ext)) return 'ep:picture'
|
||
if (['zip', 'rar'].includes(ext)) return 'ep:folder'
|
||
return 'ep:document'
|
||
}
|
||
|
||
/** 获取文件图标背景色 */
|
||
const getFileIconColor = (fileName?: string) => {
|
||
if (!fileName) return 'bg-gray-400'
|
||
const ext = fileName.split('.').pop()?.toLowerCase()
|
||
if (['doc', 'docx'].includes(ext)) return 'bg-blue-500'
|
||
if (['xls', 'xlsx'].includes(ext)) return 'bg-green-500'
|
||
if (['pdf'].includes(ext)) return 'bg-red-500'
|
||
if (['jpg', 'jpeg', 'png', 'gif'].includes(ext)) return 'bg-purple-500'
|
||
if (['zip', 'rar'].includes(ext)) return 'bg-orange-500'
|
||
return 'bg-gray-400'
|
||
}
|
||
|
||
/** 初始化 */
|
||
onMounted(() => {
|
||
if (route.query.tab) {
|
||
activeTab.value = route.query.tab as string
|
||
}
|
||
getDetail()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.inline-block {
|
||
display: inline-block;
|
||
}
|
||
|
||
/* 覆盖 el-steps 进行中状态的颜色为绿色 */
|
||
:deep(.el-step__head.is-process) {
|
||
color: #3b82f6;
|
||
border-color: #3b82f6;
|
||
}
|
||
:deep(.el-step__title.is-process) {
|
||
color: #3b82f6;
|
||
font-weight: bold;
|
||
}
|
||
:deep(.el-step__description.is-process) {
|
||
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>
|