- 移除是否签订合同手动选择,由系统自动判断 - 合同信息单独分栏显示,展示合同名称/编号/金额/供应商 - 已签订合同以绿色卡片展示 - 未签订合同显示警告提示 - 合同审批中显示错误提示,禁止操作 - 供应商名称合并到合同信息中展示 - 未签订合同时才显示成交金额和供应商输入
403 lines
11 KiB
Vue
403 lines
11 KiB
Vue
<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-row>
|
||
|
||
<el-divider content-position="left">
|
||
<span class="divider-title">合同信息</span>
|
||
</el-divider>
|
||
|
||
<div v-if="contractLoading" class="contract-loading">
|
||
<el-icon class="is-loading"><Loading /></el-icon>
|
||
<span>正在查询合同...</span>
|
||
</div>
|
||
|
||
<div v-else-if="form.hasContract === '1' && contractFlowStatus === '1'" class="contract-card success">
|
||
<div class="contract-card-header">
|
||
<el-icon><CircleCheckFilled /></el-icon>
|
||
<span>已签订合同</span>
|
||
</div>
|
||
<el-row :gutter="24">
|
||
<el-col :span="8">
|
||
<div class="info-item">
|
||
<span class="info-label">合同名称</span>
|
||
<span class="info-value">{{ form.contractName || form.contractNo || '-' }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<div class="info-item">
|
||
<span class="info-label">合同编号</span>
|
||
<span class="info-value">{{ form.contractNo || '-' }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<div class="info-item">
|
||
<span class="info-label">合同金额</span>
|
||
<span class="info-value">{{ form.contractMoney ? `${form.contractMoney}元` : '-' }}</span>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<div class="info-item">
|
||
<span class="info-label">供应商名称</span>
|
||
<span class="info-value">{{ form.supplierName || '-' }}</span>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<el-alert v-else-if="form.hasContract === '1' && contractFlowStatus !== '1'" type="error" :closable="false" show-icon class="mb20">
|
||
<template #title>
|
||
<span>合同正在流程审批中,无法进行履约操作</span>
|
||
</template>
|
||
</el-alert>
|
||
|
||
<el-alert v-else type="warning" :closable="false" show-icon class="mb20">
|
||
<template #title>
|
||
<span>未签订合同</span>
|
||
</template>
|
||
</el-alert>
|
||
|
||
<el-divider content-position="left">
|
||
<span class="divider-title">验收信息</span>
|
||
</el-divider>
|
||
|
||
<el-row :gutter="24">
|
||
<el-col :span="8" class="mb20">
|
||
<el-form-item label="是否分期验收" prop="isInstallment">
|
||
<el-radio-group v-model="form.isInstallment" :disabled="!canEdit">
|
||
<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="!canEdit" />
|
||
</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="!canEdit"
|
||
@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="!canEdit"
|
||
@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>
|
||
<el-input-number v-model="form.transactionAmount" :min="0" :precision="2" placeholder="请输入成交金额" style="width: 100%" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="8" class="mb20" v-if="form.hasContract === '0'">
|
||
<el-form-item label="供应商名称" prop="supplierName">
|
||
<el-input v-model="form.supplierName" placeholder="请输入供应商名称" clearable />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, watch, computed } from 'vue';
|
||
import type { FormInstance, FormRules } from 'element-plus';
|
||
import { Loading, CircleCheckFilled } from '@element-plus/icons-vue';
|
||
import { searchTeachers } from '/@/api/purchase/purchasingrequisition';
|
||
import { useMessage } from '/@/hooks/message';
|
||
|
||
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 contractFlowStatus = ref('');
|
||
|
||
const purchaserOptions = ref<any[]>([]);
|
||
const purchaserLoading = ref(false);
|
||
|
||
const assetAdminOptions = ref<any[]>([]);
|
||
const assetAdminLoading = ref(false);
|
||
|
||
const form = reactive({
|
||
hasContract: '0',
|
||
contractId: '',
|
||
contractName: '',
|
||
contractNo: '',
|
||
contractMoney: null as number | null,
|
||
isInstallment: '0',
|
||
totalPhases: 1,
|
||
projectName: '',
|
||
deptName: '',
|
||
supplierName: '',
|
||
purchaserId: '',
|
||
purchaserName: '',
|
||
assetAdminId: '',
|
||
assetAdminName: '',
|
||
transactionAmount: null as number | null,
|
||
});
|
||
|
||
const canEdit = computed(() => {
|
||
return form.hasContract !== '1' || contractFlowStatus.value === '1';
|
||
});
|
||
|
||
const syncFormFromModel = (val: Record<string, any> | undefined) => {
|
||
if (!val) return;
|
||
form.hasContract = val.hasContract || '0';
|
||
form.contractId = val.contractId || '';
|
||
form.contractName = val.contractName || '';
|
||
form.contractNo = val.contractNo || '';
|
||
form.contractMoney = val.contractMoney || null;
|
||
contractFlowStatus.value = val.contractFlowStatus || '';
|
||
form.isInstallment = val.isInstallment || '0';
|
||
form.totalPhases = val.totalPhases || 1;
|
||
form.supplierName = val.supplierName || '';
|
||
form.purchaserId = val.purchaserId || '';
|
||
form.purchaserName = val.purchaserName || '';
|
||
form.assetAdminId = val.assetAdminId || '';
|
||
form.assetAdminName = val.assetAdminName || '';
|
||
form.transactionAmount = val.transactionAmount || null;
|
||
|
||
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 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);
|
||
}
|
||
);
|
||
|
||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true });
|
||
|
||
const rules: FormRules = {
|
||
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' && contractFlowStatus.value !== '1') {
|
||
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;
|
||
}
|
||
.divider-title {
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
.contract-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 40px 0;
|
||
color: #409eff;
|
||
}
|
||
.contract-card {
|
||
border-radius: 8px;
|
||
padding: 16px 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.contract-card.success {
|
||
background: linear-gradient(135deg, #f0f9eb 0%, #e1f3d8 100%);
|
||
border: 1px solid #c2e7b0;
|
||
}
|
||
.contract-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #67c23a;
|
||
}
|
||
.contract-card-header .el-icon {
|
||
font-size: 20px;
|
||
}
|
||
.info-item {
|
||
margin-bottom: 12px;
|
||
}
|
||
.info-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
.info-label {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 4px;
|
||
}
|
||
.info-value {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
:deep(.el-divider__text) {
|
||
background-color: #fff;
|
||
padding: 0 10px;
|
||
}
|
||
:deep(.el-divider) {
|
||
margin: 24px 0;
|
||
}
|
||
</style> |