You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

403 lines
12 KiB
Vue

<template>
<div class="budget-table">
<!-- 标题 -->
<div class="title">项目经费预算表</div>
<!-- 表单 -->
<el-form :model="budgetForm" label-width="150px" class="budget-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目名称">
<el-input v-model="budgetForm.projectName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目号">
<el-input v-model="budgetForm.projectCode" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目经理">
<el-select v-model="budgetForm.managerId" filterable placeholder="请选择项目经理">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品经理">
<el-select v-model="budgetForm.productManagerId" filterable placeholder="请选择产品经理">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同额">
<el-input-number v-model="budgetForm.contractAmount" :min="0" :precision="2" @change="contractAmountChange" style="width:100%;text-align: left"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同净额">
<el-input-number v-model="budgetForm.netContractAmount" :min="0" :precision="2" @change="computedAmountAndRate" style="width:100%;text-align: left"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预算成本">
<div class="custom-display">
{{ budgetForm.budgetCost || '0.00' }}
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目毛利率">
<div class="custom-display">{{ budgetForm.budgetRate }}%</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="降成本后预算成本">
<div class="custom-display">
{{ budgetForm.reduceBudgetCost }}
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="降成本后预算毛利率">
<div class="custom-display">{{ budgetForm.reduceBudgetRate }}%</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目预算期间">
<el-input v-model="budgetForm.duringOperation" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位">
<el-input v-model="budgetForm.unitName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 表格 -->
<el-table :data="budgetDetailData" border style="width: 100%" :summary-method="getSummaries" show-summary>
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="budgetItem" label="预算科目名称" min-width="150" />
<el-table-column prop="budgetCost" label="预算成本" width="180" align="right">
<template #default="scope">
<el-input-number
v-if="scope.row.budgetItem === '运输费' || scope.row.budgetItem === '售后服务费' || scope.row.budgetItem === '其他成本'"
v-model="scope.row.budgetCost"
precision="2"
/>
<span v-else>{{ formatNumber(scope.row.budgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="reduceBudgetCost" label="降成本后预算成本" width="180" align="right">
<template #default="scope">
<el-input-number
v-if="scope.row.budgetItem === '运输费' || scope.row.budgetItem === '售后服务费' || scope.row.budgetItem === '其他成本'"
v-model="scope.row.reduceBudgetCost"
precision="2"
/>
<span v-else>{{ formatNumber(scope.row.reduceBudgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="referenceProjectName" label="参考项目" width="280">
<template #default="scope">
<el-input v-model="scope.row.referenceProjectName" />
</template>
</el-table-column>
</el-table>
<!-- 备注 -->
<div class="note">注:以上成本均为不含税价格</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';
import { UserVO } from '@/api/system/user/types';
import { getUserList } from '@/api/system/user';
//
const budgetForm = reactive({
budgetId: undefined,
projectId: undefined,
approvedFlag: undefined,
budgetVersion: undefined,
projectCategory: undefined,
projectCode: undefined,
projectName: undefined,
managerId: undefined,
managerName: undefined,
productManagerId: undefined,
productManagerName: undefined,
approveUserId: undefined,
approveUserName: undefined,
contractAmount: undefined,
netContractAmount: undefined,
budgetCost: undefined,
budgetRate: undefined,
reduceBudgetCost: undefined,
reduceBudgetRate: undefined,
duringOperation: undefined,
unitId: undefined,
unitName: '',
exportFlag: undefined,
budgetStatus: undefined,
flowStatus: undefined,
contractId: undefined,
remark: undefined
});
//
const budgetDetailData = ref([
{
sortOrder: 1,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 2,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 3,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 4,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 5,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 6,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 7,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
},
{
sortOrder: 8,
budgetItem: '',
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName: ''
}
]);
//
const formatNumber = (value: number) => {
if (!value) return '0.00';
return parseFloat(value).toFixed(2);
};
// 格式化数字,元转换为万元
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
return (parseFloat(value) / 10000).toFixed(2);
};
// 合计方法
const getSummaries = (param: any) => {
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 1) {
sums[index] = '合计';
return;
}
if (index === 0 || index === 4) {
sums[index] = '';
return;
}
const values = data.map((item: any) => {
return Number(item[column.property]);
});
if (!values.every((value: number) => Number.isNaN(value))) {
sums[index] = values.reduce((prev: number, curr: number) => {
const value = Number(curr);
if (!Number.isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = formatNumber(sums[index]);
} else {
sums[index] = '';
}
});
budgetForm.budgetCost = sums[2];
budgetForm.reduceBudgetCost = sums[3];
computedAmountAndRate();
return sums;
};
// 更新预算数据的方法(供父组件调用)
const updateBudgetDetailData = (type: string, budgetCost: number, reduceBudgetCost: number) => {
const item = budgetDetailData.value.find((item) => item.budgetItem === type);
if (item) {
item.budgetCost = budgetCost;
item.reduceBudgetCost = reduceBudgetCost;
}
};
// 接收项目信息
const props = defineProps({
projectInfo: {
type: Object,
default: () => ({})
},
userList: {
type: Object,
default: () => ({})
}
});
// 监听项目信息变化
watch(
() => props.projectInfo,
(newProjectInfo) => {
if (newProjectInfo) {
budgetForm.projectId = newProjectInfo.projectId || '';
budgetForm.projectName = newProjectInfo.projectName || '';
budgetForm.projectCode = newProjectInfo.projectCode || '';
budgetForm.projectCategory = newProjectInfo.projectCategory || '';
}
},
{ deep: true, immediate: true }
);
const computedAmountAndRate = async () => {
if (budgetForm.netContractAmount && budgetForm.budgetCost && budgetForm.netContractAmount > 0 && budgetForm.budgetCost > 0) {
budgetForm.budgetRate = (((budgetForm.netContractAmount - budgetForm.budgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
}
if (budgetForm.netContractAmount && budgetForm.reduceBudgetCost && budgetForm.netContractAmount > 0 && budgetForm.reduceBudgetCost > 0) {
budgetForm.reduceBudgetRate = (((budgetForm.netContractAmount - budgetForm.reduceBudgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
}
};
const contractAmountChange = async () => {
if (budgetForm.contractAmount) {
budgetForm.netContractAmount = (budgetForm.contractAmount / 1.13).toFixed(2);
}
};
// 暴露方法给父组件
defineExpose({
updateBudgetDetailData,
budgetForm,
budgetDetailData
});
// budgetForm.netContractAmount = computed(() => {
// if (budgetForm.contractAmount) {
// return (budgetForm.contractAmount / 1.13).toFixed(2);
// }
// return undefined;
// });
//
// budgetForm.budgetRate = computed(() => {
// if (budgetForm.netContractAmount && budgetForm.budgetCost && budgetForm.netContractAmount > 0 && budgetForm.budgetCost > 0) {
// return (((budgetForm.netContractAmount - budgetForm.budgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
// }
// return undefined;
// });
//
// budgetForm.reduceBudgetRate = computed(() => {
// if (budgetForm.netContractAmount && budgetForm.reduceBudgetCost && budgetForm.netContractAmount > 0 && budgetForm.reduceBudgetCost > 0) {
// return (((budgetForm.netContractAmount - budgetForm.reduceBudgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
// }
// return undefined;
// });
</script>
<style scoped>
.budget-table {
padding: 20px;
}
.title {
text-align: center;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
}
.budget-form {
margin-bottom: 20px;
}
.note {
margin-top: 10px;
font-size: 12px;
color: #666;
text-align: center;
}
.custom-display {
width: 100%;
height: 32px; /* 与el-input默认高度一致 */
line-height: 32px;
padding: 1px 11px; /* 与el-input内边距一致 */
border: 1px solid #dcdfe6; /* 与el-input边框一致 */
border-radius: 4px; /* 与el-input圆角一致 */
background-color: #f5f7fa; /* 禁用状态的背景色 */
color: #606266; /* 文字颜色 */
font-size: 14px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>