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
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>
|