Files
school-developer/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue
吴红兵 8bc778345b feat(purchase): 履约验收优化界面,移除是否签订合同选择
- 移除是否签订合同手动选择,由系统自动判断
- 合同信息单独分栏显示,展示合同名称/编号/金额/供应商
- 已签订合同以绿色卡片展示
- 未签订合同显示警告提示
- 合同审批中显示错误提示,禁止操作
- 供应商名称合并到合同信息中展示
- 未签订合同时才显示成交金额和供应商输入
2026-03-15 11:13:10 +08:00

403 lines
11 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-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>