Files
school-developer/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue
吴红兵 90ae916b93 feat(purchase): 履约验收自动显示合同信息和供应商信息
- 选择“是否签订合同”为“是”时,自动查询采购合同
- 合同流程已完成时,自动显示合同信息和供应商信息
- 合同不存在或流程未完成时,提示无法进行履约操作
- 移除合同下拉选择,改为只读显示
- 添加合同状态常量(运行中/已完成/已作废)
2026-03-15 10:53:43 +08:00

407 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
<el-row :gutter="24">
<el-col :span="8" class="mb20">
<el-form-item label="项目名称">
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" disabled />
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="需求部门">
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" disabled />
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="是否签订合同" prop="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="合同信息">
<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" :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.isInstallment === '1'">
<el-form-item label="分期次数" prop="totalPhases">
<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="form.hasContract === '1' ? '自动带出' : '请输入供应商名称'"
:readonly="form.hasContract === '1'"
:disabled="contractStatus === 'pending'"
/>
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="资产管理员" prop="assetAdminId">
<el-select
v-model="form.assetAdminId"
placeholder="请输入姓名或工号搜索"
filterable
remote
clearable
reserve-keyword
:remote-method="searchAssetAdmin"
:loading="assetAdminLoading"
style="width: 100%"
:disabled="contractStatus === 'pending'"
@change="onAssetAdminChange"
>
<el-option
v-for="item in assetAdminOptions"
:key="item.teacherNo"
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
:value="item.teacherNo"
>
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
<span style="color: #999; font-size: 12px; margin-left: 8px">{{ item.teacherNo }}</span>
</el-option>
</el-select>
<div class="field-note">如入固定资产必填</div>
</el-form-item>
</el-col>
<el-col :span="8" class="mb20">
<el-form-item label="采购人员" prop="purchaserId">
<el-select
v-model="form.purchaserId"
placeholder="请输入姓名或工号搜索"
filterable
remote
clearable
reserve-keyword
:remote-method="searchPurchaser"
:loading="purchaserLoading"
style="width: 100%"
:disabled="contractStatus === 'pending'"
@change="onPurchaserChange"
>
<el-option
v-for="item in purchaserOptions"
:key="item.teacherNo"
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
:value="item.teacherNo"
>
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
<span style="color: #999; font-size: 12px; margin-left: 8px">{{ item.teacherNo }}</span>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" class="mb20" v-if="form.hasContract === '0'">
<el-form-item label="成交金额" prop="transactionAmount" :required="form.hasContract === '0'">
<el-input-number v-model="form.transactionAmount" :min="0" :precision="2" placeholder="请输入成交金额" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<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;
purchaseId?: string | number;
resetKey?: number;
}>();
const emit = defineEmits(['update:modelValue']);
const formRef = ref<FormInstance>();
const contractLoading = 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);
const form = reactive({
hasContract: '0',
contractId: '',
isInstallment: '0',
totalPhases: 1,
projectName: '',
deptName: '',
supplierName: '',
purchaserId: '',
purchaserName: '',
assetAdminId: '',
assetAdminName: '',
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 }];
}
if (form.assetAdminId && form.assetAdminName) {
assetAdminOptions.value = [{ teacherNo: form.assetAdminId, realName: form.assetAdminName, name: form.assetAdminName }];
}
};
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({ id: props.purchaseId });
const list = res?.data;
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 (e: any) {
contractStatus.value = 'none';
currentContract.value = null;
form.contractId = '';
form.supplierName = '';
} finally {
contractLoading.value = false;
}
};
const searchPurchaser = async (query: string) => {
if (!query) {
purchaserOptions.value = [];
return;
}
purchaserLoading.value = true;
try {
const res = await searchTeachers(query);
purchaserOptions.value = res?.data || [];
} catch (_) {
purchaserOptions.value = [];
} finally {
purchaserLoading.value = false;
}
};
const searchAssetAdmin = async (query: string) => {
if (!query) {
assetAdminOptions.value = [];
return;
}
assetAdminLoading.value = true;
try {
const res = await searchTeachers(query);
assetAdminOptions.value = res?.data || [];
} catch (_) {
assetAdminOptions.value = [];
} finally {
assetAdminLoading.value = false;
}
};
const onPurchaserChange = (teacherNo: string) => {
if (!teacherNo) {
form.purchaserId = '';
form.purchaserName = '';
return;
}
const selected = purchaserOptions.value.find((item: any) => item.teacherNo === teacherNo);
if (selected) {
form.purchaserId = selected.teacherNo;
form.purchaserName = selected.realName || selected.name;
}
};
const onAssetAdminChange = (teacherNo: string) => {
if (!teacherNo) {
form.assetAdminId = '';
form.assetAdminName = '';
return;
}
const selected = assetAdminOptions.value.find((item: any) => item.teacherNo === teacherNo);
if (selected) {
form.assetAdminId = selected.teacherNo;
form.assetAdminName = selected.realName || selected.name;
}
};
watch(
() => props.modelValue,
(val) => {
syncFormFromModel(val);
},
{ deep: true, immediate: true }
);
watch(
() => props.resetKey,
() => {
syncFormFromModel(props.modelValue);
contractStatus.value = '';
currentContract.value = null;
}
);
watch(form, () => emit('update:modelValue', { ...form }), { deep: true });
watch(
() => form.hasContract,
(val) => {
if (val === '1') {
loadContractInfo();
} else {
contractStatus.value = '';
currentContract.value = null;
form.contractId = '';
}
formRef.value?.validateField('transactionAmount');
}
);
onMounted(() => {
if (form.hasContract === '1') {
loadContractInfo();
}
});
const rules: FormRules = {
hasContract: [{ required: true, message: '请选择是否签订合同', trigger: 'change' }],
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
transactionAmount: [
{
validator: (rule: any, value: any, callback: any) => {
if (form.hasContract === '0' && (value === null || value === undefined || value === '')) {
callback(new Error('未签订合同时,成交金额为必填'));
} else {
callback();
}
},
trigger: 'blur',
},
],
};
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>
<style scoped>
.mb20 {
margin-bottom: 20px;
}
.field-note {
font-size: 12px;
color: #999;
margin-top: 4px;
}
.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>