更新履约评价
This commit is contained in:
@@ -67,6 +67,73 @@ export function putObj(obj?: Object) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 履约验收流程接口 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第一步:保存履约验收公共配置,按分期次数自动生成批次
|
||||||
|
*/
|
||||||
|
export function saveCommonConfig(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/purchasingAccept/saveCommonConfig',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取履约验收公共配置及批次列表
|
||||||
|
*/
|
||||||
|
export function getCommonConfigWithBatches(purchaseId: string) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/purchasingAccept/commonConfigWithBatches',
|
||||||
|
method: 'get',
|
||||||
|
params: { purchaseId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第二步:更新单个批次
|
||||||
|
*/
|
||||||
|
export function updateBatch(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/purchasingAccept/updateBatch',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验收详情(含验收内容、验收小组)
|
||||||
|
*/
|
||||||
|
export function getDetail(purchaseId: string, batch?: number) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/purchasingAccept/detail',
|
||||||
|
method: 'get',
|
||||||
|
params: { purchaseId, batch }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许填报方式(金额<30万)
|
||||||
|
*/
|
||||||
|
export function canFillForm(purchaseId: string) {
|
||||||
|
return request({
|
||||||
|
url: '/purchase/purchasingAccept/canFillForm',
|
||||||
|
method: 'get',
|
||||||
|
params: { purchaseId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据品目类型获取验收项配置
|
||||||
|
*/
|
||||||
|
export function getAcceptanceItems(acceptanceType: string) {
|
||||||
|
return request({
|
||||||
|
url: `/purchase/acceptanceItemConfig/listByType/${acceptanceType}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 工具函数 ==========
|
// ========== 工具函数 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" class="accept-batch-form">
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="验收方式" prop="acceptType">
|
||||||
|
<el-radio-group v-model="form.acceptType" :disabled="readonly">
|
||||||
|
<el-radio label="1">填写履约验收评价表</el-radio>
|
||||||
|
<el-radio label="2" :disabled="!canFill">上传履约验收评价表</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div v-if="!canFill" class="el-form-item__tip">金额≥30万,仅支持上传模版</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="验收日期" prop="acceptDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.acceptDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="请选择"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="readonly"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 填报方式:验收内容表格 -->
|
||||||
|
<template v-if="form.acceptType === '1' && canFill">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="验收内容" prop="acceptContents">
|
||||||
|
<el-table :data="form.acceptContents" border size="small" max-height="260" class="accept-content-table">
|
||||||
|
<el-table-column prop="itemName" label="验收项" >
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{row.itemName}}
|
||||||
|
<el-input
|
||||||
|
v-if="row.type === 'input'"
|
||||||
|
v-model="row.remark"
|
||||||
|
placeholder="请输入"
|
||||||
|
size="small"
|
||||||
|
:disabled="readonly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="isQualified" label="合格/不合格" width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-radio-group v-model="row.isQualified" size="small" :disabled="readonly">
|
||||||
|
<el-radio label="1">合格</el-radio>
|
||||||
|
<el-radio label="0">不合格</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
</el-table>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 上传方式 -->
|
||||||
|
<template v-if="form.acceptType === '2'">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="履约验收模版" prop="templateFileIds">
|
||||||
|
<UploadFile
|
||||||
|
v-model="templateFileIdsStr"
|
||||||
|
:limit="1"
|
||||||
|
:data="{ purchaseId: purchaseId || '', fileType: '110' }"
|
||||||
|
upload-file-url="/purchase/purchasingfiles/upload"
|
||||||
|
:disabled="readonly"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 验收小组 -->
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="验收小组" prop="acceptTeam">
|
||||||
|
<div class="team-list">
|
||||||
|
<div v-for="(m, idx) in form.acceptTeam" :key="idx" class="team-row">
|
||||||
|
<el-input v-model="m.name" placeholder="姓名" size="small" style="width:120px" :disabled="readonly" />
|
||||||
|
<el-input v-model="m.deptName" placeholder="部门" size="small" style="width:160px" :disabled="readonly" />
|
||||||
|
<el-button v-if="!readonly && form.acceptTeam.length > 3" type="danger" link size="small" @click="removeTeam(idx)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<el-button v-if="!readonly" type="primary" link size="small" @click="addTeam">+ 增加成员</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="el-form-item__tip">至少3人,且为单数</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入" :disabled="readonly" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: Record<string, any>
|
||||||
|
canFill: boolean
|
||||||
|
readonly?: boolean
|
||||||
|
purchaseId?: string
|
||||||
|
acceptanceItems?: any[]
|
||||||
|
}>(),
|
||||||
|
{ readonly: false, canFill: true, purchaseId: '' }
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const templateFileIdsStr = ref('')
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
acceptType: '1',
|
||||||
|
acceptDate: '',
|
||||||
|
acceptContents: [] as any[],
|
||||||
|
acceptTeam: [
|
||||||
|
{ name: '', deptCode: '', deptName: '' },
|
||||||
|
{ name: '', deptCode: '', deptName: '' },
|
||||||
|
{ name: '', deptCode: '', deptName: '' },
|
||||||
|
] as any[],
|
||||||
|
templateFileIds: [] as string[],
|
||||||
|
remark: '',
|
||||||
|
...props.modelValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => Object.assign(form, val || {}), { deep: true })
|
||||||
|
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||||
|
|
||||||
|
watch(() => props.acceptanceItems, (items) => {
|
||||||
|
if (items?.length && form.acceptContents.length === 0) {
|
||||||
|
form.acceptContents = items.map((it: any) => ({
|
||||||
|
configId: it.id,
|
||||||
|
itemName: it.itemName,
|
||||||
|
type: it.type,
|
||||||
|
isQualified: '1',
|
||||||
|
remark: '',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
watch(templateFileIdsStr, (s) => {
|
||||||
|
const arr = s ? s.split(',').map((x: string) => x.trim()).filter(Boolean) : []
|
||||||
|
if (JSON.stringify(form.templateFileIds) !== JSON.stringify(arr)) {
|
||||||
|
form.templateFileIds = arr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => form.templateFileIds, (arr) => {
|
||||||
|
if (Array.isArray(arr) && arr.length) templateFileIdsStr.value = arr.join(',')
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
const addTeam = () => {
|
||||||
|
form.acceptTeam.push({ name: '', deptCode: '', deptName: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeTeam = (idx: number) => {
|
||||||
|
form.acceptTeam.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules: FormRules = {
|
||||||
|
acceptType: [{ required: true, message: '请选择验收方式', trigger: 'change' }],
|
||||||
|
acceptDate: [{ required: true, message: '请选择验收日期', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => formRef.value?.validate()
|
||||||
|
|
||||||
|
defineExpose({ validate, form })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.accept-batch-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.accept-content-table :deep(.el-table__body td) {
|
||||||
|
padding: 10px 0;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.team-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.team-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.el-form-item__tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px" class="accept-common-form compact-form">
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="项目名称">
|
||||||
|
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="需求部门">
|
||||||
|
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="是否签订合同" prop="hasContract">
|
||||||
|
<el-radio-group v-model="form.hasContract">
|
||||||
|
<el-radio label="0">否</el-radio>
|
||||||
|
<el-radio label="1">是</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" v-if="form.hasContract === '1'">
|
||||||
|
<el-form-item label="合同" prop="contractId">
|
||||||
|
<el-input v-model="form.contractId" placeholder="请选择合同(待对接合同接口)" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="是否分期验收" prop="isInstallment">
|
||||||
|
<el-radio-group v-model="form.isInstallment">
|
||||||
|
<el-radio label="0">否</el-radio>
|
||||||
|
<el-radio label="1">是</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" v-if="form.isInstallment === '1'">
|
||||||
|
<el-form-item label="分期次数" prop="totalPhases">
|
||||||
|
<el-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="供应商名称" prop="supplierName">
|
||||||
|
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="供应商联系人及电话" prop="supplierContact">
|
||||||
|
<el-input v-model="form.supplierContact" placeholder="请输入" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="采购人员" prop="purchaserId">
|
||||||
|
<org-selector v-model:orgList="purchaserList" type="user" :multiple="false" @update:orgList="onPurchaserChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="资产管理员" prop="assetAdminId">
|
||||||
|
<org-selector v-model:orgList="assetAdminList" type="user" :multiple="false" @update:orgList="onAssetAdminChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: Record<string, any>
|
||||||
|
projectName?: string
|
||||||
|
deptName?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const purchaserList = ref<any[]>([])
|
||||||
|
const assetAdminList = ref<any[]>([])
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
hasContract: '0',
|
||||||
|
contractId: '',
|
||||||
|
isInstallment: '0',
|
||||||
|
totalPhases: 1,
|
||||||
|
supplierName: '',
|
||||||
|
supplierContact: '',
|
||||||
|
purchaserId: '',
|
||||||
|
purchaserName: '',
|
||||||
|
assetAdminId: '',
|
||||||
|
assetAdminName: '',
|
||||||
|
...props.modelValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
Object.assign(form, val || {})
|
||||||
|
// 人员选择回显
|
||||||
|
if (form.purchaserId && form.purchaserName) {
|
||||||
|
purchaserList.value = [{ id: form.purchaserId, name: form.purchaserName, type: 'user' }]
|
||||||
|
} else {
|
||||||
|
purchaserList.value = []
|
||||||
|
}
|
||||||
|
if (form.assetAdminId && form.assetAdminName) {
|
||||||
|
assetAdminList.value = [{ id: form.assetAdminId, name: form.assetAdminName, type: 'user' }]
|
||||||
|
} else {
|
||||||
|
assetAdminList.value = []
|
||||||
|
}
|
||||||
|
}, { deep: true, immediate: true })
|
||||||
|
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||||
|
|
||||||
|
const onPurchaserChange = (list: any[]) => {
|
||||||
|
if (list?.length) {
|
||||||
|
const u = list[0]
|
||||||
|
form.purchaserId = u.userId || u.id || ''
|
||||||
|
form.purchaserName = u.name || u.realName || ''
|
||||||
|
} else {
|
||||||
|
form.purchaserId = ''
|
||||||
|
form.purchaserName = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAssetAdminChange = (list: any[]) => {
|
||||||
|
if (list?.length) {
|
||||||
|
const u = list[0]
|
||||||
|
form.assetAdminId = u.userId || u.id || ''
|
||||||
|
form.assetAdminName = u.name || u.realName || ''
|
||||||
|
} else {
|
||||||
|
form.assetAdminId = ''
|
||||||
|
form.assetAdminName = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules: FormRules = {
|
||||||
|
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
|
||||||
|
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => formRef.value?.validate()
|
||||||
|
|
||||||
|
defineExpose({ validate, form })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.accept-common-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.accept-common-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
/* 紧凑表单样式 */
|
||||||
|
.compact-form {
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
padding-right: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner),
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
title="履约验收"
|
||||||
|
width="85%"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
class="purchasing-accept-modal"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<div v-loading="loading" class="modal-body">
|
||||||
|
<div class="main-tabs">
|
||||||
|
<div class="main-tab-nav">
|
||||||
|
<div
|
||||||
|
class="main-tab-item"
|
||||||
|
:class="{ active: mainTab === 'common' }"
|
||||||
|
@click="mainTab = 'common'"
|
||||||
|
>
|
||||||
|
公共信息
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="main-tab-item"
|
||||||
|
:class="{ active: mainTab === 'batch' }"
|
||||||
|
@click="mainTab = 'batch'"
|
||||||
|
>
|
||||||
|
分期验收{{ batches.length > 0 ? ` (${batches.length})` : '' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main-tab-content">
|
||||||
|
<div v-show="mainTab === 'common'" class="tab-content">
|
||||||
|
<AcceptCommonForm
|
||||||
|
ref="commonFormRef"
|
||||||
|
v-model="commonForm"
|
||||||
|
:project-name="applyInfo?.projectName"
|
||||||
|
:dept-name="applyInfo?.deptName"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-show="mainTab === 'batch'" class="tab-content">
|
||||||
|
<div v-if="batches.length > 0">
|
||||||
|
<div class="batch-tabs">
|
||||||
|
<div
|
||||||
|
v-for="b in batches"
|
||||||
|
:key="b.id"
|
||||||
|
class="batch-tab-item"
|
||||||
|
:class="{ active: String(b.batch) === activeTab, disabled: !canEditBatch(b.batch) }"
|
||||||
|
@click="canEditBatch(b.batch) && (activeTab = String(b.batch))"
|
||||||
|
>
|
||||||
|
<span>第{{ b.batch }}期</span>
|
||||||
|
<el-tag v-if="isBatchCompleted(b)" type="success" size="small">已填</el-tag>
|
||||||
|
<el-tag v-else-if="!canEditBatch(b.batch)" type="info" size="small">需先完成上一期</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="batch-panel">
|
||||||
|
<AcceptBatchForm
|
||||||
|
v-for="b in batches"
|
||||||
|
v-show="String(b.batch) === activeTab"
|
||||||
|
:key="b.id"
|
||||||
|
v-model="batchForms[b.batch]"
|
||||||
|
:can-fill="canFillForm"
|
||||||
|
:readonly="false"
|
||||||
|
:purchase-id="String(purchaseId)"
|
||||||
|
:acceptance-items="acceptanceItems"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="tip-box">
|
||||||
|
<el-alert type="info" :closable="false" show-icon>
|
||||||
|
请先在「公共信息」中填写并点击「保存公共配置」,系统将按分期次数自动生成验收批次
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span>
|
||||||
|
<el-button @click="handleClose">关 闭</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="mainTab === 'common' || batches.length === 0"
|
||||||
|
type="primary"
|
||||||
|
@click="saveCommonConfig"
|
||||||
|
:loading="saving"
|
||||||
|
>
|
||||||
|
保存公共配置
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-else-if="mainTab === 'batch' && activeBatchId"
|
||||||
|
type="primary"
|
||||||
|
@click="saveCurrentBatch"
|
||||||
|
:loading="saving"
|
||||||
|
>
|
||||||
|
保存第{{ activeTab }}期
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
||||||
|
import { useMessage } from '/@/hooks/message'
|
||||||
|
import {
|
||||||
|
saveCommonConfig as apiSaveCommonConfig,
|
||||||
|
getCommonConfigWithBatches,
|
||||||
|
updateBatch,
|
||||||
|
canFillForm as apiCanFillForm,
|
||||||
|
getAcceptanceItems,
|
||||||
|
getDetail,
|
||||||
|
} from '/@/api/purchase/purchasingAccept'
|
||||||
|
import AcceptCommonForm from './AcceptCommonForm.vue'
|
||||||
|
import AcceptBatchForm from './AcceptBatchForm.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
const purchaseId = ref<string | number>('')
|
||||||
|
const applyInfo = ref<any>(null)
|
||||||
|
const rowProjectType = ref<string>('A')
|
||||||
|
const canFillForm = ref(true)
|
||||||
|
const acceptanceItems = ref<any[]>([])
|
||||||
|
const batches = ref<any[]>([])
|
||||||
|
const mainTab = ref('common')
|
||||||
|
const activeTab = ref('1')
|
||||||
|
const commonFormRef = ref()
|
||||||
|
const commonForm = reactive<Record<string, any>>({})
|
||||||
|
const batchForms = reactive<Record<number, any>>({})
|
||||||
|
|
||||||
|
const activeBatchId = computed(() => {
|
||||||
|
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||||
|
return b?.id || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const canEditBatch = (batch: number) => {
|
||||||
|
if (batch === 1) return true
|
||||||
|
for (let i = 1; i < batch; i++) {
|
||||||
|
if (!isBatchCompletedByIdx(i)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBatchCompleted = (b: any) => {
|
||||||
|
return !!(b.acceptType && b.acceptDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBatchCompletedByIdx = (batch: number) => {
|
||||||
|
const b = batches.value.find((x: any) => x.batch === batch)
|
||||||
|
return b ? isBatchCompleted(b) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
if (!purchaseId.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [configRes, canFillRes] = await Promise.all([
|
||||||
|
getCommonConfigWithBatches(String(purchaseId.value)),
|
||||||
|
apiCanFillForm(String(purchaseId.value)),
|
||||||
|
])
|
||||||
|
const config = configRes?.data
|
||||||
|
canFillForm.value = !!canFillRes?.data
|
||||||
|
|
||||||
|
if (config?.common) {
|
||||||
|
Object.assign(commonForm, {
|
||||||
|
hasContract: config.common.hasContract || '0',
|
||||||
|
contractId: config.common.contractId || '',
|
||||||
|
isInstallment: config.common.isInstallment || '0',
|
||||||
|
totalPhases: config.common.totalPhases || 1,
|
||||||
|
supplierName: config.common.supplierName || '',
|
||||||
|
supplierContact: config.common.supplierContact || '',
|
||||||
|
purchaserId: config.common.purchaserId || '',
|
||||||
|
purchaserName: config.common.purchaserName || '',
|
||||||
|
assetAdminId: config.common.assetAdminId || '',
|
||||||
|
assetAdminName: config.common.assetAdminName || '',
|
||||||
|
})
|
||||||
|
applyInfo.value = config.common
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectType = applyInfo.value?.projectType || rowProjectType.value || 'A'
|
||||||
|
const typeMap: Record<string, string> = { A: 'A', B: 'B', C: 'C' }
|
||||||
|
const at = typeMap[projectType] || 'A'
|
||||||
|
const itemsRes = await getAcceptanceItems(at)
|
||||||
|
acceptanceItems.value = itemsRes?.data || []
|
||||||
|
|
||||||
|
if (config?.batches?.length) {
|
||||||
|
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0))
|
||||||
|
activeTab.value = String(batches.value[0]?.batch || '1')
|
||||||
|
mainTab.value = 'batch'
|
||||||
|
for (const b of batches.value) {
|
||||||
|
if (!batchForms[b.batch]) batchForms[b.batch] = {}
|
||||||
|
}
|
||||||
|
await loadBatchDetails()
|
||||||
|
} else {
|
||||||
|
batches.value = []
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
useMessage().error(e?.msg || '加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadBatchDetails = async () => {
|
||||||
|
for (const b of batches.value) {
|
||||||
|
try {
|
||||||
|
const res = await getDetail(String(purchaseId.value), b.batch)
|
||||||
|
const d = res?.data
|
||||||
|
if (d?.accept) {
|
||||||
|
const itemMap = (acceptanceItems.value || []).reduce((acc: any, it: any) => {
|
||||||
|
acc[it.id] = it
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
batchForms[b.batch] = {
|
||||||
|
acceptType: d.accept.acceptType || '1',
|
||||||
|
acceptDate: d.accept.acceptDate || '',
|
||||||
|
remark: d.accept.remark || '',
|
||||||
|
templateFileIds: d.accept.templateFileIds || [],
|
||||||
|
acceptContents: (d.contents || []).map((c: any) => {
|
||||||
|
const cfg = itemMap[c.configId]
|
||||||
|
return {
|
||||||
|
configId: c.configId,
|
||||||
|
itemName: cfg?.itemName || '',
|
||||||
|
type: cfg?.type,
|
||||||
|
isQualified: c.isQualified || '1',
|
||||||
|
remark: c.remark || '',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
acceptTeam: (d.team || []).map((t: any) => ({
|
||||||
|
name: t.name,
|
||||||
|
deptCode: t.deptCode,
|
||||||
|
deptName: t.deptName,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
if (batchForms[b.batch].acceptTeam.length < 3) {
|
||||||
|
while (batchForms[b.batch].acceptTeam.length < 3) {
|
||||||
|
batchForms[b.batch].acceptTeam.push({ name: '', deptCode: '', deptName: '' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (acceptanceItems.value.length && (!batchForms[b.batch].acceptContents || batchForms[b.batch].acceptContents.length === 0)) {
|
||||||
|
batchForms[b.batch].acceptContents = acceptanceItems.value.map((it: any) => ({
|
||||||
|
configId: it.id,
|
||||||
|
itemName: it.itemName,
|
||||||
|
type: it.type,
|
||||||
|
isQualified: '1',
|
||||||
|
remark: '',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveCommonConfig = async () => {
|
||||||
|
const valid = await commonFormRef.value?.validate?.().catch(() => false)
|
||||||
|
if (!valid) return
|
||||||
|
if (commonForm.isInstallment === '1' && (!commonForm.totalPhases || commonForm.totalPhases < 1)) {
|
||||||
|
useMessage().error('请填写分期次数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await apiSaveCommonConfig({
|
||||||
|
purchaseId: String(purchaseId.value),
|
||||||
|
hasContract: commonForm.hasContract,
|
||||||
|
contractId: commonForm.contractId,
|
||||||
|
isInstallment: commonForm.isInstallment,
|
||||||
|
totalPhases: commonForm.isInstallment === '1' ? commonForm.totalPhases : 1,
|
||||||
|
supplierName: commonForm.supplierName,
|
||||||
|
supplierContact: commonForm.supplierContact,
|
||||||
|
purchaserId: commonForm.purchaserId,
|
||||||
|
purchaserName: commonForm.purchaserName,
|
||||||
|
assetAdminId: commonForm.assetAdminId,
|
||||||
|
assetAdminName: commonForm.assetAdminName,
|
||||||
|
})
|
||||||
|
useMessage().success('保存成功')
|
||||||
|
await loadData()
|
||||||
|
} catch (e: any) {
|
||||||
|
useMessage().error(e?.msg || '保存失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveCurrentBatch = async () => {
|
||||||
|
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||||
|
if (!b?.id) return
|
||||||
|
const form = batchForms[Number(activeTab.value)]
|
||||||
|
if (!form) return
|
||||||
|
|
||||||
|
const team = (form.acceptTeam || []).filter((m: any) => m?.name)
|
||||||
|
if (team.length < 3 || team.length % 2 === 0) {
|
||||||
|
useMessage().error('验收小组至少3人且为单数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await updateBatch({
|
||||||
|
id: b.id,
|
||||||
|
purchaseId: String(purchaseId.value),
|
||||||
|
acceptType: form.acceptType,
|
||||||
|
acceptDate: form.acceptDate,
|
||||||
|
remark: form.remark,
|
||||||
|
templateFileIds: form.templateFileIds || [],
|
||||||
|
acceptContents: form.acceptContents || [],
|
||||||
|
acceptTeam: team,
|
||||||
|
})
|
||||||
|
useMessage().success('保存成功')
|
||||||
|
await loadData()
|
||||||
|
} catch (e: any) {
|
||||||
|
useMessage().error(e?.msg || '保存失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false
|
||||||
|
emit('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (row: any) => {
|
||||||
|
purchaseId.value = row?.id ?? ''
|
||||||
|
rowProjectType.value = row?.projectType || 'A'
|
||||||
|
visible.value = true
|
||||||
|
batches.value = []
|
||||||
|
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)])
|
||||||
|
nextTick(() => loadData())
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-body {
|
||||||
|
padding: 0;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.main-tab-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
.main-tab-item {
|
||||||
|
padding: 12px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.main-tab-item:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.main-tab-item.active {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.main-tab-content {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
.tab-content {
|
||||||
|
min-height: 200px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tip-box {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
.batch-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.batch-tab-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.batch-tab-item:hover:not(.disabled) {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
.batch-tab-item.active {
|
||||||
|
background: var(--el-color-primary);
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.batch-tab-item.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.batch-panel {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" fixed="right" width="300">
|
<el-table-column label="操作" align="center" fixed="right" width="360">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
icon="View"
|
icon="View"
|
||||||
@@ -216,6 +216,13 @@
|
|||||||
@click="handleDelete(scope.row)">
|
@click="handleDelete(scope.row)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
icon="DocumentChecked"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="handleAccept(scope.row)">
|
||||||
|
履约验收
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -257,6 +264,9 @@
|
|||||||
ref="formDialogRef"
|
ref="formDialogRef"
|
||||||
:dict-data="dictData"
|
:dict-data="dictData"
|
||||||
@refresh="getDataList" />
|
@refresh="getDataList" />
|
||||||
|
|
||||||
|
<!-- 履约验收弹窗 -->
|
||||||
|
<PurchasingAcceptModal ref="acceptModalRef" @refresh="getDataList" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -268,10 +278,11 @@ import { getPage, delObj } from "/@/api/finance/purchasingrequisition";
|
|||||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||||
import { getDicts } from '/@/api/admin/dict';
|
import { getDicts } from '/@/api/admin/dict';
|
||||||
import { getTree } from '/@/api/finance/purchasingcategory';
|
import { getTree } from '/@/api/finance/purchasingcategory';
|
||||||
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning } from '@element-plus/icons-vue'
|
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
// 引入组件
|
// 引入组件
|
||||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||||
|
const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue'));
|
||||||
|
|
||||||
// 字典数据和品目树数据
|
// 字典数据和品目树数据
|
||||||
const dictData = ref({
|
const dictData = ref({
|
||||||
@@ -288,6 +299,7 @@ const dictData = ref({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const formDialogRef = ref()
|
const formDialogRef = ref()
|
||||||
|
const acceptModalRef = ref()
|
||||||
const searchFormRef = ref()
|
const searchFormRef = ref()
|
||||||
const showSearch = ref(true)
|
const showSearch = ref(true)
|
||||||
const showAddIframe = ref(false)
|
const showAddIframe = ref(false)
|
||||||
@@ -361,6 +373,13 @@ const handleIframeMessage = (event: MessageEvent) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 履约验收
|
||||||
|
*/
|
||||||
|
const handleAccept = (row: any) => {
|
||||||
|
acceptModalRef.value?.open(row);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除当前行
|
* 删除当前行
|
||||||
* @param row - 当前行数据
|
* @param row - 当前行数据
|
||||||
|
|||||||
Reference in New Issue
Block a user