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.

1104 lines
45 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="project-budget-container">
<!-- 审批按钮组件 -->
<approvalButton
@submitForm="handleSave"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="searchForm.budgetId"
:status="searchForm.flowStatus"
:pageType="routeParams.type"
:mode="false"
/>
<!-- 顶部表单区域 -->
<el-card class="mb-6">
<el-form :model="searchForm" label-width="80px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="项目号">
<el-input v-model="searchForm.projectCode" placeholder="请选择项目" readonly @click="showProjectSelectDialog" suffix-icon="Search">
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目名称">
<el-input v-model="searchForm.projectName" placeholder="请选择项目后自动填充" disabled />
</el-form-item>
</el-col>
<!-- <el-col :span="12" class="text-right">-->
<!-- <el-button type="primary" @click="exportBudget" size="default">-->
<!-- <el-icon><Download /></el-icon>-->
<!-- 导出-->
<!-- </el-button>-->
<!-- </el-col>-->
</el-row>
</el-form>
</el-card>
<!-- 中间标签页区域 -->
<!-- 研发项目预算 -->
<el-card class="mb-6" v-if="searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="预算表" name="budgetTable">
<RdBudgetTable
ref="rdBudgetTableRef"
:projectId="searchForm.projectId"
:costData="costData"
:projectInfo="projectInfo"
:userList="userList"
/>
</el-tab-pane>
<el-tab-pane label="设备费" name="equipmentCost">
<RdEquipmentCost ref="rdEquipmentCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="材料费" name="materialCost">
<RdMaterialCost ref="rdMaterialCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="会议差旅交流" name="travelMeetingExchange">
<RdTravelMeetingExchange ref="rdTravelMeetingExchangeRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="人工劳务咨询" name="laborService">
<RdLaborService ref="rdLaborServiceRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="资料文献费" name="literatureCost">
<RdLiteratureCost ref="rdLiteratureCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="测试化验费" name="testingCost">
<RdTestingCost ref="rdTestingCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="其他" name="otherCost">
<RdOtherCost ref="rdOtherCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 市场项目预算 -->
<el-card
class="mb-6"
shadow="never"
v-if="searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART"
>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="预算表" name="budget">
<BudgetTable ref="budgetTableRef" :projectInfo="projectInfo" :userList="userList" />
</el-tab-pane>
<el-tab-pane label="材料费" name="material">
<MaterialCost ref="materialCostRef" :budgetId="budgetId" />
</el-tab-pane>
<el-tab-pane label="人工费" name="labor">
<LaborCost ref="laborCostRef" />
</el-tab-pane>
<el-tab-pane label="安装费" name="installation">
<InstallationCost ref="installationCostRef" />
</el-tab-pane>
<el-tab-pane label="差旅费" name="travel">
<TravelCost ref="travelCostRef" />
</el-tab-pane>
<el-tab-pane label="其他" name="other">
<OtherCost ref="otherCostRef" />
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 项目选择弹窗 -->
<el-dialog v-model="projectSelectDialogVisible" title="选择项目" width="880px">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="80px">
<el-form-item label="项目号" prop="projectCode">
<el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目状态" prop="projectStatus">
<el-select v-model="queryParams.projectStatus" placeholder="请选择项目状态" clearable>
<el-option v-for="dict in project_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-table :data="projectList" border @row-click="selectProject" style="width: 100%" v-loading="projectLoading" highlight-current-row>
<el-table-column prop="projectCode" label="项目号" width="150" />
<el-table-column prop="projectName" label="项目名称" min-width="180" />
<el-table-column prop="businessDirection" label="业务方向" width="120">
<template #default="scope">
<dict-tag :options="business_direction" :value="scope.row.businessDirection" />
</template>
</el-table-column>
<el-table-column prop="projectCategory" label="项目类别" width="160">
<template #default="scope">
<dict-tag :options="project_category" :value="scope.row.projectCategory" />
</template>
</el-table-column>
<el-table-column prop="projectStatus" label="项目状态" width="100">
<template #default="scope">
<dict-tag :options="project_status" :value="scope.row.projectStatus" />
</template>
</el-table-column>
</el-table>
<div class="mt-4 flex justify-between items-center">
<pagination
v-show="projectTotal > 0"
:total="projectTotal"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getProjectList"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="projectSelectDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmProjectSelection">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 提交审批组件 -->
<submitVerify ref="submitVerifyRef" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
</div>
</template>
<script setup lang="ts" name="BudgetInfoEdit">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { listProjectInfo, addErpBudgetInfo, updateErpBudgetInfo, getErpBudgetInfo } from '@/api/oa/erp/budgetInfo';
import { ProjectInfoVO, ProjectInfoQuery } from '@/api/oa/erp/projectInfo/types';
import { getUserList } from '@/api/system/user';
import { deepEqualArrays } from '@/utils/objHandle';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { business_direction, project_status, project_category } = toRefs<any>(
proxy?.useDict('business_direction', 'project_status', 'project_category')
);
//研发项目tab
import RdBudgetTable from './rd/BudgetTable.vue';
import RdEquipmentCost from './rd/EquipmentCost.vue';
import RdMaterialCost from './rd/MaterialCost.vue';
import RdTravelMeetingExchange from './rd/TravelMeetingExchange.vue';
import RdLaborService from './rd/LaborService.vue';
import RdLiteratureCost from './rd/LiteratureCost.vue';
import RdTestingCost from './rd/TestingCost.vue';
import RdOtherCost from './rd/OtherCost.vue';
//市场项目tab
import BudgetTable from './market/BudgetTable.vue';
import MaterialCost from './market/MaterialCost.vue';
import LaborCost from './market/LaborCost.vue';
import InstallationCost from './market/InstallationCost.vue';
import TravelCost from './market/TravelCost.vue';
import OtherCost from './market/OtherCost.vue';
// 定义成本数据类型
interface CostData {
equipmentCost: number;
materialCost: number;
travelCost: number;
meetingCost: number;
internationalExchangeCost: number;
consultingDevelopmentCost: number;
laborCost: number;
serviceCost: number;
literatureCost: number;
testingCost: number;
otherCost: number;
}
// 研发项目预算tab组件引用
const rdBudgetTableRef = ref();
const rdEquipmentCostRef = ref();
const rdMaterialCostRef = ref();
const rdTravelMeetingExchangeRef = ref();
const rdLaborServiceRef = ref();
const rdLiteratureCostRef = ref();
const rdTestingCostRef = ref();
const rdOtherCostRef = ref();
// 市场项目预算tab组件引用
const budgetTableRef = ref();
const materialCostRef = ref();
const laborCostRef = ref();
const installationCostRef = ref();
const travelCostRef = ref();
const otherCostRef = ref();
const oriBudgetMaterialCostList = ref<budgetMaterialCostVO[]>([]);
const oriBudgetLaborCostList = ref<budgetLaborCostVO[]>([]);
const oriBudgetInstallCostList = ref<budgetInstallCostVO[]>([]);
const oriBudgetTravelCostList = ref<budgetTravelCostVO[]>([]);
const oriBudgetOtherCostList = ref<budgetOtherCostVO[]>([]);
const oriRdBudgetEquipmentCostList = ref<rdBudgetEquipmentCostVO[]>([]);
const oriRdBudgetMaterialCostList = ref<rdBudgetMaterialCostVO[]>([]);
const oriRdBudgetTravelCostList = ref<rdBudgetTravelCostVO[]>([]);
const oriRdBudgetMeetingCostList = ref<rdBudgetMeetingCostVO[]>([]);
const oriRdBudgetExchangeCostList = ref<rdBudgetExchangeCostVO[]>([]);
const oriRdBudgetTechConsultCostList = ref<rdBudgetTechCostVO[]>([]); //技术咨询
const oriRdBudgetExpertMeetingCostList = ref<rdBudgetTechCostVO[]>([]); //专家咨询会议形式
const oriRdBudgetExpertCommCostList = ref<rdBudgetTechCostVO[]>([]); //专家咨询通讯形式
const oriRdBudgetLaborCostList = ref<rdBudgetLaborCostVO[]>([]); //人工费
const oriRdBudgetServiceCostList = ref<rdBudgetLaborCostVO[]>([]); //劳务费
const oriRdBudgetLiteratureMaterialCostList = ref<rdBudgetLiteratureCostVO[]>([]); //资料费
const oriRdBudgetLiteratureSofwareCostList = ref<rdBudgetLiteratureCostVO[]>([]); //专用软件购买费
const oriRdBudgetTestingCostList = ref<rdBudgetTestingCostVO[]>([]);
const oriRdBudgetOtherCostList = ref<rdBudgetOtherCostVO[]>([]);
const PROJECT_CATEGORY = reactive({
MARKET: '1', //销售(实施、物流)
MARKET_PART: '2', //销售(备件)
RD: '3', //研发
PRE_PRODUCTION: '4' //预投
});
const BUSINESS_STATUS = reactive({
DRAFT: '1', //暂存
WAITING: '2', //审批中
AVAILABLE: '3' //可用
});
const MATERIAL_TYPE = {
MAIN: '1', //主要材料费
OTHER: '2' //其他材料费
};
const TECH_TYPE = reactive({
TECH_CONSULT: '1', //技术咨询开发
EXPERT_MEETING: '2', //专家咨询-会议形式
EXPERT_COMM: '3' //专家咨询-通讯形式
});
const LABOR_TYPE = reactive({
LABOR: '1', //人工费
SERVICE: '2' //劳务费
});
const LITERATURE_TYPE = {
MATERIAL: '1', //资料费
DOCUMENT: '2', //文件检索费
SOFTWARE: '3' //专用软件购买费
};
// 响应式数据
// 将activeTab从computed改为ref使其可变
const activeTab = ref('budget');
const buttonLoading = ref(false);
const searchForm = reactive({
budgetId: undefined,
projectId: '',
projectName: '',
projectCode: '',
projectCategory: '',
flowStatus: ''
});
// 监听项目类别变化,更新默认标签页
watch(
() => searchForm.projectCategory,
(newCategory) => {
if (newCategory === PROJECT_CATEGORY.RD || newCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
activeTab.value = 'budgetTable';
} else if (newCategory === PROJECT_CATEGORY.MARKET || newCategory === PROJECT_CATEGORY.MARKET_PART) {
activeTab.value = 'budget';
}
}
);
// 项目信息
const projectInfo = reactive<ProjectInfoVO>({
projectName: '',
projectCode: '',
projectCategory: ''
});
// 研发预算成本数据
const costData = reactive<CostData>({
equipmentCost: 0,
materialCost: 0,
travelCost: 0,
meetingCost: 0,
internationalExchangeCost: 0,
consultingDevelopmentCost: 0,
laborCost: 0,
serviceCost: 0,
literatureCost: 0,
testingCost: 0,
otherCost: 0
});
const userList = ref<UserVO[]>([]);
/** 查询用户列表 */
const getUsers = async () => {
const res = await getUserList({});
userList.value = res.data;
};
// 项目选择弹窗相关
const projectSelectDialogVisible = ref(false);
const selectedProject = ref<ProjectInfoVO | null>(null);
const projectList = ref<ProjectInfoVO[]>([]);
const projectTotal = ref(0);
const projectLoading = ref(true);
const data = reactive<{ queryParams: ProjectInfoQuery }>({
queryParams: {
pageNum: 1,
pageSize: 10,
contractFlag: undefined,
projectCode: undefined,
projectName: undefined,
businessDirection: undefined,
projectCategory: undefined,
spareFlag: undefined,
projectTypeId: undefined,
paymentMethod: undefined,
deptId: undefined,
managerId: undefined,
chargeId: undefined,
deputyId: undefined,
peopleId: undefined,
amount: undefined,
projectStatus: undefined,
flowStatus: undefined,
sortOrder: undefined,
contractId: undefined,
activeFlag: undefined,
params: {}
}
});
const { queryParams } = toRefs(data);
const queryFormRef = ref<ElFormInstance>();
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getProjectList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 查询项目信息列表 */
const getProjectList = async () => {
projectLoading.value = true;
const res = await listProjectInfo(queryParams.value);
console.log(searchForm);
projectList.value = res.rows;
projectTotal.value = res.total;
projectLoading.value = false;
};
// 标签页切换处理
const handleTabClick = (tab: any) => {
activeTab.value = tab.paneName;
if ((budgetId.value && routeParams.value.type === 'update') || !budgetId.value) {
if (searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
updateRdBudgetTable();
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART) {
updateBudgetTable();
}
}
};
// 显示项目选择弹窗
const showProjectSelectDialog = () => {
projectSelectDialogVisible.value = true;
selectedProject.value = undefined;
getProjectList();
};
// 选择项目
const selectProject = (row: ProjectInfoVO) => {
selectedProject.value = row;
};
// 确认项目选择
const confirmProjectSelection = () => {
if (!selectedProject.value) {
ElMessage.warning('请选择一个项目');
return;
}
// 更新项目信息
searchForm.projectId = selectedProject.value.projectId;
searchForm.projectName = selectedProject.value.projectName;
searchForm.projectCode = selectedProject.value.projectCode;
searchForm.projectCategory = selectedProject.value.projectCategory;
projectInfo.projectId = selectedProject.value.projectId;
projectInfo.projectName = selectedProject.value.projectName;
projectInfo.projectCode = selectedProject.value.projectCode;
projectInfo.projectCategory = selectedProject.value.projectCategory;
projectSelectDialogVisible.value = false;
// ElMessage.success('项目选择成功');
// 重置所有成本数据
Object.keys(costData).forEach((key) => {
costData[key as keyof CostData] = 0;
});
};
// 设备费更新处理
const handleEquipmentCostUpdate = (data: { equipmentCost: number }) => {
costData.equipmentCost = data.equipmentCost;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 材料费更新处理
const handleMaterialCostUpdate = (data: { mainMaterialCost: number; otherMaterialCost: number; totalAmount: number }) => {
costData.materialCost = data.materialCost;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 会议差旅交流更新处理
const handleTravelMeetingExchangeUpdate = (data: { travelCost: number; meetingCost: number; internationalExchangeCost: number }) => {
costData.travelCost = data.travelCost;
costData.meetingCost = data.meetingCost;
costData.internationalExchangeCost = data.internationalExchangeCost;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 人工劳务咨询更新处理
const handleLaborServiceUpdate = (data: { techConsultTotal: number; laborTotal: number; serviceTotal: number }) => {
costData.consultingDevelopmentCost = data.techConsultTotal;
costData.laborCost = data.laborTotal;
costData.serviceCost = data.serviceTotal;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 资料文献费更新处理
const handleLiteratureCostUpdate = (data: { literatureCost: number }) => {
costData.literatureCost = data.literatureCost;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 测试化验费更新处理
const handleTestingCostUpdate = (data: { totalAmount: number }) => {
costData.testingCost = data.totalAmount;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 其他费用更新处理
const handleOtherCostUpdate = (data: { totalAmount: number }) => {
costData.otherCost = data.totalAmount;
if (rdBudgetTableRef.value) {
rdBudgetTableRef.value.updateCostData(costData);
}
};
// 更新市场项目预算预算表数据
const updateBudgetTable = async () => {
if (!budgetTableRef.value) return;
// 获取各tab页的合计数据
const materialTotal = materialCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
const laborTotal = laborCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
const installationTotal = installationCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
const travelTotal = travelCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
const otherTotal = otherCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
// 更新预算表,目前是按单位是万元传值,后续如果根据单位是元,则在此判断更新传的值
budgetTableRef.value.updateBudgetDetailData('材料费', materialTotal.budgetAmount / 10000, materialTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('人工费', laborTotal.budgetAmount / 10000, laborTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('安装费', installationTotal.budgetAmount / 10000, installationTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('差旅费', travelTotal.budgetAmount / 10000, travelTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('其他费用', otherTotal.budgetAmount / 10000, otherTotal.reducedAmount / 10000);
};
// 更新研发项目预算预算表数据
const updateRdBudgetTable = async () => {
// 获取各tab页的合计数据
const equipmentAmount = rdEquipmentCostRef.value?.getTotalAmount();
const equipmentTotal = equipmentAmount.equipmentTotal || 0;
const materialTotal = rdMaterialCostRef.value?.getTotalAmount() || 0;
const travelMeetingExchangeAmount = rdTravelMeetingExchangeRef.value?.getTravelMeetingExchangeAmount();
const travelTotal = travelMeetingExchangeAmount.travelSubtotalTotal || 0;
const meetingFeeTotal = travelMeetingExchangeAmount.meetingFeeTotal || 0;
const internationalSubtotalTotal = travelMeetingExchangeAmount.internationalSubtotalTotal || 0;
const laborAmount = rdLaborServiceRef.value?.getLaborAmount();
const techConsultTotal = laborAmount.techConsultTotal || 0;
const laborTotal = laborAmount.laborTotal || 0;
const serviceTotal = laborAmount.serviceTotal || 0;
const literatureCost = rdLiteratureCostRef.value?.getLiteratureCost() || 0;
const literatureTotal = literatureCost.literatureTotal || 0;
const testingCost = rdTestingCostRef.value?.getTestingAmount() || 0;
const testingTotal = testingCost.testingTotal || 0;
const otherCost = rdOtherCostRef.value?.getOtherCost() || 0;
const otherTotal = otherCost.otherTotal || 0;
// 更新预算表,目前是按单位是万元传值,后续如果根据单位是元,则在此判断更新传的值
rdBudgetTableRef.value.updateRdBudgetDetailData('设备费', equipmentTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('材料费', materialTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('差旅费', travelTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('会议费', meetingFeeTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('国际合作与交流费', internationalSubtotalTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('咨询开发费', techConsultTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('人工费', laborTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('劳务费', serviceTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('资料/文献费', literatureTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('测试化验费', testingTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('其他费用', otherTotal / 10000);
};
// 新增一个方法用于在tab切换时更新预算数据
const updateBudgetDataOnTabChange = () => {
nextTick(() => {
if (searchForm.projectCategory === PROJECT_CATEGORY.RD) {
// 确保调用更新方法时传递正确的参数
if (rdEquipmentCostRef.value?.getTotalAmount) {
handleEquipmentCostUpdate({ equipmentCost: rdEquipmentCostRef.value.getTotalAmount() });
}
if (rdMaterialCostRef.value?.getTotalAmount) {
const materialTotal = rdMaterialCostRef.value.getTotalAmount();
handleMaterialCostUpdate({
mainMaterialCost: materialTotal.mainMaterialCost || 0,
otherMaterialCost: materialTotal.otherMaterialCost || 0,
materialCost: (materialTotal.mainMaterialCost || 0) + (materialTotal.otherMaterialCost || 0)
});
}
// 其他方法类似处理...
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET) {
updateBudgetTable();
}
});
};
// 监听tab切换更新预算表数据
// watch(activeTab, () => {
// updateBudgetDataOnTabChange();
// });
// 导出预算
const exportBudget = () => {
if (!searchForm.projectId) {
ElMessage.warning('请先选择项目');
return;
}
ElMessage.success('导出预算成功');
};
// 提取用户查找逻辑
const findUserName = (userList: any[], userId: number): string => {
const user = userList?.find((item) => item.userId === userId);
return user?.nickName || '';
};
// 提取数据比较和赋值逻辑
const assignIfChanged = (targetArray: any[], sourceArray: any[], originalArray: any[]): any[] => {
return !deepEqualArrays(originalArray, sourceArray) ? sourceArray || [] : [];
};
const handleSave = async (status: string, mode: boolean) => {
if (!searchForm.projectId) {
ElMessage.warning('请先选择项目');
return;
}
try {
buttonLoading.value = true;
const isRdProject = [PROJECT_CATEGORY.RD, PROJECT_CATEGORY.PRE_PRODUCTION].includes(searchForm.projectCategory);
const isMarketProject = [PROJECT_CATEGORY.MARKET, PROJECT_CATEGORY.MARKET_PART].includes(searchForm.projectCategory);
let budgetForm: budgetInfoForm = {};
if (isRdProject) {
budgetForm = await processRdBudgetForm(status);
} else if (isMarketProject) {
budgetForm = await processMarketBudgetForm(status);
} else {
ElMessage.warning('不支持的预算类型');
return;
}
// 统一保存逻辑
const saveOperation = budgetForm.budgetId ? () => updateErpBudgetInfo(budgetForm) : () => addErpBudgetInfo(budgetForm);
await saveOperation();
ElMessage.success('操作成功');
handleSaveSuccess();
} catch (error) {
console.error('保存失败:', error);
ElMessage.error('保存失败');
} finally {
buttonLoading.value = false;
}
};
// 处理研发预算表单
const processRdBudgetForm = async (status: string): Promise<budgetInfoForm> => {
await updateRdBudgetTable();
const budgetDetailData = rdBudgetTableRef.value?.budgetDetailData || [];
const rdBudgetInfoForm = rdBudgetTableRef.value?.rdBudgetInfoForm || {};
const footerForm = rdBudgetTableRef.value?.footerForm || {};
// 合并表单数据
const budgetForm = {
...rdBudgetInfoForm,
// 设置状态
budgetStatus: getBudgetStatus(status),
flowStatus: getFlowStatus(status),
// 设置负责人信息
managerId: footerForm.managerId,
managerName: findUserName(userList.value, footerForm.managerId),
approveUserId: footerForm.approveUserId,
approveUserName: findUserName(userList.value, footerForm.approveUserId),
// 预算详情
erpBudgetDetailList: budgetDetailData,
// 初始化所有成本列表
...initializeRdCostLists()
};
console.log(budgetForm);
// 处理各项成本数据
processRdCostData(budgetForm);
// 如果有预算ID设置要删除的列表
if (budgetForm.budgetId) {
setRdToDeletedLists(budgetForm);
}
return budgetForm;
};
// 处理市场预算表单
const processMarketBudgetForm = async (status: string): Promise<budgetInfoForm> => {
await updateBudgetTable();
const budgetDetailData = budgetTableRef.value?.budgetDetailData || [];
const budgetFormData = budgetTableRef.value?.budgetForm || {};
const budgetForm = {
...budgetFormData,
budgetStatus: getBudgetStatus(status),
flowStatus: getFlowStatus(status),
managerName: findUserName(userList.value, budgetFormData.managerId),
productManagerName: findUserName(userList.value, budgetFormData.productManagerId),
erpBudgetDetailList: budgetDetailData,
// 初始化市场预算成本列表
...initializeMarketCostLists()
};
// 处理市场预算成本数据
processMarketCostData(budgetForm);
// 如果有预算ID设置要删除的列表
if (budgetForm.budgetId) {
setMarketToDeletedLists(budgetForm);
}
return budgetForm;
};
// 辅助函数
const getBudgetStatus = (status: string): string => {
return status === 'draft' ? BUSINESS_STATUS.DRAFT : BUSINESS_STATUS.WAITING;
};
const getFlowStatus = (status: string): string => {
return status === 'draft' ? 'draft' : 'waiting';
};
const initializeRdCostLists = () => ({
erpRdBudgetEquipmentCostList: [],
erpRdBudgetMaterialCostList: [],
erpRdBudgetTravelCostList: [],
erpRdBudgetMeetingCostList: [],
erpRdBudgetExchangeCostList: [],
erpRdBudgetTechCostList: [],
erpRdBudgetLaborCostList: [],
erpRdBudgetLiteratureCostList: [],
erpRdBudgetTestingCostList: [],
erpRdBudgetOtherCostList: []
});
const initializeMarketCostLists = () => ({
erpBudgetMaterialCostList: [],
erpBudgetLaborCostList: [],
erpBudgetInstallCostList: [],
erpBudgetTravelCostList: [],
erpBudgetOtherCostList: []
});
// 处理研发成本数据
const processRdCostData = (budgetForm: budgetInfoForm) => {
// 设备成本
if (assignIfChanged([], rdEquipmentCostRef.value?.equipmentData, oriRdBudgetEquipmentCostList.value).length) {
budgetForm.erpRdBudgetEquipmentCostList = rdEquipmentCostRef.value?.equipmentData || [];
}
// 材料成本
if (assignIfChanged([], rdMaterialCostRef.value?.allMaterialData, oriRdBudgetMaterialCostList.value).length) {
budgetForm.erpRdBudgetMaterialCostList = rdMaterialCostRef.value?.allMaterialData || [];
}
// 差旅、会议、交流成本
const travelMeetingExchangeRef = rdTravelMeetingExchangeRef.value;
if (assignIfChanged([], travelMeetingExchangeRef?.travelList, oriRdBudgetTravelCostList.value).length) {
budgetForm.erpRdBudgetTravelCostList = travelMeetingExchangeRef?.travelList || [];
}
if (assignIfChanged([], travelMeetingExchangeRef?.meetingList, oriRdBudgetMeetingCostList.value).length) {
budgetForm.erpRdBudgetMeetingCostList = travelMeetingExchangeRef?.meetingList || [];
}
if (assignIfChanged([], travelMeetingExchangeRef?.exchangeList, oriRdBudgetExchangeCostList.value).length) {
budgetForm.erpRdBudgetExchangeCostList = travelMeetingExchangeRef?.exchangeList || [];
}
// 技术成本
const laborServiceRef = rdLaborServiceRef.value;
budgetForm.erpRdBudgetTechCostList = [
...assignIfChanged([], laborServiceRef?.techConsultList, oriRdBudgetTechConsultCostList.value),
...assignIfChanged([], laborServiceRef?.expertMeetingList, oriRdBudgetExpertMeetingCostList.value),
...assignIfChanged([], laborServiceRef?.expertCommList, oriRdBudgetExpertCommCostList.value)
];
// 劳务成本
budgetForm.erpRdBudgetLaborCostList = [
...assignIfChanged([], laborServiceRef?.laborList, oriRdBudgetLaborCostList.value),
...assignIfChanged([], laborServiceRef?.serviceList, oriRdBudgetServiceCostList.value)
];
// 文献成本
const literatureCostRef = rdLiteratureCostRef.value;
budgetForm.erpRdBudgetLiteratureCostList = [
...assignIfChanged([], literatureCostRef?.materialsList, oriRdBudgetLiteratureMaterialCostList.value),
...assignIfChanged([], literatureCostRef?.softwareList, oriRdBudgetLiteratureSofwareCostList.value),
literatureCostRef?.literatureRetrieval || {}
].filter((item) => Object.keys(item).length > 0);
// 测试成本
if (assignIfChanged([], rdTestingCostRef.value?.testData, oriRdBudgetTestingCostList.value).length) {
budgetForm.erpRdBudgetTestingCostList = rdTestingCostRef.value?.testData || [];
}
// 其他成本
if (assignIfChanged([], rdOtherCostRef.value?.otherCostList, oriRdBudgetOtherCostList.value).length) {
budgetForm.erpRdBudgetOtherCostList = rdOtherCostRef.value?.otherCostList || [];
}
};
// 设置研发删除列表
const setRdToDeletedLists = (budgetForm: budgetInfoForm) => {
const refs = {
equipment: rdEquipmentCostRef.value,
material: rdMaterialCostRef.value,
travelMeetingExchange: rdTravelMeetingExchangeRef.value,
laborService: rdLaborServiceRef.value,
literature: rdLiteratureCostRef.value,
testing: rdTestingCostRef.value,
other: rdOtherCostRef.value
};
budgetForm.toDeletedRdEquipmentCostIdList = refs.equipment?.toDeletedEquipmentCostIdList;
budgetForm.toDeletedRdMaterialCostIdList = refs.material?.toDeletedMaterialCostIdList;
budgetForm.toDeletedRdTravelCostIdList = refs.travelMeetingExchange?.toDeletedTravelCostIdList;
budgetForm.toDeletedRdMeetingCostIdList = refs.travelMeetingExchange?.toDeletedMeetingCostIdList;
budgetForm.toDeletedRdExchangeCostIdList = refs.travelMeetingExchange?.toDeletedExchangeCostIdList;
budgetForm.toDeletedRdTechCostIdList = refs.laborService?.toDeletedTechCostIdList;
budgetForm.toDeletedRdLaborCostIdList = refs.laborService?.toDeletedLaborCostIdList;
budgetForm.toDeletedRdLiteratureCostIdList = refs.literature?.toDeletedLiteratureCostIdList;
budgetForm.toDeletedRdTestingCostIdList = refs.testing?.toDeletedTestCostIdList;
budgetForm.toDeletedRdOtherCostIdList = refs.other?.toDeletedOtherCostIdList;
};
// 处理市场成本数据
const processMarketCostData = (budgetForm: budgetInfoForm) => {
const assignCostData = (targetKey: keyof budgetInfoForm, sourceData: any[], originalData: any[]) => {
if (!deepEqualArrays(originalData, sourceData)) {
budgetForm[targetKey] = sourceData || [];
}
};
assignCostData('erpBudgetMaterialCostList', materialCostRef.value?.budgetMaterialCostList, oriBudgetMaterialCostList.value);
assignCostData('erpBudgetLaborCostList', laborCostRef.value?.budgetLaborCostList, oriBudgetLaborCostList.value);
assignCostData('erpBudgetInstallCostList', installationCostRef.value?.installCostList, oriBudgetInstallCostList.value);
assignCostData('erpBudgetTravelCostList', travelCostRef.value?.travelCostList, oriBudgetTravelCostList.value);
assignCostData('erpBudgetOtherCostList', otherCostRef.value?.otherCostList, oriBudgetOtherCostList.value);
};
// 设置市场删除列表
const setMarketToDeletedLists = (budgetForm: budgetInfoForm) => {
budgetForm.toDeletedMaterialCostIdList = materialCostRef.value?.toDeletedMaterialCostIdList;
budgetForm.toDeletedLaborCostIdList = laborCostRef.value?.toDeletedLaborCostIdList;
budgetForm.toDeletedInstallCostIdList = installationCostRef.value?.toDeletedInstallCostIdList;
budgetForm.toDeletedTravelCostIdList = travelCostRef.value?.toDeletedTravelCostIdList;
budgetForm.toDeletedOtherCostIdList = otherCostRef.value?.toDeletedOtherCostIdList;
};
// 保存成功处理
const handleSaveSuccess = () => {
const obj = {
path: '/budget/erp/budgetInfo',
query: {
t: Date.now(),
pageNum: routeParams.value.pageNum
}
};
proxy?.$tab.closeOpenPage(obj);
};
// 关闭页面
const handleClose = () => {
// 可以添加确认对话框
// 这里简单实现返回上一页
window.history.back();
};
const route = useRoute();
const budgetId = ref();
// 初始化
onMounted(() => {
nextTick(async () => {
// 获取路由参数
routeParams.value = route.query;
budgetId.value = routeParams.value.id as string | number;
if (budgetId.value && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
proxy?.$modal.loading('正在加载数据,请稍后...');
const res = await getErpBudgetInfo(budgetId.value);
Object.assign(searchForm, res.data);
console.log(res.data);
await getUsers();
if (searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
oriRdBudgetEquipmentCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetEquipmentCostList));
const erpRdBudgetMaterialCostList = res.data.erpRdBudgetMaterialCostList;
const groupedRdBudgetMaterialCostListByMaterialType = erpRdBudgetMaterialCostList.reduce((acc, item) => {
const materialType = item.materialType;
if (!acc[materialType]) {
acc[materialType] = [];
}
acc[materialType].push(item);
return acc;
}, {});
const rdBudgetMainMaterialCostList = groupedRdBudgetMaterialCostListByMaterialType[MATERIAL_TYPE.MAIN] || [];
const rdBudgetOtherMaterialCostList = groupedRdBudgetMaterialCostListByMaterialType[MATERIAL_TYPE.OTHER] || [];
oriRdBudgetMaterialCostList.value = JSON.parse(JSON.stringify(erpRdBudgetMaterialCostList));
oriRdBudgetTravelCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetTravelCostList));
oriRdBudgetMeetingCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetMeetingCostList));
oriRdBudgetExchangeCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetExchangeCostList));
const erpRdBudgetTechCostList = res.data.erpRdBudgetTechCostList;
const groupedRdBudgetTechCostListByTechType = erpRdBudgetTechCostList.reduce((acc, item) => {
const techType = item.techType;
if (!acc[techType]) {
acc[techType] = [];
}
acc[techType].push(item);
return acc;
}, {});
const rdBudgetTechConsultCostList = groupedRdBudgetTechCostListByTechType[TECH_TYPE.TECH_CONSULT] || [];
const rdBudgetExpertMeetingCostList = groupedRdBudgetTechCostListByTechType[TECH_TYPE.EXPERT_MEETING] || [];
const rdBudgetExpertCommCostList = groupedRdBudgetTechCostListByTechType[TECH_TYPE.EXPERT_COMM] || [];
oriRdBudgetTechConsultCostList.value = JSON.parse(JSON.stringify(rdBudgetTechConsultCostList)); //技术咨询
oriRdBudgetExpertMeetingCostList.value = JSON.parse(JSON.stringify(rdBudgetExpertMeetingCostList)); //专家咨询会议形式
oriRdBudgetExpertCommCostList.value = JSON.parse(JSON.stringify(rdBudgetExpertCommCostList)); //专家咨询通讯形式
const erpRdBudgetLaborCostList = res.data.erpRdBudgetLaborCostList;
const groupedRdBudgetLaborCostListByLaborType = erpRdBudgetLaborCostList.reduce((acc, item) => {
const laborType = item.laborType;
if (!acc[laborType]) {
acc[laborType] = [];
}
acc[laborType].push(item);
return acc;
}, {});
const rdBudgetLaborCostList = groupedRdBudgetLaborCostListByLaborType[LABOR_TYPE.LABOR] || [];
const rdBudgetServiceCostList = groupedRdBudgetLaborCostListByLaborType[LABOR_TYPE.SERVICE] || [];
oriRdBudgetLaborCostList.value = JSON.parse(JSON.stringify(rdBudgetLaborCostList)); //人工费
oriRdBudgetServiceCostList.value = JSON.parse(JSON.stringify(rdBudgetServiceCostList)); //劳务费
const erpRdBudgetLiteratureCostList = res.data.erpRdBudgetLiteratureCostList;
const groupedRdBudgetLiteratureCostListByLiteratureType = erpRdBudgetLiteratureCostList.reduce((acc, item) => {
const literatureType = item.literatureType;
if (!acc[literatureType]) {
acc[literatureType] = [];
}
acc[literatureType].push(item);
return acc;
}, {});
const rdBudgetLiteratureMaterialCostList = groupedRdBudgetLiteratureCostListByLiteratureType[LITERATURE_TYPE.MATERIAL] || [];
const rdBudgetLiteratureSoftwareCostList = groupedRdBudgetLiteratureCostListByLiteratureType[LITERATURE_TYPE.SOFTWARE] || [];
oriRdBudgetLiteratureMaterialCostList.value = JSON.parse(JSON.stringify(rdBudgetLiteratureMaterialCostList)); //资料费
oriRdBudgetLiteratureSofwareCostList.value = JSON.parse(JSON.stringify(rdBudgetLiteratureSoftwareCostList)); //专用软件购买费
const rdBudgetLiteratureRetrievalCostList = groupedRdBudgetLiteratureCostListByLiteratureType[LITERATURE_TYPE.DOCUMENT] || []; //文献检索费
oriRdBudgetTestingCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetTestingCostList));
oriRdBudgetOtherCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetOtherCostList));
nextTick(() => {
console.log(rdBudgetTableRef.value?.budgetForm);
Object.assign(rdBudgetTableRef.value?.rdBudgetInfoForm, res.data);
Object.assign(rdBudgetTableRef.value?.footerForm, res.data);
Object.assign(rdBudgetTableRef.value?.budgetDetailData || [], res.data.erpBudgetDetailList);
Object.assign(rdEquipmentCostRef.value?.equipmentData || [], res.data.erpRdBudgetEquipmentCostList);
Object.assign(rdMaterialCostRef.value?.materialData || [], rdBudgetMainMaterialCostList);
Object.assign(rdMaterialCostRef.value?.otherMaterial || [], rdBudgetOtherMaterialCostList[0]);
Object.assign(rdTravelMeetingExchangeRef.value?.travelList || [], res.data.erpRdBudgetTravelCostList);
Object.assign(rdTravelMeetingExchangeRef.value?.meetingList || [], res.data.erpRdBudgetMeetingCostList);
Object.assign(rdTravelMeetingExchangeRef.value?.exchangeList || [], res.data.erpRdBudgetExchangeCostList);
Object.assign(rdLaborServiceRef.value?.techConsultList || [], rdBudgetTechConsultCostList);
Object.assign(rdLaborServiceRef.value?.expertMeetingList || [], rdBudgetExpertMeetingCostList);
Object.assign(rdLaborServiceRef.value?.expertCommList || [], rdBudgetExpertCommCostList);
Object.assign(rdLaborServiceRef.value?.laborList || [], rdBudgetLaborCostList);
Object.assign(rdLaborServiceRef.value?.serviceList || [], rdBudgetServiceCostList);
Object.assign(rdLiteratureCostRef.value?.materialsList || [], rdBudgetLiteratureMaterialCostList);
Object.assign(rdLiteratureCostRef.value?.softwareList || [], rdBudgetLiteratureSoftwareCostList);
Object.assign(
rdLiteratureCostRef.value?.literatureRetrieval || [],
rdBudgetLiteratureRetrievalCostList.length > 0 ? rdBudgetLiteratureRetrievalCostList[0] : {}
);
Object.assign(rdTestingCostRef.value?.testData || [], res.data.erpRdBudgetTestingCostList);
Object.assign(rdOtherCostRef.value?.otherCostList || [], res.data.erpRdBudgetOtherCostList);
});
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART) {
// 创建初始状态的深拷贝(确保完全独立)
oriBudgetMaterialCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetMaterialCostList));
oriBudgetLaborCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetLaborCostList));
oriBudgetInstallCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetInstallCostList));
oriBudgetTravelCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetTravelCostList));
oriBudgetOtherCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetOtherCostList));
nextTick(() => {
console.log(budgetTableRef.value?.budgetForm);
Object.assign(budgetTableRef.value?.budgetForm, res.data);
Object.assign(budgetTableRef.value?.budgetDetailData, res.data.erpBudgetDetailList);
Object.assign(materialCostRef.value?.budgetMaterialCostList, res.data.erpBudgetMaterialCostList);
Object.assign(laborCostRef.value?.budgetLaborCostList, res.data.erpBudgetLaborCostList);
Object.assign(installationCostRef.value?.installCostList, res.data.erpBudgetInstallCostList);
Object.assign(travelCostRef.value?.travelCostList, res.data.erpBudgetTravelCostList);
Object.assign(otherCostRef.value?.otherCostList, res.data.erpBudgetOtherCostList);
});
}
proxy?.$modal.closeLoading();
} else {
searchForm.projectCategory = routeParams.value.projectCategory;
// 新增模式:如果已有合同编号,禁用生成按钮
// if (form.value.contractCode) {
// isCodeGenerated.value = true;
// } else if (form.value.contractFlag === '1') {
// // 如果有合同但没有编号,允许生成
// isCodeGenerated.value = false;
// }
}
queryParams.value.projectCategory = searchForm.projectCategory;
});
});
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { getContractInfo } from '@/api/oa/erp/contractInfo';
import { budgetMaterialCostVO } from '@/api/oa/erp/budgetInfo/market/budgetMaterialCost/types';
import { budgetLaborCostVO } from '@/api/oa/erp/budgetInfo/market/budgetLaborCost/types';
import { budgetInstallCostVO } from '@/api/oa/erp/budgetInfo/market/budgetInstallCost/types';
import { budgetTravelCostVO } from '@/api/oa/erp/budgetInfo/market/budgetTravelCost/types';
import { budgetOtherCostVO } from '@/api/oa/erp/budgetInfo/market/budgetOtherCost/types';
import { budgetInfoForm } from '@/api/oa/erp/budgetInfo/types';
import { rdBudgetEquipmentCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetEquipmentCost/types';
import { rdBudgetMaterialCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetMaterialCost/types';
import { rdBudgetTravelCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTravelCost/types';
import { rdBudgetMeetingCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetMeetingCost/types';
import { rdBudgetExchangeCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetExchangeCost/types';
import { rdBudgetTechCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTechCost/types';
import { rdBudgetLaborCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetLaborCost/types';
import { rdBudgetLiteratureCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetLiteratureCost/types';
import { rdBudgetTestingCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTestingCost/types';
import { rdBudgetOtherCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetOtherCost/types';
import { UserVO } from '@/api/system/user/types';
// 路由参数
const routeParams = ref<Record<string, any>>({});
// 审批相关组件引用
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
// 审批
const approvalVerifyOpen = async () => {
await submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// 审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(searchForm.budgetId);
};
// 提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
router.go(-1);
};
</script>
<style scoped>
.project-budget-container {
padding: 20px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.justify-end {
justify-content: flex-end;
}
.gap-2 {
gap: 8px;
}
.gap-4 {
gap: 16px;
}
.mb-6 {
margin-top: 12px;
margin-bottom: 6px;
}
</style>