增加验收页面

pull/874/head
wh 2026-02-05 22:51:30 +08:00
parent 52e22970bc
commit c66abdf33e
27 changed files with 11593 additions and 6004 deletions

View File

@ -4,7 +4,7 @@ NODE_ENV=production
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
VITE_BASE_URL='http://127.0.0.1:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
import request from '@/config/axios'
// 验收实例 VO
export interface AcceptanceVO {
id: number
projectId: number
acceptanceType: string
processInstanceId: string
status: string
round: number
startTime: Date
endTime: Date
createTime: Date
}
// 验收待办 VO
export interface AcceptanceTodoVO {
acceptanceId: number
projectId: number
projectName: string
projectCode: string
acceptanceType: string
status: string
statusDesc: string
round: number
taskType: string
taskTypeDesc: string
createTime: Date
processInstanceId: string
taskId: string
taskName: string
}
// 提交预验收申请请求 VO
export interface AcceptanceSubmitPreReqVO {
projectId: number
serviceUserId?: number
liaisonUserId?: number
leaderUserId: number
}
// 审核请求 VO
export interface AcceptanceAuditReqVO {
acceptanceId: number
result: string
opinion: string
signatureUrl?: string
}
// 整改审核请求 VO
export interface AcceptanceRectifyAuditReqVO {
acceptanceId: number
result: string
opinion: string
rectifyDeadline?: Date
signatureUrl?: string
}
// 提交终验申请请求 VO
export interface AcceptanceSubmitFinalReqVO {
acceptanceId: number
}
// ==================== 基础 CRUD ====================
// 查询验收实例分页
export const getAcceptancePage = (params: any) => {
return request.get({ url: '/project/acceptance/page', params })
}
// 查询验收实例详情
export const getAcceptance = (id: number) => {
return request.get({ url: '/project/acceptance/get?id=' + id })
}
// 新增验收实例
export const createAcceptance = (data: AcceptanceVO) => {
return request.post({ url: '/project/acceptance/create', data })
}
// 修改验收实例
export const updateAcceptance = (data: AcceptanceVO) => {
return request.put({ url: '/project/acceptance/update', data })
}
// 删除验收实例
export const deleteAcceptance = (id: number) => {
return request.delete({ url: '/project/acceptance/delete?id=' + id })
}
// 批量删除验收实例
export const deleteAcceptanceList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收实例 Excel
export const exportAcceptance = (params: any) => {
return request.download({ url: '/project/acceptance/export-excel', params })
}
// ==================== 业务接口 ====================
// 提交预验收申请
export const submitPreAcceptance = (data: AcceptanceSubmitPreReqVO) => {
return request.post({ url: '/project/acceptance/submit-pre', data })
}
// 预验收审核(对口人/组长)
export const auditPreAcceptance = (data: AcceptanceAuditReqVO) => {
return request.post({ url: '/project/acceptance/audit-pre', data })
}
// 提交验收材料
export const submitMaterials = (acceptanceId: number) => {
return request.post({ url: '/project/acceptance/submit-materials?acceptanceId=' + acceptanceId })
}
// 提交预验收整改
export const submitPreRectify = (acceptanceId: number) => {
return request.post({ url: '/project/acceptance/submit-pre-rectify?acceptanceId=' + acceptanceId })
}
// 提交终验申请
export const submitFinalAcceptance = (data: AcceptanceSubmitFinalReqVO) => {
return request.post({ url: '/project/acceptance/submit-final', data })
}
// 终验管理员初审
export const auditFinalAdmin = (data: AcceptanceAuditReqVO) => {
return request.post({ url: '/project/acceptance/audit-final-admin', data })
}
// 提交终验整改
export const submitFinalRectify = (acceptanceId: number) => {
return request.post({ url: '/project/acceptance/submit-final-rectify?acceptanceId=' + acceptanceId })
}
// 整改审核
export const auditRectify = (data: AcceptanceRectifyAuditReqVO) => {
return request.post({ url: '/project/acceptance/audit-rectify', data })
}
// 专家复核
export const expertCheck = (data: AcceptanceAuditReqVO) => {
return request.post({ url: '/project/acceptance/expert-check', data })
}
// 强制归档
export const forceArchive = (acceptanceId: number, reason: string) => {
return request.post({ url: '/project/acceptance/force-archive', params: { acceptanceId, reason } })
}
// 取消验收
export const cancelAcceptance = (acceptanceId: number, reason: string) => {
return request.post({ url: '/project/acceptance/cancel', params: { acceptanceId, reason } })
}
// 查询待办任务
export const getTodoList = () => {
return request.get({ url: '/project/acceptance/todo' })
}

View File

@ -0,0 +1,57 @@
import request from '@/config/axios'
// 验收材料 VO
export interface AcceptanceMaterialVO {
id: number
acceptanceId: number
materialCode: string
fileName: string
fileUrl: string
fileSize: number
fileType: string
version: number
remark: string
uploaderId: number
uploadTime: Date
createTime: Date
}
// 查询验收材料分页
export const getAcceptanceMaterialPage = (params: any) => {
return request.get({ url: '/project/acceptance-material/page', params })
}
// 根据验收ID获取材料列表
export const getListByAcceptanceId = (acceptanceId: number) => {
return request.get({ url: '/project/acceptance-material/list-by-acceptance-id', params: { acceptanceId } })
}
// 查询验收材料详情
export const getAcceptanceMaterial = (id: number) => {
return request.get({ url: '/project/acceptance-material/get?id=' + id })
}
// 新增验收材料
export const createAcceptanceMaterial = (data: AcceptanceMaterialVO) => {
return request.post({ url: '/project/acceptance-material/create', data })
}
// 修改验收材料
export const updateAcceptanceMaterial = (data: AcceptanceMaterialVO) => {
return request.put({ url: '/project/acceptance-material/update', data })
}
// 删除验收材料
export const deleteAcceptanceMaterial = (id: number) => {
return request.delete({ url: '/project/acceptance-material/delete?id=' + id })
}
// 批量删除验收材料
export const deleteAcceptanceMaterialList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance-material/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收材料 Excel
export const exportAcceptanceMaterial = (params: any) => {
return request.download({ url: '/project/acceptance-material/export-excel', params })
}

View File

@ -0,0 +1,53 @@
import request from '@/config/axios'
// 验收材料定义 VO
export interface AcceptanceMaterialDefVO {
id: number
acceptanceType: string
materialCode: string
materialName: string
requiredFlag: boolean
uploadRole: string
sort: number
createTime: Date
}
// 查询验收材料定义分页
export const getAcceptanceMaterialDefPage = (params: any) => {
return request.get({ url: '/project/acceptance-material-def/page', params })
}
// 根据验收类型获取材料定义列表
export const getListByType = (acceptanceType: string) => {
return request.get({ url: '/project/acceptance-material-def/list-by-type', params: { acceptanceType } })
}
// 查询验收材料定义详情
export const getAcceptanceMaterialDef = (id: number) => {
return request.get({ url: '/project/acceptance-material-def/get?id=' + id })
}
// 新增验收材料定义
export const createAcceptanceMaterialDef = (data: AcceptanceMaterialDefVO) => {
return request.post({ url: '/project/acceptance-material-def/create', data })
}
// 修改验收材料定义
export const updateAcceptanceMaterialDef = (data: AcceptanceMaterialDefVO) => {
return request.put({ url: '/project/acceptance-material-def/update', data })
}
// 删除验收材料定义
export const deleteAcceptanceMaterialDef = (id: number) => {
return request.delete({ url: '/project/acceptance-material-def/delete?id=' + id })
}
// 批量删除验收材料定义
export const deleteAcceptanceMaterialDefList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance-material-def/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收材料定义 Excel
export const exportAcceptanceMaterialDef = (params: any) => {
return request.download({ url: '/project/acceptance-material-def/export-excel', params })
}

View File

