feat(purchase): 履约验收自动显示合同信息和供应商信息

- 选择“是否签订合同”为“是”时,自动查询采购合同
- 合同流程已完成时,自动显示合同信息和供应商信息
- 合同不存在或流程未完成时,提示无法进行履约操作
- 移除合同下拉选择,改为只读显示
- 添加合同状态常量(运行中/已完成/已作废)
This commit is contained in:
吴红兵
2026-03-15 10:53:43 +08:00
parent 2114f79fb1
commit 90ae916b93

View File

@@ -13,30 +13,43 @@
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="是否签订合同" prop="hasContract">
<el-radio-group v-model="form.hasContract">
<el-radio-group v-model="form.hasContract" :disabled="contractStatus === 'pending'">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8" class="mb20" v-if="form.hasContract === '1'">
<el-form-item label="合同" prop="contractId">
<el-select
v-model="form.contractId"
placeholder="请选择合同"
clearable
filterable
style="width: 100%"
:loading="contractLoading"
@visible-change="onContractSelectVisibleChange"
>
<el-option v-for="item in contractOptions" :key="item.id" :label="item.contractName || item.contractNo || item.id" :value="item.id" />
</el-select>
<el-form-item label="合同信息">
<div v-if="contractLoading" class="contract-info-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span>正在查询合同...</span>
</div>
<div v-else-if="contractStatus === 'completed' && currentContract" class="contract-info-display">
<div class="contract-item">
<span class="label">合同名称</span>
<span class="value">{{ currentContract.contractName || currentContract.contractNo || '-' }}</span>
</div>
<div class="contract-item">
<span class="label">合同金额</span>
<span class="value">{{ currentContract.money ? `${currentContract.money}` : '-' }}</span>
</div>
</div>
<el-alert v-else-if="contractStatus === 'none'" type="warning" :closable="false" show-icon>
<template #title>
<span>未找到关联合同无法进行履约操作</span>
</template>
</el-alert>
<el-alert v-else-if="contractStatus === 'pending'" type="error" :closable="false" show-icon>
<template #title>
<span>合同正在流程审批中无法进行履约操作</span>
</template>
</el-alert>
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="是否分期验收" prop="isInstallment">
<el-radio-group v-model="form.isInstallment">
<el-radio-group v-model="form.isInstallment" :disabled="contractStatus === 'pending'">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
@@ -44,12 +57,17 @@
</el-col>
<el-col :span="8" class="mb20" 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-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" :disabled="contractStatus === 'pending'" />
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="供应商名称" prop="supplierName">
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
<el-input
v-model="form.supplierName"
:placeholder="form.hasContract === '1' ? '自动带出' : '请输入供应商名称'"
:readonly="form.hasContract === '1'"
:disabled="contractStatus === 'pending'"
/>
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
@@ -64,6 +82,7 @@
:remote-method="searchAssetAdmin"
:loading="assetAdminLoading"
style="width: 100%"
:disabled="contractStatus === 'pending'"
@change="onAssetAdminChange"
>
<el-option
@@ -91,6 +110,7 @@
:remote-method="searchPurchaser"
:loading="purchaserLoading"
style="width: 100%"
:disabled="contractStatus === 'pending'"
@change="onPurchaserChange"
>
<el-option
@@ -118,30 +138,34 @@
<script setup lang="ts">
import { ref, reactive, watch, onMounted } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import { Loading } from '@element-plus/icons-vue';
import { getContracts, searchTeachers } from '/@/api/purchase/purchasingrequisition';
import { useMessage } from '/@/hooks/message';
const CONTRACT_FLOW_STATUS = {
RUNNING: '0',
COMPLETED: '1',
CANCELLED: '2',
};
const props = defineProps<{
modelValue: Record<string, any>;
projectName?: string;
deptName?: string;
/** 采购申请ID用于拉取合同列表 */
purchaseId?: string | number;
/** 每次打开弹窗时变化,用于强制重置内部 form */
resetKey?: number;
}>();
const emit = defineEmits(['update:modelValue']);
const formRef = ref<FormInstance>();
const contractOptions = ref<any[]>([]);
const contractLoading = ref(false);
const contractLoaded = ref(false);
const currentContract = ref<any>(null);
const contractStatus = ref<'none' | 'pending' | 'completed' | ''>('');
// 采购人员相关
const purchaserOptions = ref<any[]>([]);
const purchaserLoading = ref(false);
// 资产管理员相关
const assetAdminOptions = ref<any[]>([]);
const assetAdminLoading = ref(false);
@@ -157,13 +181,12 @@ const form = reactive({
purchaserName: '',
assetAdminId: '',
assetAdminName: '',
transactionAmount: null,
transactionAmount: null as number | null,
...props.modelValue,
});
const syncFormFromModel = (val: Record<string, any> | undefined) => {
Object.assign(form, val || {});
// 加载已选人员信息用于回显
if (form.purchaserId && form.purchaserName) {
purchaserOptions.value = [{ teacherNo: form.purchaserId, realName: form.purchaserName, name: form.purchaserName }];
}
@@ -172,33 +195,52 @@ const syncFormFromModel = (val: Record<string, any> | undefined) => {
}
};
const loadContractOptions = async () => {
if (contractLoaded.value || contractLoading.value) return;
if (form.hasContract !== '1') return;
const loadContractInfo = async () => {
if (!props.purchaseId) {
contractStatus.value = '';
currentContract.value = null;
return;
}
contractLoading.value = true;
contractStatus.value = '';
currentContract.value = null;
try {
const res = await getContracts(props.purchaseId ? { id: props.purchaseId } : {});
const res = await getContracts({ id: props.purchaseId });
const list = res?.data;
contractOptions.value = Array.isArray(list) ? list : [];
contractLoaded.value = true;
// 回显时:列表中含当前合同,用其供应商名称填充(若尚未有值)
if (form.contractId) {
const c = contractOptions.value.find((it: any) => it.id === form.contractId);
if (c?.supplierName) form.supplierName = c.supplierName;
if (Array.isArray(list) && list.length > 0) {
const contract = list[0];
currentContract.value = contract;
form.contractId = contract.id;
if (contract.flowStatus === CONTRACT_FLOW_STATUS.COMPLETED) {
contractStatus.value = 'completed';
form.supplierName = contract.supplierName || '';
} else if (contract.flowStatus === CONTRACT_FLOW_STATUS.RUNNING) {
contractStatus.value = 'pending';
form.supplierName = '';
} else {
contractStatus.value = 'pending';
form.supplierName = '';
}
} else {
contractStatus.value = 'none';
currentContract.value = null;
form.contractId = '';
form.supplierName = '';
}
} catch (_) {
contractOptions.value = [];
} catch (e: any) {
contractStatus.value = 'none';
currentContract.value = null;
form.contractId = '';
form.supplierName = '';
} finally {
contractLoading.value = false;
}
};
const onContractSelectVisibleChange = (visible: boolean) => {
if (visible && form.hasContract === '1' && contractOptions.value.length === 0) {
loadContractOptions();
}
};
const searchPurchaser = async (query: string) => {
if (!query) {
purchaserOptions.value = [];
@@ -261,57 +303,38 @@ watch(
() => props.modelValue,
(val) => {
syncFormFromModel(val);
// 回显已有合同ID时主动加载合同列表以便下拉显示合同名称后端已排除"其他申请"的合同,当前申请合同会在列表中)
if (form.hasContract === '1' && form.contractId && props.purchaseId && !contractLoaded.value && !contractLoading.value) {
loadContractOptions();
}
},
{ deep: true, immediate: true }
);
// resetKey 变化时强制用 modelValue 覆盖内部 form并重置合同列表以便重新拉取
watch(
() => props.resetKey,
() => {
syncFormFromModel(props.modelValue);
contractLoaded.value = false;
contractOptions.value = [];
contractStatus.value = '';
currentContract.value = null;
}
);
watch(form, () => emit('update:modelValue', { ...form }), { deep: true });
watch(
() => form.hasContract,
(val) => {
if (val === '1') {
contractLoaded.value = false;
loadContractOptions();
loadContractInfo();
} else {
contractOptions.value = [];
contractLoaded.value = false;
contractStatus.value = '';
currentContract.value = null;
form.contractId = '';
}
// hasContract 变化时触发 transactionAmount 校验
formRef.value?.validateField('transactionAmount');
}
);
// 选择合同后,自动带出合同供应商名称
watch(
() => form.contractId,
(val) => {
if (!val) {
form.supplierName = '';
return;
}
const c = contractOptions.value.find((it: any) => it.id === val);
if (c && c.supplierName) {
form.supplierName = c.supplierName;
}
}
);
onMounted(() => {
if (form.hasContract === '1') {
loadContractOptions();
loadContractInfo();
}
});
@@ -322,7 +345,6 @@ const rules: FormRules = {
transactionAmount: [
{
validator: (rule: any, value: any, callback: any) => {
// 未签订合同时,成交金额为必填
if (form.hasContract === '0' && (value === null || value === undefined || value === '')) {
callback(new Error('未签订合同时,成交金额为必填'));
} else {
@@ -334,7 +356,17 @@ const rules: FormRules = {
],
};
const validate = () => formRef.value?.validate();
const validate = async () => {
if (form.hasContract === '1' && contractStatus.value === 'pending') {
useMessage().error('合同正在流程审批中,无法进行履约操作');
return false;
}
if (form.hasContract === '1' && contractStatus.value === 'none') {
useMessage().error('未找到关联合同,无法进行履约操作');
return false;
}
return formRef.value?.validate();
};
defineExpose({ validate, form });
</script>
@@ -348,4 +380,28 @@ defineExpose({ validate, form });
color: #999;
margin-top: 4px;
}
</style>
.contract-info-loading {
display: flex;
align-items: center;
gap: 8px;
color: #409eff;
}
.contract-info-display {
background: #f5f7fa;
border-radius: 4px;
padding: 12px;
}
.contract-item {
display: flex;
align-items: center;
line-height: 1.8;
}
.contract-item .label {
color: #606266;
min-width: 80px;
}
.contract-item .value {
color: #303133;
font-weight: 500;
}
</style>