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

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