@ -0,0 +1,88 @@
import request from '@/config/axios'
// 验收会议 VO
export interface AcceptanceMeetingVO {
id: number
meetingName: string
meetingTime: Date
meetingLocation: string
meetingType: string
meetingResult: string
meetingOpinion: string
minutesFileUrl: string
signFileUrl: string
createTime: Date
}
// 批量创建会议请求 VO
export interface AcceptanceMeetingBatchCreateReqVO {
acceptanceIds: number[]
meetingName: string
meetingTime: Date
meetingLocation?: string
meetingType: string
expertUserIds: number[]
minutesFileUrl?: string
}
// 批量录入会议结果请求 VO
export interface AcceptanceMeetingBatchResultReqVO {
meetingId: number
results: AcceptanceMeetingResultVO[]
}
// 单个会议结果 VO
export interface AcceptanceMeetingResultVO {
acceptanceId: number
result: string
opinion: string
}
// ==================== 基础 CRUD ====================
// 查询验收会议分页
export const getAcceptanceMeetingPage = (params: any) => {
return request.get({ url: '/project/acceptance-meeting/page', params })
}
// 查询验收会议详情
export const getAcceptanceMeeting = (id: number) => {
return request.get({ url: '/project/acceptance-meeting/get?id=' + id })
}
// 新增验收会议
export const createAcceptanceMeeting = (data: AcceptanceMeetingVO) => {
return request.post({ url: '/project/acceptance-meeting/create', data })
}
// 修改验收会议
export const updateAcceptanceMeeting = (data: AcceptanceMeetingVO) => {
return request.put({ url: '/project/acceptance-meeting/update', data })
}
// 删除验收会议
export const deleteAcceptanceMeeting = (id: number) => {
return request.delete({ url: '/project/acceptance-meeting/delete?id=' + id })
}
// 批量删除验收会议
export const deleteAcceptanceMeetingList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance-meeting/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收会议 Excel
export const exportAcceptanceMeeting = (params: any) => {
return request.download({ url: '/project/acceptance-meeting/export-excel', params })
}
// ==================== 业务接口 ====================
// 批量创建会议
export const batchCreateMeeting = (data: AcceptanceMeetingBatchCreateReqVO) => {
return request.post({ url: '/project/acceptance-meeting/batch-create', data })
}
// 批量录入会议结果
export const batchInputResult = (data: AcceptanceMeetingBatchResultReqVO) => {
return request.post({ url: '/project/acceptance-meeting/batch-input-result', data })
}

View File

@ -0,0 +1,47 @@
import request from '@/config/axios'
// 验收会议专家关联 VO
export interface AcceptanceMeetingExpertVO {
id: number
meetingId: number
expertUserId: number
opinion: string
result: string
signatureUrl: string
createTime: Date
}
// 查询验收会议专家关联分页
export const getAcceptanceMeetingExpertPage = (params: any) => {
return request.get({ url: '/project/acceptance-meeting-expert/page', params })
}
// 查询验收会议专家关联详情
export const getAcceptanceMeetingExpert = (id: number) => {
return request.get({ url: '/project/acceptance-meeting-expert/get?id=' + id })
}
// 新增验收会议专家关联
export const createAcceptanceMeetingExpert = (data: AcceptanceMeetingExpertVO) => {
return request.post({ url: '/project/acceptance-meeting-expert/create', data })
}
// 修改验收会议专家关联
export const updateAcceptanceMeetingExpert = (data: AcceptanceMeetingExpertVO) => {
return request.put({ url: '/project/acceptance-meeting-expert/update', data })
}
// 删除验收会议专家关联
export const deleteAcceptanceMeetingExpert = (id: number) => {
return request.delete({ url: '/project/acceptance-meeting-expert/delete?id=' + id })
}
// 批量删除验收会议专家关联
export const deleteAcceptanceMeetingExpertList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance-meeting-expert/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收会议专家关联 Excel
export const exportAcceptanceMeetingExpert = (params: any) => {
return request.download({ url: '/project/acceptance-meeting-expert/export-excel', params })
}

View File

@ -0,0 +1,46 @@
import request from '@/config/axios'
// 验收会议关联 VO
export interface AcceptanceMeetingRelationVO {
id: number
meetingId: number
acceptanceId: number
meetingResult: string
meetingOpinion: string
createTime: Date
}
// 查询验收会议关联分页
export const getAcceptanceMeetingRelationPage = (params: any) => {
return request.get({ url: '/project/acceptance-meeting-relation/page', params })
}
// 查询验收会议关联详情
export const getAcceptanceMeetingRelation = (id: number) => {
return request.get({ url: '/project/acceptance-meeting-relation/get?id=' + id })
}
// 新增验收会议关联
export const createAcceptanceMeetingRelation = (data: AcceptanceMeetingRelationVO) => {
return request.post({ url: '/project/acceptance-meeting-relation/create', data })
}
// 修改验收会议关联
export const updateAcceptanceMeetingRelation = (data: AcceptanceMeetingRelationVO) => {
return request.put({ url: '/project/acceptance-meeting-relation/update', data })
}
// 删除验收会议关联
export const deleteAcceptanceMeetingRelation = (id: number) => {
return request.delete({ url: '/project/acceptance-meeting-relation/delete?id=' + id })
}
// 批量删除验收会议关联
export const deleteAcceptanceMeetingRelationList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance-meeting-relation/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收会议关联 Excel
export const exportAcceptanceMeetingRelation = (params: any) => {
return request.download({ url: '/project/acceptance-meeting-relation/export-excel', params })
}

View File

@ -0,0 +1,53 @@
import request from '@/config/axios'
// 验收审核意见 VO
export interface AcceptanceOpinionVO {
id: number
acceptanceId: number
opinionType: string
operatorId: number
opinion: string
result: string
signatureUrl: string
createTime: Date
}
// 查询验收审核意见分页
export const getAcceptanceOpinionPage = (params: any) => {
return request.get({ url: '/project/acceptance-opinion/page', params })
}
// 查询验收审核意见详情
export const getAcceptanceOpinion = (id: number) => {
return request.get({ url: '/project/acceptance-opinion/get?id=' + id })
}
// 新增验收审核意见
export const createAcceptanceOpinion = (data: AcceptanceOpinionVO) => {
return request.post({ url: '/project/acceptance-opinion/create', data })
}
// 修改验收审核意见
export const updateAcceptanceOpinion = (data: AcceptanceOpinionVO) => {
return request.put({ url: '/project/acceptance-opinion/update', data })
}
// 删除验收审核意见
export const deleteAcceptanceOpinion = (id: number) => {
return request.delete({ url: '/project/acceptance-opinion/delete?id=' + id })
}
// 批量删除验收审核意见
export const deleteAcceptanceOpinionList = (ids: number[]) => {
return request.delete({ url: '/project/acceptance-opinion/delete-list', params: { ids: ids.join(',') } })
}
// 导出验收审核意见 Excel
export const exportAcceptanceOpinion = (params: any) => {
return request.download({ url: '/project/acceptance-opinion/export-excel', params })
}
// 根据验收ID获取审核意见列表
export const getListByAcceptanceId = (acceptanceId: number) => {
return request.get({ url: '/project/acceptance-opinion/list-by-acceptance?acceptanceId=' + acceptanceId })
}

View File

@ -0,0 +1,51 @@
import request from '@/config/axios'
// 项目主表 VO
export interface ProjectVO {
id: number
projectCode: string
contractCode: string
projectName: string
applyUnit: string
liaisonUserId: number
principalUserId: number
serviceUserId: number
status: string
currentNode: string
createTime: Date
}
// 查询项目主表分页
export const getProjectPage = (params: any) => {
return request.get({ url: '/project/project/page', params })
}
// 查询项目主表详情
export const getProject = (id: number) => {
return request.get({ url: '/project/project/get?id=' + id })
}
// 新增项目主表
export const createProject = (data: ProjectVO) => {
return request.post({ url: '/project/project/create', data })
}
// 修改项目主表
export const updateProject = (data: ProjectVO) => {
return request.put({ url: '/project/project/update', data })
}
// 删除项目主表
export const deleteProject = (id: number) => {
return request.delete({ url: '/project/project/delete?id=' + id })
}
// 批量删除项目主表
export const deleteProjectList = (ids: number[]) => {
return request.delete({ url: '/project/project/delete-list', params: { ids: ids.join(',') } })
}
// 导出项目主表 Excel
export const exportProject = (params: any) => {
return request.download({ url: '/project/project/export-excel', params })
}

View File

@ -704,6 +704,28 @@ const remainingRouter: AppRouteRecordRaw[] = [
breadcrumb: false
}
},
{
path: '/project',
component: Layout,
name: 'ProjectCenter',
meta: {
hidden: true
},
children: [
{
path: 'acceptance/detail/:id(\\d+)',
name: 'AcceptanceDetail',
meta: {
title: '验收详情',
noCache: true,
hidden: true,
canTo: true,
activeMenu: '/project/acceptance'
},
component: () => import('@/views/project/acceptance/detail/index.vue')
}
]
},
{
path: '/iot',
component: Layout,

View File

@ -0,0 +1,218 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-alert
:title="'验收ID: ' + acceptanceInfo.id + ' | 类型: ' + getAcceptanceTypeLabel(acceptanceInfo.acceptanceType)"
type="info"
:closable="false"
class="mb-4"
/>
<el-form-item label="审核结果" prop="result">
<el-radio-group v-model="formData.result">
<el-radio value="PASS">通过</el-radio>
<el-radio value="REJECT">驳回</el-radio>
<el-radio v-if="showRectifyOption" value="RECTIFY"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见" prop="opinion">
<el-input
v-model="formData.opinion"
type="textarea"
:rows="4"
placeholder="请输入审核意见"
/>
</el-form-item>
<el-form-item v-if="showRectifyDeadline && formData.result === 'RECTIFY'" label="整改期限" prop="rectifyDeadline">
<el-date-picker
v-model="formData.rectifyDeadline"
type="datetime"
placeholder="请选择整改期限"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item v-if="needSignature" label="电子签名">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleSignatureSuccess"
accept="image/*"
>
<el-button type="primary">上传签名</el-button>
</el-upload>
<el-image
v-if="formData.signatureUrl"
:src="formData.signatureUrl"
style="width: 200px; height: 80px; margin-top: 10px"
fit="contain"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"></el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as AcceptanceApi from '@/api/project/acceptance'
import { FormRules } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
defineOptions({ name: 'AcceptanceAuditForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('审核') //
const formLoading = ref(false) //
const auditType = ref('') // : pre-audit, final-admin, rectify, expert-check
const acceptanceInfo = ref({
id: undefined as number | undefined,
acceptanceType: '',
status: ''
})
const formData = ref({
acceptanceId: undefined as number | undefined,
result: 'PASS',
opinion: '',
signatureUrl: '',
rectifyDeadline: undefined as string | undefined
})
const formRules = reactive<FormRules>({
result: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
opinion: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
})
const formRef = ref() // Ref
//
const uploadUrl = import.meta.env.VITE_BASE_URL + '/infra/file/upload'
const uploadHeaders = { Authorization: 'Bearer ' + getAccessToken() }
//
const showRectifyOption = computed(() => {
return ['pre-audit', 'final-admin', 'expert-check'].includes(auditType.value)
})
//
const showRectifyDeadline = computed(() => {
return auditType.value === 'rectify'
})
//
const needSignature = computed(() => {
return ['pre-audit', 'expert-check'].includes(auditType.value)
})
const getAcceptanceTypeLabel = (type: string) => {
return type === 'PRE' ? '预验收' : type === 'FINAL' ? '终验' : type
}
/** 打开弹窗 */
const open = async (row: AcceptanceApi.AcceptanceVO, type?: string) => {
dialogVisible.value = true
resetForm()
acceptanceInfo.value = {
id: row.id,
acceptanceType: row.acceptanceType,
status: row.status
}
formData.value.acceptanceId = row.id
//
if (type) {
auditType.value = type
} else {
//
if (row.status === '10') {
auditType.value = row.acceptanceType === 'PRE' ? 'pre-audit' : 'final-admin'
} else if (row.status === '40') {
auditType.value = 'rectify'
} else {
auditType.value = 'pre-audit'
}
}
dialogTitle.value = getDialogTitle()
}
defineExpose({ open }) // open
const getDialogTitle = () => {
switch (auditType.value) {
case 'pre-audit': return '预验收审核'
case 'final-admin': return '终验管理员初审'
case 'rectify': return '整改审核'
case 'expert-check': return '专家复核'
default: return '审核'
}
}
/** 签名上传成功 */
const handleSignatureSuccess = (response: any) => {
if (response.code === 0) {
formData.value.signatureUrl = response.data
message.success('签名上传成功')
} else {
message.error('签名上传失败: ' + response.msg)
}
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
//
switch (auditType.value) {
case 'pre-audit':
await AcceptanceApi.auditPreAcceptance(formData.value as AcceptanceApi.AcceptanceAuditReqVO)
break
case 'final-admin':
await AcceptanceApi.auditFinalAdmin(formData.value as AcceptanceApi.AcceptanceAuditReqVO)
break
case 'rectify':
await AcceptanceApi.auditRectify(formData.value as AcceptanceApi.AcceptanceRectifyAuditReqVO)
break
case 'expert-check':
await AcceptanceApi.expertCheck(formData.value as AcceptanceApi.AcceptanceAuditReqVO)
break
}
message.success('审核提交成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
acceptanceInfo.value = {
id: undefined,
acceptanceType: '',
status: ''
}
formData.value = {
acceptanceId: undefined,
result: 'PASS',
opinion: '',
signatureUrl: '',
rectifyDeadline: undefined
}
auditType.value = ''
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,629 @@
<template>
<ContentWrap>
<div v-loading="loading">
<!-- 头部信息 -->
<!-- 头部信息 -->
<div class="bg-white p-5 mb-6 rounded-lg shadow-sm border border-gray-100">
<div class="flex justify-between items-start mb-8">
<div>
<h2 class="text-2xl font-bold text-gray-800 flex items-center mb-3">
{{ 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>
<div class="text-gray-500 text-sm flex items-center gap-4 bg-gray-50 px-4 py-2 rounded-md">
<span><span class="font-medium text-gray-700">项目编号</span>{{ projectInfo.projectCode }}</span>
<el-divider direction="vertical" />
<span><span class="font-medium text-gray-700">验收ID</span>{{ acceptance.id }}</span>
<el-divider direction="vertical" />
<span><span class="font-medium text-gray-700">轮次</span> {{ acceptance.round }} </span>
</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">
{{ getStatusLabel(acceptance.status) }}
</el-tag>
</div>
</div>
<!-- 流程步骤 -->
<el-steps :active="activeStep" finish-status="success" align-center class="mb-2">
<el-step title="提交材料" description="待提交/完善材料" />
<el-step title="初步审核" description="对口人/组长审核" />
<el-step title="终验申请" description="确认进入终验" />
<el-step title="终验评审" description="会议评审与整改" />
<el-step title="归档完成" description="流程结束" />
</el-steps>
</div>
<el-divider />
<el-tabs v-model="activeTab">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="info">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目联络人">{{ liaisonUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="对口人">{{ serviceUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ formatDate(acceptance.startTime) }}</el-descriptions-item>
<el-descriptions-item label="完成时间">{{ formatDate(acceptance.endTime) || '-' }}</el-descriptions-item>
<el-descriptions-item label="流程实例ID">{{ acceptance.processInstanceId || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(acceptance.createTime) }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 验收材料 -->
<el-tab-pane label="验收材料" name="materials">
<div class="mb-6" v-if="canUploadMaterial">
<el-alert
title="待办任务:上传验收材料"
type="primary"
:closable="false"
show-icon
description="请完成下方所有必填材料的上传,确认无误后点击底部的【提交材料】按钮进入审核流程。"
/>
</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">
<!-- 文件图标 -->
<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>
<span v-else class="text-xs text-gray-400 italic">暂未上传文件</span>
</div>
<!-- 操作按钮组 -->
<div class="flex items-center justify-between mt-2 pt-3 border-t border-gray-100">
<div class="flex gap-2">
<el-button
v-if="item.fileUrl"
link
size="small"
type="primary"
@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 v-if="canUploadMaterial">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res, file) => handleUploadSuccess(res, file, item)"
>
<el-button
size="small"
:type="item.fileUrl ? 'primary' : 'primary'"
:plain="!!item.fileUrl"
class="!rounded-md"
>
<Icon icon="ep:upload" class="mr-1"/>
{{ item.fileUrl ? '重新上传' : '上传材料' }}
</el-button>
</el-upload>
</div>
</div>
</div>
</div>
<!-- 状态角标 -->
<div class="absolute top-0 right-0">
<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>
<div v-else class="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded-bl-lg">待上传</div>
</div>
</el-card>
</template>
</div>
</el-tab-pane>
<!-- 审批记录 -->
<el-tab-pane label="审批记录" name="opinions">
<el-timeline>
<el-timeline-item
v-for="(opinion, index) in opinions"
:key="index"
:type="getOpinionType(opinion.result)"
:timestamp="formatDate(opinion.createTime)"
placement="top"
>
<el-card>
<h4 class="mb-2">
{{ opinion.reviewerRole || '审核人' }}{{ opinion.reviewerName || '未知' }}
<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">
{{ getOpinionLabel(opinion.result) }}
</el-tag>
</h4>
<p class="text-gray-600">{{ opinion.opinion || '无意见' }}</p>
<el-image
v-if="opinion.signatureUrl"
:src="opinion.signatureUrl"
style="width: 150px; height: 60px; margin-top: 8px"
fit="contain"
/>
</el-card>
</el-timeline-item>
<el-empty v-if="opinions.length === 0" description="暂无审批记录" />
</el-timeline>
</el-tab-pane>
</el-tabs>
<!-- 操作按钮 -->
<div class="mt-6 flex justify-end gap-2" v-if="showActions">
<el-button @click="goBack"></el-button>
<el-button
v-if="canSubmitMaterial"
type="primary"
@click="handleSubmitMaterial"
>
提交材料
</el-button>
<el-button
v-if="canSubmitRectify"
type="warning"
@click="handleSubmitRectify"
>
提交整改
</el-button>
<el-button
v-if="canSubmitFinal"
type="success"
@click="handleSubmitFinal"
>
提交终验申请
</el-button>
<el-button
v-if="canAudit"
type="primary"
@click="openAuditDialog"
>
<Icon icon="ep:check" class="mr-1" /> 审核
</el-button>
</div>
</div>
<!-- 审核对话框 -->
<el-dialog
v-model="auditDialogVisible"
title="验收审核"
width="500px"
:close-on-click-modal="false"
>
<el-form
ref="auditFormRef"
:model="auditForm"
:rules="auditFormRules"
label-width="100px"
>
<el-form-item label="审核结果" prop="result">
<el-radio-group v-model="auditForm.result">
<el-radio-button value="PASS">
<Icon icon="ep:check" class="mr-1" /> 通过
</el-radio-button>
<el-radio-button value="REJECT">
<Icon icon="ep:close" class="mr-1" /> 驳回
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见" prop="opinion">
<el-input
v-model="auditForm.opinion"
type="textarea"
:rows="4"
:placeholder="auditForm.result === 'REJECT' ? '请填写驳回理由...' : '请填写审核意见...'"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAudit"></el-button>
</template>
</el-dialog>
</ContentWrap>
</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)
const activeTab = ref('info')
//
const acceptance = ref<AcceptanceApi.AcceptanceVO>({} as AcceptanceApi.AcceptanceVO)
//
const projectInfo = ref<any>({})
//
const liaisonUserName = ref('')
const serviceUserName = ref('')
//
const materials = ref<any[]>([])
//
const opinions = ref<any[]>([])
//
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 canUploadMaterial = computed(() => {
const currentUserId = useUserStore().getUser.id
//
return projectInfo.value.liaisonUserId === currentUserId && ['05', '11', '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)
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
}
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 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 {
await AcceptanceApi.auditPreAcceptance({
acceptanceId: acceptance.value.id,
result: auditForm.value.result,
opinion: auditForm.value.opinion
})
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 || '-'
}
//
// 1.
const defs = await AcceptanceMaterialDefApi.getListByType(acceptance.value.acceptanceType)
// 2.
const uploads = await AcceptanceMaterialApi.getListByAcceptanceId(id) as unknown as any[]
// 3.
materials.value = defs.map((def: any) => {
//
const allUploads = uploads.filter((u: any) => u.materialCode === def.materialCode)
// IDID
const upload = allUploads.length > 0 ? allUploads.sort((a: any, b: any) => b.id - a.id)[0] : undefined
return {
...def,
materialType: def.requiredFlag ? '必填' : '可选',
isRequired: def.requiredFlag, // 便使
fileUrl: upload?.fileUrl,
fileName: upload?.fileName,
fileSize: upload?.fileSize,
uploadId: upload?.id,
acceptanceId: id
}
})
//
//
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 {
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: #67c23a;
border-color: #67c23a;
}
:deep(.el-step__title.is-process) {
color: #67c23a;
font-weight: bold;
}
:deep(.el-step__description.is-process) {
color: #67c23a;
}
</style>

View File

@ -0,0 +1,303 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="项目ID" prop="projectId">
<el-input
v-model="queryParams.projectId"
placeholder="请输入项目ID"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item label="验收类型" prop="acceptanceType">
<el-select
v-model="queryParams.acceptanceType"
placeholder="请选择验收类型"
clearable
class="!w-180px"
>
<el-option label="预验收" value="PRE" />
<el-option label="终验" value="FINAL" />
</el-select>
</el-form-item>
<el-form-item label="验收状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择验收状态"
clearable
class="!w-180px"
>
<el-option label="待审核" value="10" />
<el-option label="审核中" value="20" />
<el-option label="待会议" value="30" />
<el-option label="待整改" value="40" />
<el-option label="已通过" value="80" />
<el-option label="已驳回" value="90" />
</el-select>
</el-form-item>
<el-form-item label="轮次" prop="round">
<el-input-number
v-model="queryParams.round"
:min="1"
placeholder="轮次"
class="!w-120px"
/>
</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-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['project:acceptance:export']"
>
<Icon icon="ep:download" />导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="验收ID" align="center" prop="id" width="80" />
<el-table-column label="项目ID" align="center" prop="projectId" width="80" />
<el-table-column label="验收类型" align="center" prop="acceptanceType" width="100">
<template #default="scope">
<el-tag v-if="scope.row.acceptanceType === 'PRE'" type="info"></el-tag>
<el-tag v-else-if="scope.row.acceptanceType === 'FINAL'" type="primary">终验</el-tag>
<el-tag v-else>{{ scope.row.acceptanceType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)">{{ getStatusLabel(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="轮次" align="center" prop="round" width="80" />
<el-table-column label="流程实例ID" align="center" prop="processInstanceId" :show-overflow-tooltip="true" />
<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">
<el-button
link
type="primary"
@click="handleView(scope.row)"
>
查看
</el-button>
<el-button
link
type="primary"
@click="openAuditForm(scope.row)"
v-if="canAudit(scope.row)"
>
审核
</el-button>
<el-button
link
type="warning"
@click="handleForceArchive(scope.row)"
v-hasPermi="['project:acceptance:force-archive']"
>
强制归档
</el-button>
<el-button
link
type="danger"
@click="handleCancel(scope.row)"
v-hasPermi="['project:acceptance:cancel']"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 审核表单弹窗 -->
<AcceptanceAuditForm ref="auditFormRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as AcceptanceApi from '@/api/project/acceptance'
import AcceptanceAuditForm from './AcceptanceAuditForm.vue'
defineOptions({ name: 'ProjectAcceptance' })
const message = useMessage() //
const { t } = useI18n() //
const router = useRouter()
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
projectId: undefined,
acceptanceType: undefined,
status: undefined,
round: undefined,
createTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const statusMap: Record<string, { label: string; type: string }> = {
'10': { label: '待审核', type: 'info' },
'20': { label: '审核中', type: 'warning' },
'30': { label: '待会议', type: '' },
'40': { label: '待整改', type: 'danger' },
'80': { label: '已通过', type: 'success' },
'90': { label: '已驳回', type: 'danger' }
}
const getStatusLabel = (status: string) => {
return statusMap[status]?.label || status
}
const getStatusType = (status: string) => {
return statusMap[status]?.type || 'info'
}
/** 判断是否可以审核 */
const canAudit = (row: AcceptanceApi.AcceptanceVO) => {
return ['10', '20', '40'].includes(row.status)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await AcceptanceApi.getAcceptancePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 查看详情 */
const handleView = (row: AcceptanceApi.AcceptanceVO) => {
router.push({ path: '/project/acceptance/detail/' + row.id })
}
/** 打开审核表单 */
const auditFormRef = ref()
const openAuditForm = (row: AcceptanceApi.AcceptanceVO) => {
auditFormRef.value.open(row)
}
/** 强制归档 */
const handleForceArchive = async (row: AcceptanceApi.AcceptanceVO) => {
try {
const { value } = await ElMessageBox.prompt('请输入强制归档原因', '强制归档', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '原因不能为空'
})
await AcceptanceApi.forceArchive(row.id, value)
message.success('强制归档成功')
await getList()
} catch {}
}
/** 取消验收 */
const handleCancel = async (row: AcceptanceApi.AcceptanceVO) => {
try {
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消验收', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '原因不能为空'
})
await AcceptanceApi.cancelAcceptance(row.id, value)
message.success('取消验收成功')
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await AcceptanceApi.exportAcceptance(queryParams)
download.excel(data, '验收数据.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,143 @@
<template>
<ContentWrap>
<div class="mb-4">
<el-alert
title="待办任务"
type="info"
description="以下是您需要处理的验收任务"
:closable="false"
/>
</div>
<el-table v-loading="loading" :data="list" stripe>
<el-table-column label="项目名称" align="center" prop="projectName" :show-overflow-tooltip="true" />
<el-table-column label="项目编号" align="center" prop="projectCode" width="150" />
<el-table-column label="验收类型" align="center" prop="acceptanceType" width="100">
<template #default="scope">
<el-tag v-if="scope.row.acceptanceType === 'PRE'" type="info"></el-tag>
<el-tag v-else-if="scope.row.acceptanceType === 'FINAL'" type="primary">终验</el-tag>
<el-tag v-else>{{ scope.row.acceptanceType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="验收状态" align="center" prop="statusDesc" width="120" />
<el-table-column label="轮次" align="center" prop="round" width="80" />
<el-table-column label="任务类型" align="center" prop="taskTypeDesc" width="150" />
<el-table-column label="任务名称" align="center" prop="taskName" :show-overflow-tooltip="true" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="handleProcess(scope.row)"
>
<Icon icon="ep:check" /> 处理
</el-button>
<el-button
link
type="info"
@click="handleView(scope.row)"
>
<Icon icon="ep:view" /> 查看
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 审核表单弹窗 -->
<AcceptanceAuditForm ref="auditFormRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as AcceptanceApi from '@/api/project/acceptance'
import AcceptanceAuditForm from '../AcceptanceAuditForm.vue'
defineOptions({ name: 'AcceptanceTodo' })
const router = useRouter()
const loading = ref(true) //
const list = ref<AcceptanceApi.AcceptanceTodoVO[]>([]) //
/** 查询待办列表 */
const getList = async () => {
loading.value = true
try {
list.value = await AcceptanceApi.getTodoList()
} finally {
loading.value = false
}
}
/** 处理任务 */
const auditFormRef = ref()
const handleProcess = (row: AcceptanceApi.AcceptanceTodoVO) => {
//
const taskType = row.taskType
if (['LIAISON_REVIEW', 'LEADER_REVIEW', 'ADMIN_REVIEW', 'EXPERT_CHECK'].includes(taskType)) {
//
auditFormRef.value.open({
id: row.acceptanceId,
acceptanceType: row.acceptanceType,
status: row.status
}, getAuditType(taskType))
} else if (taskType === 'RECTIFY_SUBMIT' || taskType === 'task_pre_rectify' || taskType === 'task_final_rectify') {
// tab
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 {
//
handleView(row)
}
}
/** 获取审核类型 */
const getAuditType = (taskType: string) => {
switch (taskType) {
case 'LIAISON_REVIEW':
case 'LEADER_REVIEW':
return 'pre-audit'
case 'ADMIN_REVIEW':
return 'final-admin'
case 'EXPERT_CHECK':
return 'expert-check'
default:
return 'pre-audit'
}
}
/** 提交整改 */
const message = useMessage()
const handleSubmitRectify = async (row: AcceptanceApi.AcceptanceTodoVO) => {
try {
await message.confirm('确定提交整改吗?')
if (row.acceptanceType === 'PRE') {
await AcceptanceApi.submitPreRectify(row.acceptanceId)
} else {
await AcceptanceApi.submitFinalRectify(row.acceptanceId)
}
message.success('整改提交成功')
await getList()
} catch {}
}
/** 查看详情 */
const handleView = (row: AcceptanceApi.AcceptanceTodoVO) => {
router.push({ path: '/project/acceptance/detail/' + row.acceptanceId, query: { tab: 'info' } })
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,179 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="验收ID" prop="acceptanceId">
<el-input v-model="formData.acceptanceId" placeholder="请输入验收ID" />
</el-form-item>
<el-form-item label="材料编号" prop="materialCode">
<el-select v-model="formData.materialCode" placeholder="请选择材料类型" filterable>
<el-option
v-for="def in materialDefList"
:key="def.materialCode"
:label="def.materialName + ' (' + def.materialCode + ')'"
:value="def.materialCode"
/>
</el-select>
</el-form-item>
<el-form-item label="上传文件" prop="fileUrl">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeUpload"
>
<el-button type="primary">选择文件</el-button>
</el-upload>
<div v-if="formData.fileName" class="mt-2">
<el-tag>{{ formData.fileName }}</el-tag>
</div>
</el-form-item>
<el-form-item label="版本号" prop="version">
<el-input-number v-model="formData.version" :min="1" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as AcceptanceMaterialApi from '@/api/project/acceptanceMaterial'
import * as AcceptanceMaterialDefApi from '@/api/project/acceptanceMaterialDef'
import { FormRules } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
defineOptions({ name: 'AcceptanceMaterialForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) //
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
acceptanceId: undefined,
materialCode: '',
fileName: '',
fileUrl: '',
fileSize: 0,
fileType: '',
version: 1,
remark: ''
})
const formRules = reactive<FormRules>({
acceptanceId: [{ required: true, message: '验收ID不能为空', trigger: 'blur' }],
materialCode: [{ required: true, message: '材料编号不能为空', trigger: 'change' }],
fileUrl: [{ required: true, message: '请上传文件', trigger: 'change' }]
})
const formRef = ref() // Ref
const materialDefList = ref<AcceptanceMaterialDefApi.AcceptanceMaterialDefVO[]>([]) //
//
const uploadUrl = import.meta.env.VITE_BASE_URL + '/infra/file/upload'
const uploadHeaders = { Authorization: 'Bearer ' + getAccessToken() }
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
const defResult = await AcceptanceMaterialDefApi.getAcceptanceMaterialDefPage({ pageNo: 1, pageSize: 100 })
materialDefList.value = defResult.list
//
if (id) {
formLoading.value = true
try {
formData.value = await AcceptanceMaterialApi.getAcceptanceMaterial(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 上传前校验 */
const beforeUpload = (file: File) => {
const maxSize = 50 * 1024 * 1024 // 50MB
if (file.size > maxSize) {
message.error('文件大小不能超过50MB')
return false
}
return true
}
/** 上传成功 */
const handleUploadSuccess = (response: any, file: File) => {
if (response.code === 0) {
formData.value.fileUrl = response.data
formData.value.fileName = file.name
formData.value.fileSize = file.size
formData.value.fileType = file.name.split('.').pop() || ''
message.success('上传成功')
} else {
message.error('上传失败: ' + response.msg)
}
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as AcceptanceMaterialApi.AcceptanceMaterialVO
if (formType.value === 'create') {
await AcceptanceMaterialApi.createAcceptanceMaterial(data)
message.success(t('common.createSuccess'))
} else {
await AcceptanceMaterialApi.updateAcceptanceMaterial(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
acceptanceId: undefined,
materialCode: '',
fileName: '',
fileUrl: '',
fileSize: 0,
fileType: '',
version: 1,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,227 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="验收ID" prop="acceptanceId">
<el-input
v-model="queryParams.acceptanceId"
placeholder="请输入验收ID"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item label="材料编号" prop="materialCode">
<el-input
v-model="queryParams.materialCode"
placeholder="请输入材料编号"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item label="文件名" prop="fileName">
<el-input
v-model="queryParams.fileName"
placeholder="请输入文件名"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['project:acceptance-material:create']"
>
<Icon icon="ep:plus" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['project:acceptance-material:export']"
>
<Icon icon="ep:download" />导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="验收ID" align="center" prop="acceptanceId" width="100" />
<el-table-column label="材料编号" align="center" prop="materialCode" width="150" />
<el-table-column label="文件名" align="center" prop="fileName" :show-overflow-tooltip="true" />
<el-table-column label="文件类型" align="center" prop="fileType" width="100" />
<el-table-column label="文件大小" align="center" prop="fileSize" width="100">
<template #default="scope">
{{ formatFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column label="版本" align="center" prop="version" width="80" />
<el-table-column
label="上传时间"
align="center"
prop="uploadTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="handleDownload(scope.row)"
>
下载
</el-button>
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['project:acceptance-material:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['project:acceptance-material:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<AcceptanceMaterialForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as AcceptanceMaterialApi from '@/api/project/acceptanceMaterial'
import AcceptanceMaterialForm from './AcceptanceMaterialForm.vue'
defineOptions({ name: 'AcceptanceMaterial' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
acceptanceId: undefined,
materialCode: undefined,
fileName: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 格式化文件大小 */
const formatFileSize = (size: number) => {
if (!size) return '-'
if (size < 1024) return size + ' B'
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB'
return (size / 1024 / 1024).toFixed(2) + ' MB'
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await AcceptanceMaterialApi.getAcceptanceMaterialPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 下载文件 */
const handleDownload = (row: AcceptanceMaterialApi.AcceptanceMaterialVO) => {
if (row.fileUrl) {
window.open(row.fileUrl, '_blank')
} else {
message.warning('文件地址不存在')
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await AcceptanceMaterialApi.deleteAcceptanceMaterial(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await AcceptanceMaterialApi.exportAcceptanceMaterial(queryParams)
download.excel(data, '验收材料.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,129 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="验收类型" prop="acceptanceType">
<el-select v-model="formData.acceptanceType" placeholder="请选择验收类型">
<el-option label="预验收" value="PRE" />
<el-option label="终验" value="FINAL" />
</el-select>
</el-form-item>
<el-form-item label="材料编码" prop="materialCode">
<el-input v-model="formData.materialCode" placeholder="请输入材料编码" />
</el-form-item>
<el-form-item label="材料名称" prop="materialName">
<el-input v-model="formData.materialName" placeholder="请输入材料名称" />
</el-form-item>
<el-form-item label="是否必传" prop="requiredFlag">
<el-switch v-model="formData.requiredFlag" />
</el-form-item>
<el-form-item label="上传角色" prop="uploadRole">
<el-select v-model="formData.uploadRole" placeholder="请选择上传角色" clearable>
<el-option label="申报单位" value="APPLICANT" />
<el-option label="网信办" value="ADMIN" />
<el-option label="专家" value="EXPERT" />
</el-select>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as AcceptanceMaterialDefApi from '@/api/project/acceptanceMaterialDef'
import { FormRules } from 'element-plus'
defineOptions({ name: 'AcceptanceMaterialDefForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) //
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
acceptanceType: 'PRE',
materialCode: '',
materialName: '',
requiredFlag: false,
uploadRole: '',
sort: 0
})
const formRules = reactive<FormRules>({
acceptanceType: [{ required: true, message: '验收类型不能为空', trigger: 'change' }],
materialCode: [{ required: true, message: '材料编码不能为空', trigger: 'blur' }],
materialName: [{ required: true, message: '材料名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await AcceptanceMaterialDefApi.getAcceptanceMaterialDef(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as AcceptanceMaterialDefApi.AcceptanceMaterialDefVO
if (formType.value === 'create') {
await AcceptanceMaterialDefApi.createAcceptanceMaterialDef(data)
message.success(t('common.createSuccess'))
} else {
await AcceptanceMaterialDefApi.updateAcceptanceMaterialDef(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
acceptanceType: 'PRE',
materialCode: '',
materialName: '',
requiredFlag: false,
uploadRole: '',
sort: 0
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,212 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="验收类型" prop="acceptanceType">
<el-select
v-model="queryParams.acceptanceType"
placeholder="请选择验收类型"
clearable
class="!w-180px"
>
<el-option label="预验收" value="PRE" />
<el-option label="终验" value="FINAL" />
</el-select>
</el-form-item>
<el-form-item label="材料编码" prop="materialCode">
<el-input
v-model="queryParams.materialCode"
placeholder="请输入材料编码"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item label="材料名称" prop="materialName">
<el-input
v-model="queryParams.materialName"
placeholder="请输入材料名称"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['project:acceptance-material-def:create']"
>
<Icon icon="ep:plus" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['project:acceptance-material-def:export']"
>
<Icon icon="ep:download" />导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="验收类型" align="center" prop="acceptanceType" width="100">
<template #default="scope">
<el-tag v-if="scope.row.acceptanceType === 'PRE'" type="info"></el-tag>
<el-tag v-else-if="scope.row.acceptanceType === 'FINAL'" type="primary">终验</el-tag>
<el-tag v-else>{{ scope.row.acceptanceType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="材料编码" align="center" prop="materialCode" width="150" />
<el-table-column label="材料名称" align="center" prop="materialName" :show-overflow-tooltip="true" />
<el-table-column label="是否必传" align="center" prop="requiredFlag" width="100">
<template #default="scope">
<el-tag v-if="scope.row.requiredFlag" type="danger"></el-tag>
<el-tag v-else type="info">非必传</el-tag>
</template>
</el-table-column>
<el-table-column label="上传角色" align="center" prop="uploadRole" width="120" />
<el-table-column label="排序" align="center" prop="sort" width="80" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['project:acceptance-material-def:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['project:acceptance-material-def:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<AcceptanceMaterialDefForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as AcceptanceMaterialDefApi from '@/api/project/acceptanceMaterialDef'
import AcceptanceMaterialDefForm from './AcceptanceMaterialDefForm.vue'
defineOptions({ name: 'AcceptanceMaterialDef' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
acceptanceType: undefined,
materialCode: undefined,
materialName: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await AcceptanceMaterialDefApi.getAcceptanceMaterialDefPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await AcceptanceMaterialDefApi.deleteAcceptanceMaterialDef(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await AcceptanceMaterialDefApi.exportAcceptanceMaterialDef(queryParams)
download.excel(data, '材料定义.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,190 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="会议名称" prop="meetingName">
<el-input v-model="formData.meetingName" placeholder="请输入会议名称" />
</el-form-item>
<el-form-item label="会议时间" prop="meetingTime">
<el-date-picker
v-model="formData.meetingTime"
type="datetime"
placeholder="请选择会议时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="会议地点" prop="meetingLocation">
<el-input v-model="formData.meetingLocation" placeholder="请输入会议地点" />
</el-form-item>
<el-form-item label="会议类型" prop="meetingType">
<el-select v-model="formData.meetingType" placeholder="请选择会议类型">
<el-option label="预验收" value="PRE" />
<el-option label="终验" value="FINAL" />
</el-select>
</el-form-item>
<el-form-item label="会议结论" prop="meetingResult">
<el-select v-model="formData.meetingResult" placeholder="请选择会议结论" clearable>
<el-option label="通过" value="PASS" />
<el-option label="不通过" value="REJECT" />
<el-option label="需整改" value="RECTIFY" />
</el-select>
</el-form-item>
<el-form-item label="专家意见" prop="meetingOpinion">
<el-input
v-model="formData.meetingOpinion"
type="textarea"
:rows="3"
placeholder="请输入专家组集体意见"
/>
</el-form-item>
<el-form-item label="会议纪要" prop="minutesFileUrl">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleMinutesSuccess"
>
<el-button type="primary">上传文件</el-button>
</el-upload>
<el-link v-if="formData.minutesFileUrl" :href="formData.minutesFileUrl" target="_blank" class="ml-2">
查看文件
</el-link>
</el-form-item>
<el-form-item label="签字扫描件" prop="signFileUrl">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleSignSuccess"
>
<el-button type="primary">上传文件</el-button>
</el-upload>
<el-link v-if="formData.signFileUrl" :href="formData.signFileUrl" target="_blank" class="ml-2">
查看文件
</el-link>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as AcceptanceMeetingApi from '@/api/project/acceptanceMeeting'
import { FormRules } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
defineOptions({ name: 'AcceptanceMeetingForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) //
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
meetingName: '',
meetingTime: '',
meetingLocation: '',
meetingType: 'FINAL',
meetingResult: '',
meetingOpinion: '',
minutesFileUrl: '',
signFileUrl: ''
})
const formRules = reactive<FormRules>({
meetingName: [{ required: true, message: '会议名称不能为空', trigger: 'blur' }],
meetingTime: [{ required: true, message: '会议时间不能为空', trigger: 'change' }],
meetingType: [{ required: true, message: '会议类型不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
//
const uploadUrl = import.meta.env.VITE_BASE_URL + '/infra/file/upload'
const uploadHeaders = { Authorization: 'Bearer ' + getAccessToken() }
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await AcceptanceMeetingApi.getAcceptanceMeeting(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 会议纪要上传成功 */
const handleMinutesSuccess = (response: any) => {
if (response.code === 0) {
formData.value.minutesFileUrl = response.data
message.success('上传成功')
}
}
/** 签字扫描件上传成功 */
const handleSignSuccess = (response: any) => {
if (response.code === 0) {
formData.value.signFileUrl = response.data
message.success('上传成功')
}
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as AcceptanceMeetingApi.AcceptanceMeetingVO
if (formType.value === 'create') {
await AcceptanceMeetingApi.createAcceptanceMeeting(data)
message.success(t('common.createSuccess'))
} else {
await AcceptanceMeetingApi.updateAcceptanceMeeting(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
meetingName: '',
meetingTime: '',
meetingLocation: '',
meetingType: 'FINAL',
meetingResult: '',
meetingOpinion: '',
minutesFileUrl: '',
signFileUrl: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,174 @@
<template>
<Dialog v-model="dialogVisible" title="批量创建会议" width="700px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="选择验收" prop="acceptanceIds">
<el-select
v-model="formData.acceptanceIds"
multiple
filterable
placeholder="请选择要加入会议的验收实例"
style="width: 100%"
>
<el-option
v-for="item in acceptanceList"
:key="item.id"
:label="'验收ID: ' + item.id + ' - 项目ID: ' + item.projectId"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="会议名称" prop="meetingName">
<el-input v-model="formData.meetingName" placeholder="请输入会议名称" />
</el-form-item>
<el-form-item label="会议时间" prop="meetingTime">
<el-date-picker
v-model="formData.meetingTime"
type="datetime"
placeholder="请选择会议时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="会议地点" prop="meetingLocation">
<el-input v-model="formData.meetingLocation" placeholder="请输入会议地点" />
</el-form-item>
<el-form-item label="会议类型" prop="meetingType">
<el-select v-model="formData.meetingType" placeholder="请选择会议类型">
<el-option label="预验收" value="PRE" />
<el-option label="终验" value="FINAL" />
</el-select>
</el-form-item>
<el-form-item label="参会专家" prop="expertUserIds">
<el-select
v-model="formData.expertUserIds"
multiple
filterable
placeholder="请选择参会专家"
style="width: 100%"
>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="会议纪要" prop="minutesFileUrl">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleUploadSuccess"
>
<el-button type="primary">上传文件</el-button>
</el-upload>
<el-link v-if="formData.minutesFileUrl" :href="formData.minutesFileUrl" target="_blank" class="ml-2">
查看文件
</el-link>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"></el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as AcceptanceMeetingApi from '@/api/project/acceptanceMeeting'
import * as AcceptanceApi from '@/api/project/acceptance'
import * as UserApi from '@/api/system/user'
import { FormRules } from 'element-plus'
import { getAccessToken } from '@/utils/auth'
defineOptions({ name: 'BatchCreateForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
acceptanceIds: [] as number[],
meetingName: '',
meetingTime: '',
meetingLocation: '',
meetingType: 'FINAL',
expertUserIds: [] as number[],
minutesFileUrl: ''
})
const formRules = reactive<FormRules>({
acceptanceIds: [{ required: true, message: '请选择验收实例', trigger: 'change' }],
meetingName: [{ required: true, message: '会议名称不能为空', trigger: 'blur' }],
meetingTime: [{ required: true, message: '会议时间不能为空', trigger: 'change' }],
meetingType: [{ required: true, message: '会议类型不能为空', trigger: 'change' }],
expertUserIds: [{ required: true, message: '请选择参会专家', trigger: 'change' }]
})
const formRef = ref() // Ref
const acceptanceList = ref<AcceptanceApi.AcceptanceVO[]>([]) //
const userList = ref<UserApi.UserVO[]>([]) //
//
const uploadUrl = import.meta.env.VITE_BASE_URL + '/infra/file/upload'
const uploadHeaders = { Authorization: 'Bearer ' + getAccessToken() }
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
resetForm()
//
const result = await AcceptanceApi.getAcceptancePage({ pageNo: 1, pageSize: 100, status: '30' })
acceptanceList.value = result.list
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // open
/** 上传成功 */
const handleUploadSuccess = (response: any) => {
if (response.code === 0) {
formData.value.minutesFileUrl = response.data
message.success('上传成功')
}
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await AcceptanceMeetingApi.batchCreateMeeting(formData.value as AcceptanceMeetingApi.AcceptanceMeetingBatchCreateReqVO)
message.success('会议创建成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
acceptanceIds: [],
meetingName: '',
meetingTime: '',
meetingLocation: '',
meetingType: 'FINAL',
expertUserIds: [],
minutesFileUrl: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,138 @@
<template>
<Dialog v-model="dialogVisible" title="录入会议结果" width="700px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
label-width="100px"
>
<el-alert
:title="'会议: ' + meetingInfo.meetingName"
type="info"
:closable="false"
class="mb-4"
/>
<el-divider content-position="left">会议整体结论</el-divider>
<el-form-item label="会议结论" prop="meetingResult">
<el-select v-model="formData.overallResult" placeholder="请选择整体结论">
<el-option label="通过" value="PASS" />
<el-option label="不通过" value="REJECT" />
<el-option label="需整改" value="RECTIFY" />
</el-select>
</el-form-item>
<el-form-item label="专家意见" prop="meetingOpinion">
<el-input
v-model="formData.overallOpinion"
type="textarea"
:rows="3"
placeholder="请输入专家组集体意见"
/>
</el-form-item>
<el-divider content-position="left">各项目结果</el-divider>
<el-table :data="formData.results" border>
<el-table-column label="验收ID" align="center" prop="acceptanceId" width="100" />
<el-table-column label="结论" align="center" width="150">
<template #default="scope">
<el-select v-model="scope.row.result" placeholder="请选择">
<el-option label="通过" value="PASS" />
<el-option label="不通过" value="REJECT" />
<el-option label="需整改" value="RECTIFY" />
</el-select>
</template>
</el-table-column>
<el-table-column label="专家意见" align="center">
<template #default="scope">
<el-input v-model="scope.row.opinion" placeholder="请输入意见" />
</template>
</el-table-column>
</el-table>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"></el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as AcceptanceMeetingApi from '@/api/project/acceptanceMeeting'
import * as AcceptanceMeetingRelationApi from '@/api/project/acceptanceMeetingRelation'
defineOptions({ name: 'BatchResultForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const meetingInfo = ref({
id: undefined as number | undefined,
meetingName: ''
})
const formData = ref({
meetingId: undefined as number | undefined,
overallResult: 'PASS',
overallOpinion: '',
results: [] as AcceptanceMeetingApi.AcceptanceMeetingResultVO[]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (row: AcceptanceMeetingApi.AcceptanceMeetingVO) => {
dialogVisible.value = true
resetForm()
meetingInfo.value = {
id: row.id,
meetingName: row.meetingName
}
formData.value.meetingId = row.id
//
const result = await AcceptanceMeetingRelationApi.getAcceptanceMeetingRelationPage({
pageNo: 1,
pageSize: 100,
meetingId: row.id
})
formData.value.results = result.list.map((item: any) => ({
acceptanceId: item.acceptanceId,
result: item.meetingResult || 'PASS',
opinion: item.meetingOpinion || ''
}))
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
formLoading.value = true
try {
await AcceptanceMeetingApi.batchInputResult({
meetingId: formData.value.meetingId!,
results: formData.value.results
})
message.success('结果录入成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
meetingInfo.value = {
id: undefined,
meetingName: ''
}
formData.value = {
meetingId: undefined,
overallResult: 'PASS',
overallOpinion: '',
results: []
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,260 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="会议名称" prop="meetingName">
<el-input
v-model="queryParams.meetingName"
placeholder="请输入会议名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="会议类型" prop="meetingType">
<el-select
v-model="queryParams.meetingType"
placeholder="请选择会议类型"
clearable
class="!w-180px"
>
<el-option label="预验收" value="PRE" />
<el-option label="终验" value="FINAL" />
</el-select>
</el-form-item>
<el-form-item label="会议结论" prop="meetingResult">
<el-select
v-model="queryParams.meetingResult"
placeholder="请选择会议结论"
clearable
class="!w-180px"
>
<el-option label="通过" value="PASS" />
<el-option label="不通过" value="REJECT" />
<el-option label="需整改" value="RECTIFY" />
</el-select>
</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-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="primary"
plain
@click="openBatchCreateForm"
v-hasPermi="['project:acceptance-meeting:batch-create']"
>
<Icon icon="ep:plus" /> 批量创建会议
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['project:acceptance-meeting:export']"
>
<Icon icon="ep:download" />导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="会议ID" align="center" prop="id" width="80" />
<el-table-column label="会议名称" align="center" prop="meetingName" :show-overflow-tooltip="true" />
<el-table-column
label="会议时间"
align="center"
prop="meetingTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="会议地点" align="center" prop="meetingLocation" width="150" />
<el-table-column label="会议类型" align="center" prop="meetingType" width="100">
<template #default="scope">
<el-tag v-if="scope.row.meetingType === 'PRE'" type="info"></el-tag>
<el-tag v-else-if="scope.row.meetingType === 'FINAL'" type="primary">终验</el-tag>
<el-tag v-else>{{ scope.row.meetingType }}</el-tag>
</template>
</el-table-column>
<el-table-column label="会议结论" align="center" prop="meetingResult" width="100">
<template #default="scope">
<el-tag v-if="scope.row.meetingResult === 'PASS'" type="success"></el-tag>
<el-tag v-else-if="scope.row.meetingResult === 'REJECT'" type="danger">不通过</el-tag>
<el-tag v-else-if="scope.row.meetingResult === 'RECTIFY'" type="warning">需整改</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="专家意见" align="center" prop="meetingOpinion" :show-overflow-tooltip="true" />
<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">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['project:acceptance-meeting:update']"
>
编辑
</el-button>
<el-button
link
type="warning"
@click="openResultForm(scope.row)"
v-hasPermi="['project:acceptance-meeting:batch-result']"
>
录入结果
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['project:acceptance-meeting:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<AcceptanceMeetingForm ref="formRef" @success="getList" />
<!-- 批量创建弹窗 -->
<BatchCreateForm ref="batchCreateFormRef" @success="getList" />
<!-- 录入结果弹窗 -->
<BatchResultForm ref="resultFormRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as AcceptanceMeetingApi from '@/api/project/acceptanceMeeting'
import AcceptanceMeetingForm from './AcceptanceMeetingForm.vue'
import BatchCreateForm from './BatchCreateForm.vue'
import BatchResultForm from './BatchResultForm.vue'
defineOptions({ name: 'AcceptanceMeeting' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
meetingName: undefined,
meetingType: undefined,
meetingResult: undefined,
createTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await AcceptanceMeetingApi.getAcceptanceMeetingPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 批量创建会议 */
const batchCreateFormRef = ref()
const openBatchCreateForm = () => {
batchCreateFormRef.value.open()
}
/** 录入会议结果 */
const resultFormRef = ref()
const openResultForm = (row: AcceptanceMeetingApi.AcceptanceMeetingVO) => {
resultFormRef.value.open(row)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await AcceptanceMeetingApi.deleteAcceptanceMeeting(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await AcceptanceMeetingApi.exportAcceptanceMeeting(queryParams)
download.excel(data, '会议数据.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,214 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="验收ID" prop="acceptanceId">
<el-input
v-model="queryParams.acceptanceId"
placeholder="请输入验收ID"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item label="操作人ID" prop="operatorId">
<el-input
v-model="queryParams.operatorId"
placeholder="请输入操作人ID"
clearable
@keyup.enter="handleQuery"
class="!w-180px"
/>
</el-form-item>
<el-form-item label="审核结果" prop="result">
<el-select
v-model="queryParams.result"
placeholder="请选择审核结果"
clearable
class="!w-180px"
>
<el-option label="通过" value="PASS" />
<el-option label="驳回" value="REJECT" />
<el-option label="需整改" value="RECTIFY" />
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['project:acceptance-opinion:export']"
>
<Icon icon="ep:download" />导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="验收ID" align="center" prop="acceptanceId" width="100" />
<el-table-column label="意见类型" align="center" prop="opinionType" width="120" />
<el-table-column label="操作人ID" align="center" prop="operatorId" width="100" />
<el-table-column label="审核结果" align="center" prop="result" width="100">
<template #default="scope">
<el-tag v-if="scope.row.result === 'PASS'" type="success"></el-tag>
<el-tag v-else-if="scope.row.result === 'REJECT'" type="danger">驳回</el-tag>
<el-tag v-else-if="scope.row.result === 'RECTIFY'" type="warning">需整改</el-tag>
<span v-else>{{ scope.row.result }}</span>
</template>
</el-table-column>
<el-table-column label="审核意见" align="center" prop="opinion" :show-overflow-tooltip="true" />
<el-table-column label="签名" align="center" prop="signatureUrl" width="100">
<template #default="scope">
<el-image
v-if="scope.row.signatureUrl"
:src="scope.row.signatureUrl"
:preview-src-list="[scope.row.signatureUrl]"
style="width: 50px; height: 20px"
fit="contain"
/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="100" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="handleView(scope.row)"
>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 详情弹窗 -->
<Dialog v-model="detailVisible" title="意见详情" width="600px">
<el-descriptions :column="1" border>
<el-descriptions-item label="验收ID">{{ detailData.acceptanceId }}</el-descriptions-item>
<el-descriptions-item label="意见类型">{{ detailData.opinionType }}</el-descriptions-item>
<el-descriptions-item label="操作人ID">{{ detailData.operatorId }}</el-descriptions-item>
<el-descriptions-item label="审核结果">
<el-tag v-if="detailData.result === 'PASS'" type="success"></el-tag>
<el-tag v-else-if="detailData.result === 'REJECT'" type="danger">驳回</el-tag>
<el-tag v-else-if="detailData.result === 'RECTIFY'" type="warning">需整改</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核意见">{{ detailData.opinion }}</el-descriptions-item>
<el-descriptions-item label="电子签名">
<el-image
v-if="detailData.signatureUrl"
:src="detailData.signatureUrl"
:preview-src-list="[detailData.signatureUrl]"
style="width: 200px; height: 80px"
fit="contain"
/>
<span v-else></span>
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as AcceptanceOpinionApi from '@/api/project/acceptanceOpinion'
defineOptions({ name: 'AcceptanceOpinion' })
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
acceptanceId: undefined,
operatorId: undefined,
result: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const detailVisible = ref(false)
const detailData = ref<AcceptanceOpinionApi.AcceptanceOpinionVO>({} as any)
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await AcceptanceOpinionApi.getAcceptanceOpinionPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 查看详情 */
const handleView = (row: AcceptanceOpinionApi.AcceptanceOpinionVO) => {
detailData.value = row
detailVisible.value = true
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await AcceptanceOpinionApi.exportAcceptanceOpinion(queryParams)
download.excel(data, '审核意见.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,186 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-row>
<el-col :span="12">
<el-form-item label="立项编号" prop="projectCode">
<el-input v-model="formData.projectCode" placeholder="请输入立项编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同编号" prop="contractCode">
<el-input v-model="formData.contractCode" placeholder="请输入合同编号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="formData.projectName" placeholder="请输入项目名称" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="申报单位" prop="applyUnit">
<el-input v-model="formData.applyUnit" placeholder="请输入申报单位" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="项目联络人" prop="liaisonUserId">
<el-select v-model="formData.liaisonUserId" placeholder="请选择项目联络人" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目负责人" prop="principalUserId">
<el-select v-model="formData.principalUserId" placeholder="请选择项目负责人" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="对口服务人" prop="serviceUserId">
<el-select v-model="formData.serviceUserId" placeholder="请选择对口服务人" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
<el-option label="待验收" value="0" />
<el-option label="验收中" value="1" />
<el-option label="已完成" value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as ProjectApi from '@/api/project/project'
import * as UserApi from '@/api/system/user'
import { FormRules } from 'element-plus'
defineOptions({ name: 'ProjectForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
projectCode: '',
contractCode: '',
projectName: '',
applyUnit: '',
liaisonUserId: undefined,
principalUserId: undefined,
serviceUserId: undefined,
status: '0'
})
const formRules = reactive<FormRules>({
projectCode: [{ required: true, message: '项目立项编号不能为空', trigger: 'blur' }],
projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const userList = ref<UserApi.UserVO[]>([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ProjectApi.getProject(id)
} finally {
formLoading.value = false
}
}
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as ProjectApi.ProjectVO
if (formType.value === 'create') {
await ProjectApi.createProject(data)
message.success(t('common.createSuccess'))
} else {
await ProjectApi.updateProject(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
projectCode: '',
contractCode: '',
projectName: '',
applyUnit: '',
liaisonUserId: undefined,
principalUserId: undefined,
serviceUserId: undefined,
status: '0'
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,135 @@
<template>
<Dialog v-model="dialogVisible" title="发起预验收">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="120px"
>
<el-form-item label="项目名称">
<el-input v-model="projectInfo.projectName" disabled />
</el-form-item>
<el-form-item label="项目编号">
<el-input v-model="projectInfo.projectCode" disabled />
</el-form-item>
<el-form-item label="对口服务人" prop="serviceUserId">
<el-select v-model="formData.serviceUserId" placeholder="请选择对口服务人" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="项目联络人" prop="liaisonUserId">
<el-select v-model="formData.liaisonUserId" placeholder="请选择项目联络人" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="专家组长" prop="leaderUserId">
<el-select v-model="formData.leaderUserId" placeholder="请选择专家组长" clearable filterable>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"></el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as ProjectApi from '@/api/project/project'
import * as AcceptanceApi from '@/api/project/acceptance'
import * as UserApi from '@/api/system/user'
import { FormRules } from 'element-plus'
defineOptions({ name: 'StartAcceptanceForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const projectInfo = ref({
id: undefined,
projectName: '',
projectCode: ''
})
const formData = ref({
projectId: undefined as number | undefined,
serviceUserId: undefined as number | undefined,
liaisonUserId: undefined as number | undefined,
leaderUserId: undefined as number | undefined
})
const formRules = reactive<FormRules>({
leaderUserId: [{ required: true, message: '专家组长不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
const userList = ref<UserApi.UserVO[]>([]) //
/** 打开弹窗 */
const open = async (row: ProjectApi.ProjectVO) => {
dialogVisible.value = true
resetForm()
projectInfo.value = {
id: row.id,
projectName: row.projectName,
projectCode: row.projectCode
}
formData.value.projectId = row.id
formData.value.serviceUserId = row.serviceUserId
formData.value.liaisonUserId = row.liaisonUserId
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await AcceptanceApi.submitPreAcceptance(formData.value as AcceptanceApi.AcceptanceSubmitPreReqVO)
message.success('预验收申请提交成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
projectInfo.value = {
id: undefined,
projectName: '',
projectCode: ''
}
formData.value = {
projectId: undefined,
serviceUserId: undefined,
liaisonUserId: undefined,
leaderUserId: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,277 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="申报单位" prop="applyUnit">
<el-input
v-model="queryParams.applyUnit"
placeholder="请输入申报单位"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option label="待验收" value="0" />
<el-option label="验收中" value="1" />
<el-option label="已完成" value="2" />
</el-select>
</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-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['project:project:create']"
>
<Icon icon="ep:plus" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['project:project:export']"
>
<Icon icon="ep:download" />导出
</el-button>
<el-button
type="danger"
plain
:disabled="checkedIds.length === 0"
@click="handleDeleteBatch"
v-hasPermi="['project:project:delete']"
>
<Icon icon="ep:delete" />批量删除
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" @selection-change="handleRowCheckboxChange">
<el-table-column type="selection" width="55" />
<el-table-column label="项目ID" align="center" prop="id" width="80" />
<el-table-column label="立项编号" align="center" prop="projectCode" width="150" />
<el-table-column label="合同编号" align="center" prop="contractCode" width="150" />
<el-table-column label="项目名称" align="center" prop="projectName" :show-overflow-tooltip="true" />
<el-table-column label="申报单位" align="center" prop="applyUnit" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === '0'" type="info"></el-tag>
<el-tag v-else-if="scope.row.status === '1'" type="warning">验收中</el-tag>
<el-tag v-else-if="scope.row.status === '2'" type="success">已完成</el-tag>
<el-tag v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="当前节点" align="center" prop="currentNode" width="120" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="180" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['project:project:update']"
>
编辑
</el-button>
<el-button
link
type="primary"
@click="handleStartAcceptance(scope.row)"
v-hasPermi="['project:acceptance:submit-pre']"
>
发起验收
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['project:project:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<ProjectForm ref="formRef" @success="getList" />
<!-- 发起验收弹窗 -->
<StartAcceptanceForm ref="startAcceptanceFormRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as ProjectApi from '@/api/project/project'
import ProjectForm from './ProjectForm.vue'
import StartAcceptanceForm from './StartAcceptanceForm.vue'
defineOptions({ name: 'ProjectProject' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
projectCode: undefined,
contractCode: undefined,
projectName: undefined,
applyUnit: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProjectApi.getProjectPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await ProjectApi.deleteProject(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 批量删除按钮操作 */
const checkedIds = ref<number[]>([])
const handleRowCheckboxChange = (rows: ProjectApi.ProjectVO[]) => {
checkedIds.value = rows.map((row) => row.id)
}
const handleDeleteBatch = async () => {
try {
//
await message.delConfirm()
//
await ProjectApi.deleteProjectList(checkedIds.value)
checkedIds.value = []
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await ProjectApi.exportProject(queryParams)
download.excel(data, '项目数据.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 发起验收操作 */
const startAcceptanceFormRef = ref()
const handleStartAcceptance = (row: ProjectApi.ProjectVO) => {
startAcceptanceFormRef.value.open(row)
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>