1.1.00前端:

feat(budget):根据研发预算新模板修改保存功能
dev
xs 2 months ago
parent 58119732cd
commit ac84526867

@ -121,3 +121,15 @@ export const listMaterialInfo = (query?: MaterialInfoQuery): AxiosPromise<Materi
params: query
});
};
/**
*
* @param query
* @returns {*}
*/
export function getRdBudgetDefinitionList() {
return request({
url: '/oa/erp/budgetInfo/getRdBudgetDefinitionList',
method: 'get'
});
};

@ -1,5 +1,5 @@
<template>
<div class="project-budget-container">
<div class="project-budget-container" v-loading="loading">
<!-- 审批按钮组件 -->
<approvalButton
@submitForm="handleSave"
@ -27,19 +27,17 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="版本号">
<el-input v-model="searchForm.budgetVersion" disabled>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请说明">
<el-input v-model="searchForm.remark" type="textarea" placeholder="请输入申请说明" />
</el-form-item>
</el-col>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="版本号">
<el-input v-model="searchForm.budgetVersion" disabled> </el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请说明">
<el-input v-model="searchForm.remark" type="textarea" placeholder="请输入申请说明" />
</el-form-item>
</el-col>
<!-- <el-col :span="12" class="text-right">-->
<!-- <el-button type="primary" @click="exportBudget" size="default">-->
@ -55,7 +53,10 @@
<!-- 研发项目预算 -->
<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">
<el-tab-pane label="预算编制标准说明" name="budgetDefinition">
<RdBudgetDefinition ref="rdBudgetDefinitionRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="表1-预算汇总" name="budgetTable">
<RdBudgetTable
ref="rdBudgetTableRef"
:projectId="searchForm.projectId"
@ -64,25 +65,22 @@
: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">
<el-tab-pane label="表2-材料费" 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">
<el-tab-pane label="表3-人工费" 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 label="表4-差旅费" name="travelMeetingExchange">
<RdTravelMeetingExchange ref="rdTravelMeetingExchangeRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="测试化验费" name="testingCost">
<el-tab-pane label="表5-测试化验加工费" name="testingCost">
<RdTestingCost ref="rdTestingCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="其他" name="otherCost">
<el-tab-pane label="表6-咨询费、设计费" name="techCost">
<RdTechCost ref="rdTechCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="表7-其他费用" name="otherCost">
<RdOtherCost ref="rdOtherCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
</el-tabs>
@ -190,7 +188,14 @@
<script setup lang="ts" name="BudgetInfoEdit">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { listProjectInfo, addErpBudgetInfo, updateErpBudgetInfo, changeErpBudgetInfo, getErpBudgetInfo } from '@/api/oa/erp/budgetInfo';
import {
listProjectInfo,
addErpBudgetInfo,
updateErpBudgetInfo,
changeErpBudgetInfo,
getErpBudgetInfo,
getRdBudgetDefinitionList
} from '@/api/oa/erp/budgetInfo';
import { ProjectInfoVO, ProjectInfoQuery } from '@/api/oa/erp/projectInfo/types';
import { getUserList } from '@/api/system/user';
@ -205,12 +210,12 @@ const { business_direction, project_status, project_category } = toRefs<any>(
);
//tab
import RdBudgetDefinition from './rd/BudgetDefinition.vue';
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 RdTechCost from './rd/TechCost.vue';
import RdTestingCost from './rd/TestingCost.vue';
import RdOtherCost from './rd/OtherCost.vue';
@ -222,6 +227,8 @@ import InstallationCost from './market/InstallationCost.vue';
import TravelCost from './market/TravelCost.vue';
import OtherCost from './market/OtherCost.vue';
const loading = ref(true);
//
interface CostData {
equipmentCost: number;
@ -238,12 +245,12 @@ interface CostData {
}
// tab
const rdBudgetDefinitionRef = ref();
const rdBudgetTableRef = ref();
const rdEquipmentCostRef = ref();
const rdMaterialCostRef = ref();
const rdTravelMeetingExchangeRef = ref();
const rdLaborServiceRef = ref();
const rdLiteratureCostRef = ref();
const rdTechCostRef = ref();
const rdTestingCostRef = ref();
const rdOtherCostRef = ref();
@ -261,20 +268,15 @@ 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 oriRdBudgetLaborCostList = ref<rdBudgetLaborCostVO[]>([]); //
const oriRdBudgetTravelCostList = ref<rdBudgetTravelCostVO[]>([]); //-
const oriRdBudgetTransportationCostList = ref<rdBudgetTravelCostVO[]>([]); //-
const oriRdBudgetTestingCostList = ref<rdBudgetTestingCostVO[]>([]); //
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 oriRdBudgetOtherCostList = ref<rdBudgetOtherCostVO[]>([]); //
const PROJECT_CATEGORY = reactive({
MARKET: '1', //
@ -289,26 +291,15 @@ const BUSINESS_STATUS = reactive({
AVAILABLE: '3' //
});
const MATERIAL_TYPE = {
MAIN: '1', //
OTHER: '2' //
};
const TECH_TYPE = reactive({
TECH_CONSULT: '1', //
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' //
const TRIP_TYPE = {
TRAVEL: '1', //
TRANSPORTATION: '2' //
};
//
@ -478,66 +469,6 @@ const confirmProjectSelection = () => {
});
};
//
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;
@ -560,75 +491,31 @@ const updateBudgetTable = async () => {
//
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 techCost = rdTechCostRef.value?.getTechAmount() || 0;
const expertConsultTotal = techCost.expertConsultTotal || 0;
const techConsultTotal = techCost.techConsultTotal || 0;
const travelMeetingExchangeAmount = rdTravelMeetingExchangeRef.value?.getTravelAmount();
const travelTotal = travelMeetingExchangeAmount.travelSubtotalTotal || 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('差旅费', travelTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('测试化验加工费', testingTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('专家咨询费用', expertConsultTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('新产品设计费', techConsultTotal / 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);
@ -695,7 +582,7 @@ const processRdBudgetForm = async (status: string): Promise<budgetInfoForm> => {
const budgetDetailData = rdBudgetTableRef.value?.budgetDetailData || [];
const rdBudgetInfoForm = rdBudgetTableRef.value?.rdBudgetInfoForm || {};
const footerForm = rdBudgetTableRef.value?.footerForm || {};
rdBudgetInfoForm.budgetCost = rdBudgetTableRef.value?.totalAmount;
//
const budgetForm = {
@ -704,10 +591,9 @@ const processRdBudgetForm = async (status: string): Promise<budgetInfoForm> => {
budgetStatus: getBudgetStatus(status),
flowStatus: getFlowStatus(status),
//
managerId: footerForm.managerId,
managerName: findUserName(userList.value, footerForm.managerId),
approveUserId: footerForm.approveUserId,
approveUserName: findUserName(userList.value, footerForm.approveUserId),
managerName: findUserName(userList.value, rdBudgetInfoForm.managerId),
productManagerName: findUserName(userList.value, rdBudgetInfoForm.productManagerId),
approveUserName: findUserName(userList.value, rdBudgetInfoForm.approveUserId),
//
erpBudgetDetailList: budgetDetailData,
//
@ -764,15 +650,11 @@ const getFlowStatus = (status: string): string => {
};
const initializeRdCostLists = () => ({
erpRdBudgetEquipmentCostList: [],
erpRdBudgetMaterialCostList: [],
erpRdBudgetTravelCostList: [],
erpRdBudgetMeetingCostList: [],
erpRdBudgetExchangeCostList: [],
erpRdBudgetTechCostList: [],
erpRdBudgetLaborCostList: [],
erpRdBudgetLiteratureCostList: [],
erpRdBudgetTravelCostList: [],
erpRdBudgetTestingCostList: [],
erpRdBudgetTechCostList: [],
erpRdBudgetOtherCostList: []
});
@ -787,70 +669,35 @@ const initializeMarketCostLists = () => ({
//
const processRdCostData = (budgetForm: budgetInfoForm) => {
//
console.log(rdMaterialCostRef.value?.allMaterialData);
console.log(oriRdBudgetMaterialCostList.value);
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 || []).map(item => {
const newItem = { ...item };
//
if (newItem.materialType === 2 || newItem.materialType === '2') {
if (newItem.price !== null && newItem.price !== undefined) {
const priceValue = parseFloat(newItem.price);
if (!isNaN(priceValue)) {
// 10000
newItem.price = priceValue * 10000;
}
}
}
return newItem;
});
if (assignIfChanged([], rdMaterialCostRef.value?.materialData, oriRdBudgetMaterialCostList.value).length) {
budgetForm.erpRdBudgetMaterialCostList = rdMaterialCostRef.value?.materialData || [];
}
//
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 || [];
//
if (assignIfChanged([], rdLaborServiceRef.value?.laborList, oriRdBudgetLaborCostList.value).length) {
budgetForm.erpRdBudgetLaborCostList = rdLaborServiceRef.value?.laborList || [];
}
//
const laborServiceRef = rdLaborServiceRef.value;
budgetForm.erpRdBudgetTechCostList = [
...assignIfChanged([], laborServiceRef?.techConsultList, oriRdBudgetTechConsultCostList.value),
...assignIfChanged([], laborServiceRef?.expertMeetingList, oriRdBudgetExpertMeetingCostList.value),
...assignIfChanged([], laborServiceRef?.expertCommList, oriRdBudgetExpertCommCostList.value)
//
budgetForm.erpRdBudgetTravelCostList = [
...assignIfChanged([], rdTravelMeetingExchangeRef.value?.travelList, oriRdBudgetTravelCostList.value),
...assignIfChanged([], rdTravelMeetingExchangeRef.value?.transportationList, oriRdBudgetTransportationCostList.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 || [];
}
//
budgetForm.erpRdBudgetTechCostList = [
...assignIfChanged([], rdTechCostRef.value?.techConsultList, oriRdBudgetTechConsultCostList.value),
...assignIfChanged([], rdTechCostRef.value?.expertMeetingList, oriRdBudgetExpertMeetingCostList.value),
...assignIfChanged([], rdTechCostRef.value?.expertCommList, oriRdBudgetExpertCommCostList.value)
];
//
if (assignIfChanged([], rdOtherCostRef.value?.otherCostList, oriRdBudgetOtherCostList.value).length) {
budgetForm.erpRdBudgetOtherCostList = rdOtherCostRef.value?.otherCostList || [];
@ -860,24 +707,19 @@ const processRdCostData = (budgetForm: budgetInfoForm) => {
//
const setRdToDeletedLists = (budgetForm: budgetInfoForm) => {
const refs = {
equipment: rdEquipmentCostRef.value,
material: rdMaterialCostRef.value,
travelMeetingExchange: rdTravelMeetingExchangeRef.value,
laborService: rdLaborServiceRef.value,
literature: rdLiteratureCostRef.value,
testing: rdTestingCostRef.value,
tech: rdTechCostRef.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.toDeletedRdTravelCostIdList = refs.travelMeetingExchange?.toDeletedTravelCostIdList;
budgetForm.toDeletedRdTestingCostIdList = refs.testing?.toDeletedTestCostIdList;
budgetForm.toDeletedRdTechCostIdList = refs.tech?.toDeletedTechCostIdList;
budgetForm.toDeletedRdOtherCostIdList = refs.other?.toDeletedOtherCostIdList;
};
@ -931,14 +773,10 @@ const budgetId = ref();
//
const PRIMARY_KEY_MAP = {
//
erpRdBudgetEquipmentCostList: 'equipmentCostId',
erpRdBudgetMaterialCostList: 'materialCostId',
erpRdBudgetTravelCostList: 'travelCostId',
erpRdBudgetMeetingCostList: 'meetingCostId',
erpRdBudgetExchangeCostList: 'exchangeCostId',
erpRdBudgetTechCostList: 'techCostId',
erpRdBudgetLaborCostList: 'laborCostId',
erpRdBudgetLiteratureCostList: 'literatureCostId',
erpRdBudgetTestingCostList: 'testingCostId',
erpRdBudgetOtherCostList: 'otherCostId',
@ -973,16 +811,22 @@ async function loadBudgetData() {
await getUsers();
if (!budgetId.value || !['update', 'view', 'approval'].includes(routeParams.value.type as string)) {
handleAddMode();
return;
await handleAddMode();
} else {
await handleLoadMode();
}
await handleLoadMode();
loading.value = false;
}
function handleAddMode() {
async function handleAddMode() {
searchForm.projectCategory = routeParams.value.projectCategory;
queryParams.value.projectCategory = searchForm.projectCategory;
if (isRdOrPreProductionCategory()) {
const rdBudgetDefinitionListData = await getRdBudgetDefinitionList();
if (rdBudgetDefinitionRef.value.budgetDefinitionData) {
rdBudgetDefinitionRef.value.budgetDefinitionData = rdBudgetDefinitionListData.data;
}
}
}
async function handleLoadMode() {
@ -1022,47 +866,15 @@ async function handleRdOrPreProductionData(data: any) {
initOriginalRdData(data);
}
//
const groupedMaterialData = groupByField(data.erpRdBudgetMaterialCostList, 'materialType');
groupedMaterialData[MATERIAL_TYPE.OTHER] = groupedMaterialData[MATERIAL_TYPE.OTHER].map(item => {
return {
...item,
unitPrice: undefined,
amount: undefined,
unitId: undefined,
//
price: item.price ? (item.price / 10000).toFixed(2) : ''
};
});
const groupedTechData = groupByField(data.erpRdBudgetTechCostList, 'techType');
const groupedLaborData = groupByField(data.erpRdBudgetLaborCostList, 'laborType');
const groupedLiteratureData = groupByField(data.erpRdBudgetLiteratureCostList, 'literatureType');
const groupedTravelData = groupByField(data.erpRdBudgetTravelCostList, 'tripType');
//
const categorizedData = {
rdBudgetMainMaterialCostList: handleChangeTypeIds(groupedMaterialData[MATERIAL_TYPE.MAIN] || [], 'erpRdBudgetMaterialCostList', isChangeType),
rdBudgetOtherMaterialCostList: handleChangeTypeIds(groupedMaterialData[MATERIAL_TYPE.OTHER] || [], 'erpRdBudgetMaterialCostList', isChangeType),
rdBudgetTravelCostList: handleChangeTypeIds(groupedTravelData[TRIP_TYPE.TRAVEL] || [], 'erpRdBudgetTravelCostList', isChangeType),
rdBudgetTransportationCostList: handleChangeTypeIds(groupedTravelData[TRIP_TYPE.TRANSPORTATION] || [], 'erpRdBudgetTravelCostList', isChangeType),
rdBudgetTechConsultCostList: handleChangeTypeIds(groupedTechData[TECH_TYPE.TECH_CONSULT] || [], 'erpRdBudgetTechCostList', isChangeType),
rdBudgetExpertMeetingCostList: handleChangeTypeIds(groupedTechData[TECH_TYPE.EXPERT_MEETING] || [], 'erpRdBudgetTechCostList', isChangeType),
rdBudgetExpertCommCostList: handleChangeTypeIds(groupedTechData[TECH_TYPE.EXPERT_COMM] || [], 'erpRdBudgetTechCostList', isChangeType),
rdBudgetLaborCostList: handleChangeTypeIds(groupedLaborData[LABOR_TYPE.LABOR] || [], 'erpRdBudgetLaborCostList', isChangeType),
rdBudgetServiceCostList: handleChangeTypeIds(groupedLaborData[LABOR_TYPE.SERVICE] || [], 'erpRdBudgetLaborCostList', isChangeType),
rdBudgetLiteratureMaterialCostList: handleChangeTypeIds(
groupedLiteratureData[LITERATURE_TYPE.MATERIAL] || [],
'erpRdBudgetLiteratureCostList',
isChangeType
),
rdBudgetLiteratureSoftwareCostList: handleChangeTypeIds(
groupedLiteratureData[LITERATURE_TYPE.SOFTWARE] || [],
'erpRdBudgetLiteratureCostList',
isChangeType
),
rdBudgetLiteratureRetrievalCostList: handleChangeTypeIds(
groupedLiteratureData[LITERATURE_TYPE.DOCUMENT] || [],
'erpRdBudgetLiteratureCostList',
isChangeType
)
rdBudgetExpertCommCostList: handleChangeTypeIds(groupedTechData[TECH_TYPE.EXPERT_COMM] || [], 'erpRdBudgetTechCostList', isChangeType)
};
await nextTick();
@ -1152,36 +964,27 @@ function groupByField(array: any[], field: string): Record<string, any[]> {
function initOriginalRdData(data: any) {
//
oriRdBudgetEquipmentCostList.value = deepClone(data.erpRdBudgetEquipmentCostList);
oriRdBudgetMaterialCostList.value = deepClone(data.erpRdBudgetMaterialCostList);
oriRdBudgetTravelCostList.value = deepClone(data.erpRdBudgetTravelCostList);
oriRdBudgetMeetingCostList.value = deepClone(data.erpRdBudgetMeetingCostList);
oriRdBudgetExchangeCostList.value = deepClone(data.erpRdBudgetExchangeCostList);
oriRdBudgetLaborCostList.value = deepClone(data.erpRdBudgetLaborCostList);
oriRdBudgetTestingCostList.value = deepClone(data.erpRdBudgetTestingCostList);
oriRdBudgetOtherCostList.value = deepClone(data.erpRdBudgetOtherCostList);
//
//
if (data.erpRdBudgetTravelCostList) {
const groupedTravelData = groupByField(data.erpRdBudgetTravelCostList, 'tripType');
oriRdBudgetTravelCostList.value = deepClone(groupedTravelData[TRIP_TYPE.TRAVEL] || []);
oriRdBudgetTransportationCostList.value = deepClone(groupedTravelData[TRIP_TYPE.TRANSPORTATION] || []);
}
//
if (data.erpRdBudgetTechCostList) {
const groupedTechData = groupByField(data.erpRdBudgetTechCostList, 'techType');
oriRdBudgetTechConsultCostList.value = deepClone(groupedTechData[TECH_TYPE.TECH_CONSULT] || []);
oriRdBudgetExpertMeetingCostList.value = deepClone(groupedTechData[TECH_TYPE.EXPERT_MEETING] || []);
oriRdBudgetExpertCommCostList.value = deepClone(groupedTechData[TECH_TYPE.EXPERT_COMM] || []);
}
if (data.erpRdBudgetLaborCostList) {
const groupedLaborData = groupByField(data.erpRdBudgetLaborCostList, 'laborType');
oriRdBudgetLaborCostList.value = deepClone(groupedLaborData[LABOR_TYPE.LABOR] || []);
oriRdBudgetServiceCostList.value = deepClone(groupedLaborData[LABOR_TYPE.SERVICE] || []);
}
if (data.erpRdBudgetLiteratureCostList) {
const groupedLiteratureData = groupByField(data.erpRdBudgetLiteratureCostList, 'literatureType');
oriRdBudgetLiteratureMaterialCostList.value = deepClone(groupedLiteratureData[LITERATURE_TYPE.MATERIAL] || []);
oriRdBudgetLiteratureSofwareCostList.value = deepClone(groupedLiteratureData[LITERATURE_TYPE.SOFTWARE] || []);
}
}
// initOriginalMarketData isChangeType
function initOriginalMarketData(data: any) {
//
oriBudgetMaterialCostList.value = deepClone(data.erpBudgetMaterialCostList);
@ -1191,14 +994,18 @@ function initOriginalMarketData(data: any) {
oriBudgetOtherCostList.value = deepClone(data.erpBudgetOtherCostList);
}
//
//
function assignRdDataToComponents(data: any, categorizedData: any, isChangeType: boolean) {
if (!rdBudgetTableRef.value) return;
// - ID
const formData = isChangeType ? processFormDataForChange(data, 'budgetId') : data;
Object.assign(rdBudgetTableRef.value.rdBudgetInfoForm, formData);
Object.assign(rdBudgetTableRef.value.footerForm, formData);
//
if (rdBudgetDefinitionRef.value.budgetDefinitionData) {
rdBudgetDefinitionRef.value.budgetDefinitionData = data.budgetDefinitionDTOList;
}
//
const budgetDetailList = isChangeType
@ -1210,101 +1017,36 @@ function assignRdDataToComponents(data: any, categorizedData: any, isChangeType:
rdBudgetTableRef.value.budgetDetailData = budgetDetailList;
}
// - categorizedData ori
const equipmentData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetEquipmentCostList, 'erpRdBudgetEquipmentCostList', true)
: oriRdBudgetEquipmentCostList.value;
if (rdEquipmentCostRef.value?.equipmentData) {
rdEquipmentCostRef.value.equipmentData = deepClone(equipmentData);
}
//
if (rdMaterialCostRef.value) {
//
if (rdMaterialCostRef.value.materialData) {
rdMaterialCostRef.value.materialData = deepClone(categorizedData.rdBudgetMainMaterialCostList);
}
const materialData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetMaterialCostList, 'erpRdBudgetMaterialCostList', true)
: oriRdBudgetMaterialCostList.value;
//
if (categorizedData.rdBudgetOtherMaterialCostList.length > 0) {
rdMaterialCostRef.value.otherMaterial = deepClone(categorizedData.rdBudgetOtherMaterialCostList[0]);
}
if (rdMaterialCostRef.value?.materialData) {
rdMaterialCostRef.value.materialData = deepClone(materialData);
}
// //
if (rdTravelMeetingExchangeRef.value) {
//
const travelData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetTravelCostList, 'erpRdBudgetTravelCostList', true)
: oriRdBudgetTravelCostList.value;
if (rdTravelMeetingExchangeRef.value.travelList) {
rdTravelMeetingExchangeRef.value.travelList = deepClone(travelData);
}
//
const meetingData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetMeetingCostList, 'erpRdBudgetMeetingCostList', true)
: oriRdBudgetMeetingCostList.value;
if (rdTravelMeetingExchangeRef.value.meetingList) {
rdTravelMeetingExchangeRef.value.meetingList = deepClone(meetingData);
}
//
const exchangeData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetExchangeCostList, 'erpRdBudgetExchangeCostList', true)
: oriRdBudgetExchangeCostList.value;
if (rdTravelMeetingExchangeRef.value.exchangeList) {
rdTravelMeetingExchangeRef.value.exchangeList = deepClone(exchangeData);
}
}
// /
//
const laborData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetLaborCostList, 'erpRdBudgetLaborCostList', true)
: oriRdBudgetLaborCostList.value;
if (rdLaborServiceRef.value) {
//
if (rdLaborServiceRef.value.techConsultList) {
rdLaborServiceRef.value.techConsultList = deepClone(categorizedData.rdBudgetTechConsultCostList);
}
//
if (rdLaborServiceRef.value.expertMeetingList) {
rdLaborServiceRef.value.expertMeetingList = deepClone(categorizedData.rdBudgetExpertMeetingCostList);
}
//
if (rdLaborServiceRef.value.expertCommList) {
rdLaborServiceRef.value.expertCommList = deepClone(categorizedData.rdBudgetExpertCommCostList);
}
//
if (rdLaborServiceRef.value.laborList) {
rdLaborServiceRef.value.laborList = deepClone(categorizedData.rdBudgetLaborCostList);
}
//
if (rdLaborServiceRef.value.serviceList) {
rdLaborServiceRef.value.serviceList = deepClone(categorizedData.rdBudgetServiceCostList);
rdLaborServiceRef.value.laborList = deepClone(laborData);
}
}
// //
if (rdLiteratureCostRef.value) {
//
if (rdLiteratureCostRef.value.materialsList) {
rdLiteratureCostRef.value.materialsList = deepClone(categorizedData.rdBudgetLiteratureMaterialCostList);
//
if (rdTravelMeetingExchangeRef.value) {
//
if (rdTravelMeetingExchangeRef.value.travelList) {
rdTravelMeetingExchangeRef.value.travelList = deepClone(categorizedData.rdBudgetTravelCostList);
}
//
if (rdLiteratureCostRef.value.softwareList) {
rdLiteratureCostRef.value.softwareList = deepClone(categorizedData.rdBudgetLiteratureSoftwareCostList);
}
//
if (rdLiteratureCostRef.value.literatureRetrieval && categorizedData.rdBudgetLiteratureRetrievalCostList.length > 0) {
rdLiteratureCostRef.value.literatureRetrieval = deepClone(categorizedData.rdBudgetLiteratureRetrievalCostList[0]);
//
if (rdTravelMeetingExchangeRef.value.transportationList) {
rdTravelMeetingExchangeRef.value.transportationList = deepClone(categorizedData.rdBudgetTransportationCostList);
}
}
@ -1317,6 +1059,24 @@ function assignRdDataToComponents(data: any, categorizedData: any, isChangeType:
rdTestingCostRef.value.testData = deepClone(testingData);
}
//
if (rdTechCostRef.value) {
//
if (rdTechCostRef.value.techConsultList) {
rdTechCostRef.value.techConsultList = deepClone(categorizedData.rdBudgetTechConsultCostList);
}
//
if (rdTechCostRef.value.expertMeetingList) {
rdTechCostRef.value.expertMeetingList = deepClone(categorizedData.rdBudgetExpertMeetingCostList);
}
//
if (rdTechCostRef.value.expertCommList) {
rdTechCostRef.value.expertCommList = deepClone(categorizedData.rdBudgetExpertCommCostList);
}
}
//
const otherData = isChangeType
? handleChangeTypeIds(data.erpRdBudgetOtherCostList, 'erpRdBudgetOtherCostList', true)
@ -1373,21 +1133,16 @@ function deepClone<T>(obj: T): T {
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';

@ -0,0 +1,39 @@
<template>
<div class="budget-table-container">
<el-table :data="budgetDefinitionData" style="width: 100%">
<el-table-column prop="serialNumber" label="序号" width="80" />
<el-table-column prop="subjectName" label="预算科目名称" width="200">
<template #default="scope">
<span :style="{ color: scope.row.subjectFlag === '2' ? 'red' : '' }">
{{ scope.row.subjectName }}
</span>
</template>
</el-table-column>
<el-table-column prop="subjectDefinition" label="预算科目定义" min-width="380">
<template #default="scope">
<div style="white-space: pre-line">
{{ scope.row.subjectDefinition }}
</div>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
//
const budgetDefinitionData = ref([]);
//
defineExpose({
budgetDefinitionData
});
</script>
<style scoped>
.budget-table-container {
padding: 20px;
}
</style>

@ -1,7 +1,5 @@
<template>
<div class="budget-table-container">
<h2 class="title-center">项目经费预算表</h2>
<el-form :model="rdBudgetInfoForm" label-width="120px" class="mb-4">
<el-row :gutter="20">
<el-col :span="12">
@ -14,6 +12,11 @@
<el-input v-model="rdBudgetInfoForm.projectCode" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="编制日期">
<el-date-picker v-model="rdBudgetInfoForm.preparationDate" format="YYYY-MM-DD" size="large" style="width:100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目预算期间">
<el-input v-model="rdBudgetInfoForm.duringOperation" />
@ -24,57 +27,76 @@
<el-input v-model="rdBudgetInfoForm.unitName" disabled value="万元" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目经理">
<el-select v-model="rdBudgetInfoForm.managerId" filterable placeholder="请选择项目经理" clearable>
<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="rdBudgetInfoForm.productManagerId" filterable placeholder="请选择部门负责人" clearable>
<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="rdBudgetInfoForm.approveUserId" filterable placeholder="请选择财务负责人" clearable>
<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-form>
<el-table :data="budgetDetailData" style="width: 100%" border show-summary :summary-method="getSummaries">
<el-table
:data="budgetDetailData"
style="width: 100%"
border
show-summary
:summary-method="
() => {
const cols = ['', '合计(万元)', '', '', ''];
cols[cols.length - 3] = formatNumber(totalAmount);
cols[cols.length - 2] = formatNumber(totalProportion) + '%';
return cols;
}
"
>
<el-table-column prop="sortOrder" label="序号" width="80" />
<el-table-column prop="budgetItem" label="预算科目名称" />
<el-table-column prop="budgetCost" label="项目经费" width="260" align="right">
<el-table-column prop="budgetItem" label="预算科目名称" min-width="180" />
<el-table-column prop="budgetCost" label="项目经费" width="200">
<template #default="scope">
<span>{{ formatNumber(scope.row.budgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="budgetCostProportion" label="项目经费占比" width="200">
<template #default="scope">
<span>{{ computeBudgetCostProportion(scope.row) }}%</span>
</template>
</el-table-column>
<el-table-column prop="detailSource" label="数据来源" width="360">
<template #default="scope">
<el-input v-model="scope.row.detailSource" />
</template>
</el-table-column>
</el-table>
<el-form :model="footerForm" label-width="160px" class="mt-4">
<el-row>
<el-col :span="12">
<el-form-item label="编制(项目经理)">
<el-select v-model="footerForm.managerId" filterable placeholder="请选择项目经理" clearable>
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"></el-option>
</el-select>
<!-- <el-select-->
<!-- v-model="footerForm.managerId"-->
<!-- placeholder="请选择项目经理"-->
<!-- filterable-->
<!-- clearable-->
<!-- :filter-method="(query) => (creatorSearchQuery = query)"-->
<!-- :remote="false"-->
<!-- >-->
<!-- <el-option v-for="user in filteredCreatorList" :key="user.id" :label="formatUserDisplay(user)" :value="user.name">-->
<!-- <div class="flex items-center">-->
<!-- <span class="mr-2">{{ user.department }}</span>-->
<!-- <span>{{ user.name }}</span>-->
<!-- </div>-->
<!-- </el-option>-->
<!-- </el-select>-->
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="审核(评审组长)">
<el-select v-model="footerForm.approveUserId" filterable placeholder="请选择评审组长" clearable>
<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-form>
<div class="mt-4 bg-gray-50 p-3 rounded text-left font-semibold">
<p> 序号1-7由表二到表七的数据自动链接生成不需手动填写 </p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed, watch } from 'vue';
import { budgetInfoVO } from '@/api/oa/erp/budgetInfo/types';
//
//
const rdBudgetInfoForm = reactive({
@ -107,10 +129,6 @@ const rdBudgetInfoForm = reactive({
remark: undefined
});
const footerForm = reactive({
managerId: '',
approveUserId: ''
});
// ,
const formatNumber = (value: number) => {
@ -118,6 +136,18 @@ const formatNumber = (value: number) => {
return parseFloat(value).toFixed(2);
};
const computeBudgetCostProportion = (row: budgetInfoVO) => {
const budgetCost = Number(row.budgetCost) || 0;
const totalBudgetCost = Number(totalAmount.value) || 0;
//
let percentage = 0;
if (totalBudgetCost > 0) {
percentage = (budgetCost / totalBudgetCost) * 100;
}
return percentage.toFixed(2);
};
// ,
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
@ -131,64 +161,35 @@ const formatUserDisplay = (user) => {
//
const budgetDetailData = ref([
{ sortOrder: 1, budgetItem: '设备费', budgetCost: 0 },
{ sortOrder: 2, budgetItem: '材料费', budgetCost: 0 },
{ sortOrder: 1, budgetItem: '材料费', budgetCost: 0 },
{ sortOrder: 2, budgetItem: '人工费', budgetCost: 0 },
{ sortOrder: 3, budgetItem: '差旅费', budgetCost: 0 },
{ sortOrder: 4, budgetItem: '会议费', budgetCost: 0 },
{ sortOrder: 5, budgetItem: '国际合作与交流费', budgetCost: 0 },
{ sortOrder: 6, budgetItem: '咨询开发费', budgetCost: 0 },
{ sortOrder: 7, budgetItem: '人工费', budgetCost: 0 },
{ sortOrder: 8, budgetItem: '劳务费', budgetCost: 0 },
{ sortOrder: 9, budgetItem: '资料/文献费', budgetCost: 0 },
{ sortOrder: 10, budgetItem: '测试化验费', budgetCost: 0 },
{ sortOrder: 11, budgetItem: '其他费用', budgetCost: 0 }
{ sortOrder: 4, budgetItem: '测试化验加工费', budgetCost: 0 },
{ sortOrder: 5, budgetItem: '专家咨询费用', budgetCost: 0 },
{ sortOrder: 6, budgetItem: '新产品设计费', budgetCost: 0 },
{ sortOrder: 7, budgetItem: '其他费用', budgetCost: 0 }
]);
//
const totalAmount = computed(() => {
return budgetDetailData.value.slice(0, -1).reduce((sum, item) => sum + Number(item.budgetCost), 0);
return budgetDetailData.value.reduce((sum, item) => {
const budgetCost = Number(item.budgetCost) || 0;
const formattedAmount = parseFloat(budgetCost.toFixed(2));
return sum + formattedAmount;
}, 0);
});
//
const updateCostItem = (subject, amount) => {
const index = budgetDetailData.value.findIndex((item) => item.budgetItem === subject);
if (index !== -1) {
// 使
const updatedItem = { ...budgetDetailData.value[index], amount: amount };
budgetDetailData.value.splice(index, 1, updatedItem);
//
const totalIndex = budgetDetailData.value.length - 1;
const totalRow = { ...budgetDetailData.value[totalIndex], amount: totalAmount.value };
budgetDetailData.value.splice(totalIndex, 1, totalRow);
}
};
//
const updateCostData = (costData) => {
//
updateCostItem('设备费', costData.equipmentCost || 0);
//
updateCostItem('材料费', costData.materialCost || 0);
//
updateCostItem('差旅费', costData.travelCost || 0);
//
updateCostItem('会议费', costData.meetingCost || 0);
//
updateCostItem('国际合作与交流费', costData.internationalExchangeCost || 0);
//
updateCostItem('咨询开发费', costData.consultingDevelopmentCost || 0);
//
updateCostItem('人工费', costData.laborCost || 0);
//
updateCostItem('劳务费', costData.serviceCost || 0);
// /
updateCostItem('资料/文献费', costData.literatureCost || 0);
//
updateCostItem('测试化验费', costData.testingCost || 0);
//
updateCostItem('其他费用', costData.otherCost || 0);
};
const totalProportion = computed(() => {
return budgetDetailData.value.reduce((sum, item) => {
const budgetCost = Number(item.budgetCost) || 0;
const formattedAmount = parseFloat(budgetCost.toFixed(2));
let percentage = 0;
if (totalAmount.value > 0) {
percentage = (budgetCost / totalAmount.value) * 100;
}
return sum + percentage;
}, 0);
});
//
const updateRdBudgetDetailData = (type: string, budgetCost: number) => {
@ -225,15 +226,6 @@ watch(
{ deep: true, immediate: true }
);
//
const getBudgetData = () => {
return {
rdBudgetInfoForm: { ...rdBudgetInfoForm },
footerForm: { ...footerForm },
budgetData: budgetDetailData.value.map((item) => ({ ...item }))
};
};
//
const getSummaries = ({ columns, data }: any) => {
const sums: any[] = [];
@ -242,7 +234,7 @@ const getSummaries = ({ columns, data }: any) => {
sums[index] = '合计(万元)';
return;
}
if (column.property === 'budgetCost') {
if (column.property === 'budgetCost' || column.property === 'budgetCostProportion') {
const values = data.map((item: any) => Number(item[column.property]) || 0);
sums[index] = values.reduce((prev: number, curr: number) => {
const value = Number(curr);
@ -252,8 +244,12 @@ const getSummaries = ({ columns, data }: any) => {
return prev;
}
}, 0);
sums[index] = formatNumber(sums[index]);
rdBudgetInfoForm.budgetCost = sums[index];
if (column.property === 'budgetCost') {
sums[index] = formatNumber(sums[index]);
rdBudgetInfoForm.budgetCost = sums[index];
} else {
sums[index] = formatNumber(sums[index]) + '%';
}
} else {
sums[index] = '';
}
@ -263,12 +259,10 @@ const getSummaries = ({ columns, data }: any) => {
//
defineExpose({
updateCostItem,
updateCostData,
updateRdBudgetDetailData,
budgetDetailData,
rdBudgetInfoForm,
footerForm
totalAmount
});
</script>

@ -1,261 +0,0 @@
<template>
<div class="equipment-cost-container">
<el-card class="table-card">
<template #header>
<div class="card-header">
<span>设备费明细</span>
<div class="flex items-center gap-2">
<span style="white-space: nowrap">行数:</span>
<el-input-number v-model="addRowCount" :min="1" :max="10" size="small" style="width: 80px" />
<el-button type="primary" size="small" @click="handleAddRow">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" size="small" @click="handleBatchDelete" :disabled="selectedRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
</template>
<el-table
:data="equipmentData"
border
style="width: 100%"
show-summary
:summary-method="getSummaries"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="equipmentName" label="设备名称" min-width="200">
<template #default="scope">
<el-input
v-model="scope.row.equipmentName"
placeholder="请输入设备名称"
:data-row-index="equipmentData.findIndex((item) => item.sortOrder === scope.row.sortOrder)"
/>
</template>
</el-table-column>
<el-table-column prop="equipmentSpec" label="设备型号" min-width="280">
<template #default="scope">
<el-input v-model="scope.row.equipmentSpec" placeholder="请输入设备型号" />
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价(元/台件)" width="180">
<template #default="scope">
<el-input-number v-model.number="scope.row.unitPrice" placeholder="单价" :min="0" :precision="2" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="amount" label="数量(台件)" width="180">
<template #default="scope">
<el-input-number v-model.number="scope.row.amount" placeholder="数量" :min="0" :precision="2" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="120">
<template #default="scope">
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetEquipmentCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetEquipmentCost/types';
// IDprops
const props = defineProps<{
projectId: string;
}>();
//
const emit = defineEmits<{
update: [data: { equipmentCost: number }];
}>();
//
const equipmentData = ref<rdBudgetEquipmentCostVO[]>([]);
const toDeletedEquipmentCostIdList = ref([]);
//
const selectedRows = ref<any[]>([]);
//
const addRowCount = ref(1);
// ,
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 handleAddRow = () => {
const currentSortOrder = equipmentData.value && equipmentData.value.length > 0 ? Math.max(...equipmentData.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < addRowCount.value; i++) {
equipmentData.value.push({
sortOrder: currentSortOrder + i + 1,
equipmentName: '',
equipmentSpec: '',
amount: undefined,
unitPrice: undefined,
price: 0
});
}
//
setTimeout(() => {
const tableBody = document.querySelector('.el-table__body-wrapper');
if (tableBody) {
tableBody.scrollTop = tableBody.scrollHeight;
}
//
setTimeout(() => {
const inputElement = document.querySelector(`[data-row-index="${currentSortOrder}"]`);
if (inputElement) {
(inputElement as HTMLInputElement).focus();
}
}, 100);
}, 0);
};
//
const handleSelectionChange = (rows: any[]) => {
selectedRows.value = rows;
};
//
const handleDelete = (index: number, row: rdBudgetEquipmentCostVO) => {
equipmentData.value.splice(index, 1);
//
equipmentData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.equipmentCostId) {
toDeletedEquipmentCostIdList.value.push(row.equipmentCostId);
}
};
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedRows.value.forEach((selectedRow) => {
if (selectedRow.materialCostId) {
toDeletedEquipmentCostIdList.value.push(selectedRow.materialCostId);
}
});
equipmentData.value = equipmentData.value.filter((item) => !selectedRows.value.includes(item));
//
equipmentData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = [];
//
};
//
const calculateAmount = () => {
equipmentData.value.forEach((item) => {
item.price = Math.round((item.amount || 0) * (item.unitPrice || 0));
});
};
//
const getTotalAmount = () => {
const totalBudgetAmount = equipmentData.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
return {
equipmentTotal: totalBudgetAmount
};
};
//
const getSummaries = ({ columns, data }: any) => {
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 5) {
sums[index] = '合计';
return;
}
if (column.property === 'price') {
const values = data.map((item: any) => Number(item[column.property]) || 0);
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] = format2TenThousandNumber(sums[index]);
} else {
sums[index] = '';
}
});
return sums;
};
//
const refreshData = () => {
if (props.projectId) {
}
};
//
const resetData = () => {
equipmentData.value.splice(0, equipmentData.value.length);
};
//
defineExpose({
equipmentData,
toDeletedEquipmentCostIdList,
getTotalAmount
});
</script>
<style scoped>
.equipment-cost-container {
padding: 10px;
}
.table-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
:deep(.el-table__footer-wrapper) {
font-weight: 600;
}
</style>

@ -1,205 +1,5 @@
<template>
<div class="labor-service-container">
<!-- 技术服务费预算明细表 -->
<el-card class="mb-6">
<h3 class="text-lg font-medium mb-4">技术服务费预算明细表</h3>
<!-- 技术咨询开发 -->
<div class="mb-6">
<div class="flex items-center justify-between mb-3">
<h4 class="font-medium text-base">技术咨询开发</h4>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="techAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmTechAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleTechBatchDelete" size="small" :disabled="selectedTechRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="techConsultList" style="width: 100%" border @selection-change="handleTechSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="techContent" label="内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.techContent" placeholder="请输入咨询开发内容" />
</template>
</el-table-column>
<el-table-column prop="unitName" label="单位" width="160">
<template #default="scope">
<el-input v-model="scope.row.unitName" placeholder="请输入单位" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="200">
<template #default="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleTechDelete(scope.$index, scope.row)" icon="Delete">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 技术咨询开发小计 -->
<div class="mt-0 bg-gray-50 p-2 rounded text-right font-medium border-t border-gray-200">小计: {{ techConsultSubtotal.toFixed(2) }} 万元</div>
</div>
<!-- 专家咨询 -->
<div>
<h4 class="font-medium text-base mb-3">专家咨询</h4>
<!-- 会议形式 -->
<div class="mb-6">
<div class="flex items-center justify-between mb-3 ml-4">
<h5 class="font-medium text-sm">1. 会议形式</h5>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="expertMeetingAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmExpertMeetingAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleExpertMeetingBatchDelete" size="small" :disabled="selectedExpertMeetingRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="expertMeetingList" style="width: 100%" border @selection-change="handleExpertMeetingSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="techContent" label="内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.techContent" placeholder="请输入咨询内容"/>
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="专家人数" width="150">
<template #default="scope">
<el-input-number
v-model="scope.row.peopleNumber"
:min="0"
:step="1"
:precision="0"
size="small"
@change="calculateExpertMeetingAmount"
/>
</template>
</el-table-column>
<el-table-column prop="days" label="天数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.days" :min="0" :step="1" :precision="2" size="small" @change="calculateExpertMeetingAmount" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleExpertMeetingDelete(scope.$index, scope.row)" icon="Delete">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 会议形式小计 -->
<div class="mt-0 bg-gray-50 p-2 rounded text-right font-medium border-t border-gray-200">
小计: {{ formatNumber(expertMeetingSubtotal) }} 万元
</div>
</div>
<!-- 通讯形式 -->
<div>
<div class="flex items-center justify-between mb-3 ml-4">
<h5 class="font-medium text-sm">2. 通讯形式</h5>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="expertCommAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmExpertCommAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleExpertCommBatchDelete" size="small" :disabled="selectedExpertCommRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="expertCommList" style="width: 100%" border @selection-change="handleExpertCommSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="techContent" label="内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.techContent" placeholder="请输入咨询内容"/>
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="人数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.peopleNumber" :min="0" :step="1" :precision="0" size="small" @change="calculateExpertCommAmount" />
</template>
</el-table-column>
<el-table-column prop="frequency" label="次数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.frequency" :min="0" :step="1" :precision="2" size="small" @change="calculateExpertCommAmount" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="150">
<template #default="scope">
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleExpertCommDelete(scope.$index, scope.row)" icon="Delete" >
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 通讯形式小计 -->
<div class="mt-0 bg-gray-50 p-2 rounded text-right font-medium border-t border-gray-200">
小计: {{ formatNumber(expertCommSubtotal) }} 万元
</div>
</div>
<!-- 专家咨询合计 -->
<div class="mt-2 bg-gray-50 p-3 rounded text-right font-semibold border-t border-gray-200">
专家咨询合计: {{ formatNumber(expertConsultSubtotal) }} 万元技术服务费合计: {{ formatNumber(techExpertSubtotal) }} 万元
</div>
</div>
</el-card>
<!-- 人工费预算明细表 -->
<el-card class="mb-6">
<div class="flex items-center justify-between mb-4">
@ -222,13 +22,20 @@
</div>
</div>
<el-table v-loading="loading" :data="laborList" style="width: 100%" border @selection-change="handleLaborSelectionChange">
<el-table
v-loading="loading"
:data="laborList"
style="width: 100%"
:summary-method="getSummaries"
show-summary
@selection-change="handleLaborSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="personnelCategory" label="人员类别" width="150">
<template #default="scope">
<el-input v-model="scope.row.personnelCategory" placeholder="请输入人员类别" :data-row-index="scope.$index" />
</template>
<template #default="scope">
<el-input v-model="scope.row.personnelCategory" placeholder="请输入人员类别" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="人数" width="130">
<template #default="scope">
@ -240,7 +47,7 @@
<el-input-number v-model="scope.row.cumulativeTime" :min="0" :step="1" :precision="2" size="small" @change="calculateLaborAmount" />
</template>
</el-table-column>
<el-table-column prop="monthRate" label="月平均投入比例(%)" width="150">
<el-table-column prop="monthRate" label="均投入比例(%)" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.monthRate" :min="0" :max="100" :step="1" :precision="2" size="small" @change="calculateLaborAmount" />
</template>
@ -255,117 +62,20 @@
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column prop="projectPersonnel" label="投入人员" min-width="200">
<el-table-column prop="remark" label="备注" min-width="200">
<template #default="scope">
<el-select
v-model="scope.row.projectPersonnelArray"
placeholder="请选择投入人员"
multiple
filterable
default-first-option
@change="handlePersonnelChange(scope.row)"
>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.nickName" />
</el-select>
<el-input v-model="scope.row.remark" placeholder="备注" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleLaborDelete(scope.$index, scope.row)" icon="Delete" >
删除
</el-button>
<el-button type="danger" size="small" @click="handleLaborDelete(scope.$index, scope.row)" icon="Delete"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 人工费合计 -->
<div class="mt-0 bg-gray-50 p-3 rounded text-right font-semibold border-t border-gray-200">
人工费合计: {{ formatNumber(laborTotal) }} 万元
</div>
</el-card>
<!-- 劳务费预算明细表 -->
<el-card class="mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium">劳务费预算明细表</h3>
<div class="flex gap-2 items-center">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="serviceAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmServiceAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleServiceBatchDelete" size="small" :disabled="selectedServiceRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="serviceList" style="width: 100%" border @selection-change="handleServiceSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="personnelCategory" label="人员类别" width="150">
<template #default="scope">
<el-input v-model="scope.row.personnelCategory" placeholder="请输入人员类别" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="人数" width="130">
<template #default="scope">
<el-input-number v-model="scope.row.peopleNumber" :min="0" :step="1" :precision="0" size="small" @change="calculateServiceAmount" />
</template>
</el-table-column>
<el-table-column prop="cumulativeTime" label="累计时间(月)" width="130">
<template #default="scope">
<el-input-number v-model="scope.row.cumulativeTime" :min="0" :step="1" :precision="2" size="small" @change="calculateServiceAmount" />
</template>
</el-table-column>
<el-table-column prop="monthRate" label="月平均投入比例(%)" width="150">
<template #default="scope">
<el-input-number
v-model="scope.row.monthRate"
:min="0"
:max="100"
:step="1"
:precision="2"
size="small"
@change="calculateServiceAmount"
/>
</template>
</el-table-column>
<el-table-column prop="artificialStandard" label="人工标准(元/人月)" width="150">
<template #default="scope">
<el-input-number
v-model="scope.row.artificialStandard"
:min="0"
:step="10"
:precision="2"
size="small"
@change="calculateServiceAmount"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" min-width="120">
<template #default="scope">
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleServiceDelete(scope.$index, scope.row)" icon="Delete" >
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 劳务费合计 -->
<div class="mt-0 bg-gray-50 p-3 rounded text-right font-semibold border-t border-gray-200">
劳务费合计: {{ formatNumber(serviceTotal) }} 万元
<div class="mt-4 bg-gray-50 p-3 rounded text-left font-semibold">
<p> 人工费的预算人工标准各项目组按照成员的大概平均工资水平进行估算表格所给数据为建议参考数据</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;人员类别按照各项目实际情况填写产品不在本公司内加工和组装的生产加工费已经核算在外协件里不再有生产加工费</p>
</div>
</el-card>
</div>
@ -374,16 +84,7 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetTechCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTechCost/types';
import { rdBudgetLaborCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetLaborCost/types';
import { UserVO } from '@/api/system/user/types';
import { getUserList } from '@/api/system/user';
const TECH_TYPE = {
TECH_CONSULT: '1', //
EXPERT_MEETING: '2', //-
EXPERT_COMM: '3' //-
};
const LABOR_TYPE = {
LABOR_FEE: '1', //
@ -399,9 +100,7 @@ const props = defineProps<{
const emit = defineEmits<{
update: [
data: {
techConsultTotal: number;
laborTotal: number;
serviceTotal: number;
}
];
}>();
@ -409,88 +108,20 @@ const emit = defineEmits<{
//
const loading = ref(false);
const userList = ref<UserVO[]>([]);
/** 查询用户列表 */
const getUsers = async () => {
const res = await getUserList({});
userList.value = res.data;
};
//
const techConsultList = ref<rdBudgetTechCostVO[]>([]);
const selectedTechRows = ref<rdBudgetTechCostVO[]>([]);
const techAddRowCount = ref(1);
// -
const expertMeetingList = ref<rdBudgetTechCostVO[]>([]);
const selectedExpertMeetingRows = ref<rdBudgetTechCostVO[]>([]);
const expertMeetingAddRowCount = ref(1);
// -
const expertCommList = ref<rdBudgetTechCostVO[]>([]);
const selectedExpertCommRows = ref<rdBudgetTechCostVO[]>([]);
const expertCommAddRowCount = ref(1);
const toDeletedTechCostIdList = ref([]);
//
const laborList = ref<rdBudgetLaborCostVO[]>([]);
const selectedLaborRows = ref<rdBudgetLaborCostVO[]>([]);
const laborAddRowCount = ref(1);
//
const serviceList = ref<rdBudgetLaborCostVO[]>([]);
const selectedServiceRows = ref<rdBudgetLaborCostVO[]>([]);
const serviceAddRowCount = ref(1);
const toDeletedLaborCostIdList = ref([]);
//
const techConsultSubtotal = computed(() => {
return techConsultList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const expertMeetingSubtotal = computed(() => {
return expertMeetingList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.price) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
const expertCommSubtotal = computed(() => {
return expertCommList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.price) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
const expertConsultSubtotal = computed(() => {
return expertMeetingSubtotal.value + expertCommSubtotal.value;
});
const techExpertSubtotal = computed(() => {
return (techConsultSubtotal.value || 0) + (expertConsultSubtotal.value || 0);
});
const laborTotal = computed(() => {
return laborList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.price) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
const serviceTotal = computed(() => {
return serviceList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.price) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
// const laborTotal = computed(() => {
// return laborList.value.reduce((sum, item) => {
// const amountInTenThousand = Number(item.price) || 0;
// const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
// return sum + formattedAmount;
// }, 0);
// });
// ,
const formatNumber = (value: number) => {
@ -504,26 +135,35 @@ const format2TenThousandNumber = (value: number) => {
return (parseFloat(value) / 10000).toFixed(2);
};
//
const calculateExpertMeetingAmount = () => {
expertMeetingList.value.forEach((item) => {
const expertCount = item.peopleNumber || 0;
const days = item.days || 0;
// 1<2,**600>=2,(*2*600+*(-2)*300)
if (days < 2) {
item.price = expertCount * days * 600;
//
const getSummaries = (param: any) => {
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 2) {
sums[index] = '合计';
return;
}
if (column.property === 'price') {
const values = data.map((item: any) => 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] = format2TenThousandNumber(sums[index]);
} else {
sums[index] = '';
}
} else {
item.price = expertCount * 2 * 600 + expertCount * (days - 2) * 300;
sums[index] = '';
}
});
};
//
const calculateExpertCommAmount = () => {
expertCommList.value.forEach((item) => {
// = * * 70 / 10000
item.price = (item.peopleNumber || 0) * (item.frequency || 0) * 70;
});
return sums;
};
//
@ -534,187 +174,13 @@ const calculateLaborAmount = () => {
});
};
//
const calculateServiceAmount = () => {
serviceList.value.forEach((item) => {
// = * * * / 10000 / 100
item.price = ((item.peopleNumber || 0) * (item.cumulativeTime || 0) * (item.monthRate || 0) * (item.artificialStandard || 0)) / 100;
});
};
const getLaborAmount = () => {
const laborTotal = laborList.value.reduce((sum, item) => sum + Number(item.price), 0)
return {
techConsultTotal: ((techConsultSubtotal.value || 0) + (expertConsultSubtotal.value || 0)) * 10000,
laborTotal: (Number(laborTotal.value) || 0) * 10000,
serviceTotal: (Number(serviceTotal.value) || 0) * 10000
laborTotal: laborTotal
};
};
//
const confirmTechAdd = () => {
const currentSortOrder =
techConsultList.value && techConsultList.value.length > 0 ? Math.max(...techConsultList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < techAddRowCount.value; i++) {
const newItem: rdBudgetTechCostVO = {
sortOrder: currentSortOrder + i + 1,
techType: TECH_TYPE.TECH_CONSULT,
techContent: '',
unitId: undefined,
remark: '',
price: 0
};
techConsultList.value.push(newItem);
}
};
const handleTechSelectionChange = (selection: rdBudgetTechCostVO[]) => {
selectedTechRows.value = selection;
};
//
const handleTechDelete = (index: number, row: rdBudgetTechCostVO) => {
techConsultList.value.splice(index, 1);
//
techConsultList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.techCostId) {
toDeletedTechCostIdList.value.push(row.techCostId);
}
};
//
const handleTechBatchDelete = () => {
if (selectedTechRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedTechRows.value.forEach((selectedRow) => {
if (selectedRow.techCostId) {
toDeletedTechCostIdList.value.push(selectedRow.techCostId);
}
});
techConsultList.value = techConsultList.value.filter((item) => !selectedTechRows.value.includes(item));
//
techConsultList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedTechRows.value = [];
};
// -
const confirmExpertMeetingAdd = () => {
const currentSortOrder =
expertMeetingList.value && expertMeetingList.value.length > 0 ? Math.max(...expertMeetingList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < expertMeetingAddRowCount.value; i++) {
const newItem: rdBudgetTechCostVO = {
sortOrder: currentSortOrder + i + 1,
techType: TECH_TYPE.EXPERT_MEETING,
techContent: '',
peopleNumber: undefined,
days: undefined,
price: 0
};
expertMeetingList.value.push(newItem);
}
calculateExpertMeetingAmount();
};
const handleExpertMeetingSelectionChange = (selection: rdBudgetTechCostVO[]) => {
selectedExpertMeetingRows.value = selection;
};
//
const handleExpertMeetingDelete = (index: number, row: rdBudgetTechCostVO) => {
expertMeetingList.value.splice(index, 1);
//
expertMeetingList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.techCostId) {
toDeletedTechCostIdList.value.push(row.techCostId);
}
};
//
const handleExpertMeetingBatchDelete = () => {
if (selectedExpertMeetingRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedExpertMeetingRows.value.forEach((selectedRow) => {
if (selectedRow.techCostId) {
toDeletedTechCostIdList.value.push(selectedRow.techCostId);
}
});
expertMeetingList.value = expertMeetingList.value.filter((item) => !selectedExpertMeetingRows.value.includes(item));
//
expertMeetingList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedExpertMeetingRows.value = [];
};
// -
const confirmExpertCommAdd = () => {
const currentSortOrder =
expertCommList.value && expertCommList.value.length > 0 ? Math.max(...expertCommList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < expertCommAddRowCount.value; i++) {
const newItem: rdBudgetTechCostVO = {
sortOrder: currentSortOrder + i + 1,
techType: TECH_TYPE.EXPERT_COMM,
techContent: '',
peopleNumber: undefined,
frequency: undefined,
price: 0
};
expertCommList.value.push(newItem);
}
calculateExpertCommAmount();
};
const handleExpertCommSelectionChange = (selection: rdBudgetTechCostVO[]) => {
selectedExpertCommRows.value = selection;
};
//
const handleExpertCommDelete = (index: number, row: rdBudgetTechCostVO) => {
expertCommList.value.splice(index, 1);
//
expertCommList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.techCostId) {
toDeletedTechCostIdList.value.push(row.techCostId);
}
};
//
const handleExpertCommBatchDelete = () => {
if (selectedExpertCommRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedExpertCommRows.value.forEach((selectedRow) => {
if (selectedRow.techCostId) {
toDeletedTechCostIdList.value.push(selectedRow.techCostId);
}
});
expertCommList.value = expertCommList.value.filter((item) => !selectedExpertCommRows.value.includes(item));
//
expertCommList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedExpertCommRows.value = [];
};
//
const confirmLaborAdd = () => {
const currentSortOrder = laborList.value && laborList.value.length > 0 ? Math.max(...laborList.value.map((item) => item.sortOrder)) : 0;
@ -729,18 +195,12 @@ const confirmLaborAdd = () => {
monthRate: 0,
artificialStandard: 0,
price: 0,
projectPersonnel: '',
projectPersonnelArray: []
remark: ''
};
laborList.value.push(newItem);
}
};
//
const handlePersonnelChange = (row: rdBudgetLaborCostVO) => {
row.projectPersonnel = row.projectPersonnelArray?.join(',') || '';
};
const handleLaborSelectionChange = (selection: rdBudgetLaborCostVO[]) => {
selectedLaborRows.value = selection;
};
@ -777,60 +237,6 @@ const handleLaborBatchDelete = () => {
selectedLaborRows.value = [];
};
//
const confirmServiceAdd = () => {
const currentSortOrder = serviceList.value && serviceList.value.length > 0 ? Math.max(...serviceList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < serviceAddRowCount.value; i++) {
const newItem: rdBudgetLaborCostVO = {
sortOrder: currentSortOrder + i + 1,
laborType: LABOR_TYPE.SERVICE_FEE,
personnelCategory: '',
peopleNumber: undefined,
cumulativeTime: undefined,
monthRate: undefined,
artificialStandard: undefined,
price: 0
};
serviceList.value.push(newItem);
}
};
const handleServiceSelectionChange = (selection: rdBudgetLaborCostVO[]) => {
selectedServiceRows.value = selection;
};
//
const handleServiceDelete = (index: number, row: rdBudgetLaborCostVO) => {
serviceList.value.splice(index, 1);
//
serviceList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.laborCostId) {
toDeletedLaborCostIdList.value.push(row.laborCostId);
}
};
//
const handleServiceBatchDelete = () => {
if (selectedServiceRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedServiceRows.value.forEach((selectedRow) => {
if (selectedRow.laborCostId) {
toDeletedLaborCostIdList.value.push(selectedRow.laborCostId);
}
});
serviceList.value = serviceList.value.filter((item) => !selectedServiceRows.value.includes(item));
//
serviceList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedServiceRows.value = [];
};
// projectId
watch(
() => props.projectId,
@ -841,18 +247,9 @@ watch(
{ immediate: true }
);
onMounted(() => {
getUsers();
});
//
defineExpose({
techConsultList,
expertMeetingList,
expertCommList,
toDeletedTechCostIdList,
laborList,
serviceList,
toDeletedLaborCostIdList,
getLaborAmount
});

@ -1,385 +0,0 @@
<template>
<div class="literature-cost-container">
<!-- 资料费预算明细表 -->
<el-card class="mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium">资料费</h3>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="materialsAddRowCount" :min="1" :max="10" :step="1" size="small" />
<el-button type="primary" @click="confirmMaterialsAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleMaterialBatchDelete" size="small" :disabled="selectedMaterialsRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="materialsList" style="width: 100%" border @selection-change="handleMaterialsSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="itemDesc" label="项目" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.itemDesc" placeholder="请输入资料项目名称" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleMaterialDelete(scope.$index, scope.row)" icon="Delete">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 资料费合计 -->
<div class="mt-2 bg-gray-50 p-2 rounded text-right font-medium">合计: {{ formatNumber(materialsTotal) }} 万元</div>
</el-card>
<!-- 文献检索费预算明细表 -->
<el-card class="mb-6">
<h3 class="text-lg font-medium mb-4">文献检索费</h3>
<el-table :data="[literatureRetrieval]" style="width: 100%" border>
<el-table-column prop="itemDesc" label="项目" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.itemDesc" disabled />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 专用软件购买费预算明细表 -->
<el-card>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium">专用软件购买费</h3>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="softwareAddRowCount" :min="1" :max="10" :step="1" size="small" />
<el-button type="primary" @click="confirmSoftwareAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleSoftwareBatchDelete" size="small" :disabled="selectedSoftwareRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="softwareList" style="width: 100%" border @selection-change="handleSoftwareSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="itemDesc" label="项目" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.itemDesc" placeholder="请输入软件名称" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleSoftwareDelete(scope.$index, scope.row)" icon="Delete">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 专用软件购买费合计 -->
<div class="mt-2 bg-gray-50 p-2 rounded text-right font-medium">合计: {{ formatNumber(softwareTotal) }} 万元</div>
</el-card>
<!-- 总合计 -->
<div class="mt-4 bg-gray-100 p-4 rounded text-right font-semibold">
资料文献费总计: {{ formatNumber(totalAmount) }}
万元
</div>
<!-- 对话框组件 -->
<!-- 对话框组件已移除直接在界面上添加行数输入框 -->
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetLiteratureCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetLiteratureCost/types';
const LITERATURE_TYPE = {
MATERIAL: '1', //
DOCUMENT: '2', //
SOFTWARE: '3' //
};
// Props
const props = defineProps<{
projectId?: string;
}>();
//
const loading = ref(false);
//
const materialsList = ref<rdBudgetLiteratureCostVO[]>([]);
const selectedMaterialsRows = ref<rdBudgetLiteratureCostVO[]>([]);
const materialsAddRowCount = ref(1);
//
const literatureRetrieval = ref<rdBudgetLiteratureCostVO>({
literatureType: LITERATURE_TYPE.DOCUMENT,
itemDesc: '文献检索费',
price: 0
});
//
const softwareList = ref<rdBudgetLiteratureCostVO[]>([]);
const selectedSoftwareRows = ref<rdBudgetLiteratureCostVO[]>([]);
const softwareAddRowCount = ref(1);
const toDeletedLiteratureCostIdList = ref([]);
//
const materialsTotal = computed(() => {
return materialsList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const softwareTotal = computed(() => {
return softwareList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const totalAmount = computed(() => {
return (materialsTotal.value || 0) + (literatureRetrieval.value.price || 0) + (softwareTotal.value || 0);
});
const getLiteratureCost = () => {
return {
literatureTotal: (totalAmount.value || 0) * 10000
};
};
// ,
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 confirmMaterialsAdd = () => {
const currentSortOrder = materialsList.value && materialsList.value.length > 0 ? Math.max(...materialsList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < materialsAddRowCount.value; i++) {
const newItem: rdBudgetLiteratureCostVO = {
sortOrder: currentSortOrder + i + 1,
literatureType: LITERATURE_TYPE.MATERIAL,
itemDesc: '',
price: 0
};
materialsList.value.push(newItem);
}
//
setTimeout(() => {
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"] .el-input__inner`);
if (firstNewInput) {
(firstNewInput as HTMLInputElement).focus();
}
}, 100);
};
const handleMaterialsSelectionChange = (selection: rdBudgetLiteratureCostVO[]) => {
selectedMaterialsRows.value = selection;
};
//
const handleMaterialDelete = (index: number, row: rdBudgetLiteratureCostVO) => {
materialsList.value.splice(index, 1);
//
materialsList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.literatureCostId) {
toDeletedLiteratureCostIdList.value.push(row.literatureCostId);
}
};
//
const handleMaterialBatchDelete = () => {
if (selectedMaterialsRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedMaterialsRows.value.forEach((selectedRow) => {
if (selectedRow.literatureCostId) {
toDeletedLiteratureCostIdList.value.push(selectedRow.literatureCostId);
}
});
materialsList.value = materialsList.value.filter((item) => !selectedMaterialsRows.value.includes(item));
//
materialsList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedMaterialsRows.value = [];
};
//
const confirmSoftwareAdd = () => {
const currentSortOrder = softwareList.value && softwareList.value.length > 0 ? Math.max(...softwareList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < softwareAddRowCount.value; i++) {
const newItem: rdBudgetLiteratureCostVO = {
sortOrder: currentSortOrder + i + 1,
literatureType: LITERATURE_TYPE.SOFTWARE,
itemDesc: '',
price: 0
};
softwareList.value.push(newItem);
}
//
setTimeout(() => {
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"] .el-input__inner`);
if (firstNewInput) {
(firstNewInput as HTMLInputElement).focus();
}
}, 100);
};
const handleSoftwareSelectionChange = (selection: rdBudgetLiteratureCostVO[]) => {
selectedSoftwareRows.value = selection;
};
//
const handleSoftwareDelete = (index: number, row: rdBudgetLiteratureCostVO) => {
softwareList.value.splice(index, 1);
//
softwareList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.literatureCostId) {
toDeletedLiteratureCostIdList.value.push(row.literatureCostId);
}
};
//
const handleSoftwareBatchDelete = () => {
if (selectedSoftwareRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedSoftwareRows.value.forEach((selectedRow) => {
if (selectedRow.literatureCostId) {
toDeletedLiteratureCostIdList.value.push(selectedRow.literatureCostId);
}
});
softwareList.value = softwareList.value.filter((item) => !selectedSoftwareRows.value.includes(item));
//
softwareList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedSoftwareRows.value = [];
};
// projectId
watch(
() => props.projectId,
(newProjectId) => {
if (newProjectId) {
}
},
{ immediate: true }
);
//
defineExpose({
materialsList,
softwareList,
literatureRetrieval,
toDeletedLiteratureCostIdList,
getLiteratureCost
});
</script>
<style scoped>
.literature-cost-container {
padding: 0;
}
:deep(.el-table) {
margin-bottom: 16px;
}
:deep(.el-input-number) {
width: 100%;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.bg-gray-50 {
background-color: #f5f7fa;
}
.bg-gray-100 {
background-color: #f0f2f5;
}
.p-2 {
padding: 8px;
}
.p-4 {
padding: 16px;
}
.rounded {
border-radius: 4px;
}
.text-right {
text-align: right;
}
.font-medium {
font-weight: 500;
}
.font-semibold {
font-weight: 600;
}
</style>

@ -22,12 +22,17 @@
</div>
</template>
<el-table :data="allMaterialData" border style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" :selectable="checkSelectable" />
<el-table :data="materialData" border style="width: 100%" @selection-change="handleSelectionChange" :summary-method="getSummaries" show-summary>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="materialName" label="材料类型" width="180">
<template #default="scope">
<el-input v-model="scope.row.materialType" placeholder="请输入材料类型" />
</template>
</el-table-column>
<el-table-column prop="materialName" label="材料名称" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.materialName" placeholder="请输入材料名称" :disabled="scope.row.materialName === '其他材料费'" />
<el-input v-model="scope.row.materialName" placeholder="请输入材料名称" />
</template>
</el-table-column>
@ -41,25 +46,12 @@
default-first-option
style="width: 100%"
clearable
v-if="scope.row.materialType === MATERIAL_TYPE.MAIN"
>
<el-option v-for="option in unitOptions" :key="option.unitId" :label="option.unitName" :value="option.unitId" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价(元/单位数量)" width="180">
<template #default="scope">
<el-input-number
v-model.number="scope.row.unitPrice"
placeholder="单价"
:min="0"
:precision="2"
size="small"
@change="calculateAmount"
v-if="scope.row.materialType === MATERIAL_TYPE.MAIN"
/>
</template>
</el-table-column>
<el-table-column prop="amount" label="购置数量" width="180">
<template #default="scope">
<el-input-number
@ -69,32 +61,29 @@
:precision="2"
size="small"
@change="calculateAmount"
v-if="scope.row.materialType === MATERIAL_TYPE.MAIN"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<el-table-column prop="unitPrice" label="单价(万元)" width="180">
<template #default="scope">
<el-input-number v-if="scope.row.materialType === MATERIAL_TYPE.OTHER" v-model="scope.row.price" size="small" :min="0" :precision="2" />
<!-- 其他情况直接显示格式化后的值 -->
<span v-else>
{{ format2TenThousandNumber(scope.row.price) }}
</span>
<el-input-number v-model.number="scope.row.unitPrice" placeholder="单价" :min="0" :precision="2" size="small" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="price" label="总金额(万元)" width="160">
<template #default="scope">
{{ formatNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column prop="remark" label="预算价格依据" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.remark" placeholder="请输入预算价格依据" />
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.materialType === MATERIAL_TYPE.MAIN"
type="danger"
size="small"
@click="handleDelete(scope.$index, scope.row)"
icon="Delete"
>
删除
</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
@ -131,11 +120,8 @@
</table>
</div>
-->
<div class="mt-4 bg-gray-50 p-3 rounded">
<div class="flex justify-end gap-8">
<span>主要材料费小计: {{ format2TenThousandNumber(mainMaterialTotalAmount) }} 万元</span>
<span class="font-semibold">合计: {{ formatNumber(totalAmount) }} 万元</span>
</div>
<div class="mt-4 bg-gray-50 p-3 rounded text-left">
<p><b>填写说明</b>指研究开发活动过程中投入的各种半成品原材料辅助材料等费用半成品:包括自制半成品外协加工件原材料外购件带料外协的外购件辅助材料现场使用的耗材</p>
</div>
</el-card>
</div>
@ -153,34 +139,11 @@ const props = defineProps<{
projectId: string;
}>();
const MATERIAL_TYPE = {
MAIN: '1', //
OTHER: '2' //
};
//
const materialData = ref<rdBudgetMaterialCostVO[]>([]);
const toDeletedMaterialCostIdList = ref([]);
//
const otherMaterialData = ref<rdBudgetMaterialCostVO>();
const defaultOtherMaterial = ref({
sortOrder: 1,
materialName: '其他材料费',
materialType: MATERIAL_TYPE.OTHER,
unitId: undefined,
unitPrice: undefined,
amount: undefined,
price: 0
});
//
const otherMaterial = ref(defaultOtherMaterial);
// +
const allMaterialData = computed(() => {
return [...materialData.value, otherMaterial.value];
});
const unitOptions = ref<UnitInfoVO[]>([]);
@ -188,13 +151,12 @@ const unitOptions = ref<UnitInfoVO[]>([]);
// const unitOptions = ref(['kg', 'g', 't', 'm', 'cm', 'mm', 'm²', 'm³', '', '', '', '', '', '', '', '', '', '', '', '']);
//
const totalAmount = ref(0);
const totalQuantity = ref(0);
//
const mainMaterialTotalAmount = computed(() => {
return materialData.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
// const totalAmount = computed(() => {
// return materialData.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
// });
//
const selectedRows = ref<any[]>([]);
@ -214,31 +176,6 @@ const format2TenThousandNumber = (value: number) => {
return (parseFloat(value) / 10000).toFixed(2);
};
const checkSelectable = (row: rdBudgetMaterialCostVO, index: number) => {
return row.materialType === MATERIAL_TYPE.MAIN;
};
// otherMaterialData
watch(
otherMaterialData,
(newVal) => {
if (newVal) {
otherMaterial.value = newVal;
} else {
otherMaterial.value = defaultOtherMaterial.value;
}
},
{ immediate: true }
); // immediate: true
//
watch(
() => [materialData, otherMaterial],
() => {
calculateTotal();
},
{ deep: true }
);
//
const addSpecifiedRows = async () => {
@ -253,14 +190,13 @@ const addSpecifiedRows = async () => {
materialData.value.push({
sortOrder: currentSortOrder + i + 1,
materialName: '',
materialType: MATERIAL_TYPE.MAIN,
materialType: '',
unitId: undefined,
unitPrice: undefined,
amount: undefined,
price: 0
});
}
otherMaterial.value.sortOrder = currentSortOrder + 1 + addRowCount.value;
// DOM
await nextTick();
scrollToBottom();
@ -290,8 +226,6 @@ const handleDelete = (index: number, row: rdBudgetMaterialCostVO) => {
if (row.materialCostId) {
toDeletedMaterialCostIdList.value.push(row.materialCostId);
}
otherMaterial.value.sortOrder = otherMaterial.value.sortOrder - 1;
calculateTotal();
};
//
@ -311,8 +245,6 @@ const handleBatchDelete = () => {
materialData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
otherMaterial.value.sortOrder = otherMaterial.value.sortOrder - selectedRows.value.length;
calculateTotal();
selectedRows.value = [];
};
@ -322,24 +254,44 @@ const calculateAmount = () => {
materialData.value.forEach((item) => {
item.price = (item.amount || 0) * (item.unitPrice || 0);
});
//
// otherMaterial.value.price = (otherMaterial.value.amount || 0) * (otherMaterial.value.unitPrice || 0);
//
calculateTotal();
};
//
const calculateTotal = () => {
const mainAmount = parseFloat(((Number(mainMaterialTotalAmount.value) || 0) / 10000).toFixed(2));
const otherAmount = parseFloat((Number(otherMaterial.value.price) || 0).toFixed(2));
totalAmount.value = (mainAmount + otherAmount).toFixed(2);
};
//
const getSummaries = (param: any) => {
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 2) {
sums[index] = '合计(万元)';
return;
}
if (column.property === 'price') {
const values = data.map((item: any) => 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] = '';
}
} else {
sums[index] = '';
}
});
return sums;
}
//
const getTotalAmount = () => {
return (Number(totalAmount.value) || 0) * 10000;
const totalAmount = materialData.value.reduce((sum, item) => sum + Number(item.price), 0)
return totalAmount * 10000;
};
//
@ -361,32 +313,13 @@ const refreshData = () => {
//
const resetData = () => {
materialData.value.splice(0, materialData.value.length);
totalAmount.value = 0;
totalQuantity.value = 0;
//
// otherMaterial.unit = '';
// otherMaterial.quantity = 0;
// otherMaterial.unitPrice = 0;
// otherMaterial.amount = 0;
};
//
const getFormData = () => {
return {
materialData: [...materialData.value],
otherMaterial: { ...otherMaterial },
totalAmount: totalAmount.value,
totalQuantity: totalQuantity.value
};
};
//
defineExpose({
materialData,
otherMaterial,
otherMaterialData,
allMaterialData,
toDeletedMaterialCostIdList,
getTotalAmount
});

@ -21,12 +21,26 @@
</div>
</div>
<el-table v-loading="loading" :data="otherCostList" style="width: 100%" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table
v-loading="loading"
:data="otherCostList"
style="width: 100%"
show-summary
:summary-method="
() => {
const cols = ['', '', '合计(万元)', ''];
cols[cols.length - 1] = formatNumber(totalAmount);
return cols;
}
"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" :selectable="checkSelectable" />
<el-table-column prop="sortOrder" label="序号" width="80" type="index" />
<el-table-column prop="itemDesc" label="项目" min-width="200">
<el-table-column prop="itemDesc" label="费用项目" width="200">
<template #default="scope">
<el-input v-model="scope.row.itemDesc" placeholder="请输入项目名称" :data-row-index="scope.$index" />
<el-input v-model="scope.row.itemDesc" placeholder="请输入项目名称" :data-row-index="scope.$index" :disabled="scope.row.sortOrder <= 7"/>
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="180">
@ -34,20 +48,21 @@
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column prop="remark" label="费用说明" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.remark" placeholder="请输入费用说明" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete" v-if="scope.row.sortOrder > 7">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 合计行 -->
<div class="mt-4 bg-gray-50 p-3 rounded text-right font-semibold">合计: {{ formatNumber(totalAmount) }} 万元</div>
</el-card>
<!-- 添加对话框已移除直接在界面上添加行数输入框 -->
</div>
</template>
@ -63,11 +78,26 @@ const props = defineProps<{
//
const loading = ref(false);
const otherCostList = ref<rdBudgetOtherCostVO[]>([]);
// const otherCostList = ref<rdBudgetOtherCostVO[]>([]);
const selectedRows = ref<rdBudgetOtherCostVO[]>([]);
const addRowCount = ref(1);
const toDeletedOtherCostIdList = ref([]);
//
const otherCostList = ref([
{ sortOrder: 1, itemDesc: '安装费' },
{ sortOrder: 2, itemDesc: '资料/文献费' },
{ sortOrder: 3, itemDesc: '会议费' },
{ sortOrder: 4, itemDesc: '劳务费' },
{ sortOrder: 5, itemDesc: '运输装卸费' },
{ sortOrder: 6, itemDesc: '中介代理费' },
{ sortOrder: 7, itemDesc: '其他费用' }
]);
const checkSelectable = (row: rdBudgetOtherCostVO, index: number) => {
return row.sortOrder > 7;
};
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00';

@ -0,0 +1,653 @@
<template>
<div class="labor-service-container">
<!-- 技术服务费预算明细表 -->
<el-card class="mb-6">
<h3 class="text-lg font-medium mb-4">专家咨询费预算明细表</h3>
<div>
<!-- 会议形式 -->
<div class="mb-6">
<div class="flex items-center justify-between mb-3 ml-4">
<h5 class="font-medium text-sm">1. 会议形式</h5>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="expertMeetingAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmExpertMeetingAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleExpertMeetingBatchDelete" size="small" :disabled="selectedExpertMeetingRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="expertMeetingList"
style="width: 100%"
:summary-method="getExpertMeetingSummaries"
show-summary
border
@selection-change="handleExpertMeetingSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="techContent" label="内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.techContent" placeholder="请输入咨询内容" />
</template>
</el-table-column>
<el-table-column prop="professionalDirection" label="专业方向" width="180">
<template #default="scope">
<el-input v-model="scope.row.professionalDirection" placeholder="请输入专业方向" />
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="专家人数" width="150">
<template #default="scope">
<el-input-number
v-model="scope.row.peopleNumber"
:min="0"
:step="1"
:precision="0"
size="small"
/>
</template>
</el-table-column>
<el-table-column prop="days" label="天数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.days" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleExpertMeetingDelete(scope.$index, scope.row)" icon="Delete"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 通讯形式 -->
<div>
<div class="flex items-center justify-between mb-3 ml-4">
<h5 class="font-medium text-sm">2. 通讯形式</h5>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="expertCommAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmExpertCommAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleExpertCommBatchDelete" size="small" :disabled="selectedExpertCommRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="expertCommList"
style="width: 100%"
:summary-method="getExpertCommSummaries"
show-summary
@selection-change="handleExpertCommSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="techContent" label="内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.techContent" placeholder="请输入咨询内容" />
</template>
</el-table-column>
<el-table-column prop="professionalDirection" label="专业方向" width="180">
<template #default="scope">
<el-input v-model="scope.row.professionalDirection" placeholder="请输入专业方向" />
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="专家人数" width="150">
<template #default="scope">
<el-input-number
v-model="scope.row.peopleNumber"
:min="0"
:step="1"
:precision="0"
size="small"
/>
</template>
</el-table-column>
<el-table-column prop="frequency" label="次数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.frequency" :min="0" :step="1" :precision="0" size="small" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleExpertCommDelete(scope.$index, scope.row)" icon="Delete"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 专家咨询合计 -->
<div class="mt-2 bg-gray-50 p-3 rounded text-right font-semibold border-t border-gray-200">
专家咨询合计: {{ formatNumber(expertConsultSubtotal) }} 万元
</div>
</div>
</el-card>
<!-- 新产品设计费明细表 -->
<el-card class="mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium">新产品设计费</h3>
<div class="flex gap-2 items-center">
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="techAddRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmTechAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleTechBatchDelete" size="small" :disabled="selectedTechRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="techConsultList"
:summary-method="getProductDesignSummaries"
show-summary
style="width: 100%"
border
@selection-change="handleTechSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="techContent" label="内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.techContent" placeholder="请输入咨询开发内容" />
</template>
</el-table-column>
<el-table-column prop="unitName" label="单位" width="160">
<template #default="scope">
<el-input v-model="scope.row.unitName" placeholder="请输入单位" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="200">
<template #default="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleTechDelete(scope.$index, scope.row)" icon="Delete"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetTechCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTechCost/types';
const TECH_TYPE = {
TECH_CONSULT: '1', //
EXPERT_MEETING: '2', //-
EXPERT_COMM: '3' //-
};
// Props
const props = defineProps<{
projectId?: string;
}>();
// Emits
const emit = defineEmits<{
update: [
data: {
techConsultTotal: number;
laborTotal: number;
serviceTotal: number;
}
];
}>();
//
const loading = ref(false);
//
const techConsultList = ref<rdBudgetTechCostVO[]>([]);
const selectedTechRows = ref<rdBudgetTechCostVO[]>([]);
const techAddRowCount = ref(1);
// -
const expertMeetingList = ref<rdBudgetTechCostVO[]>([]);
const selectedExpertMeetingRows = ref<rdBudgetTechCostVO[]>([]);
const expertMeetingAddRowCount = ref(1);
// -
const expertCommList = ref<rdBudgetTechCostVO[]>([]);
const selectedExpertCommRows = ref<rdBudgetTechCostVO[]>([]);
const expertCommAddRowCount = ref(1);
const toDeletedTechCostIdList = ref([]);
//
const getProductDesignSummaries = (param: any) => {
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 2) {
sums[index] = '合计';
return;
}
if (column.property === 'price') {
const values = data.map((item: any) => 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] = '';
}
} else {
sums[index] = '';
}
});
return sums;
};
//
const techConsultSubtotal = computed(() => {
return techConsultList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const expertMeetingSubtotal = computed(() => {
return expertMeetingList.value.reduce((sum, item) => {
const amountInTenThousand = Number(item.price) || 0;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
const expertCommSubtotal = computed(() => {
return expertCommList.value.reduce((sum, item) => {
const amountInTenThousand = Number(item.price) || 0;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
const expertConsultSubtotal = computed(() => {
return expertMeetingSubtotal.value + expertCommSubtotal.value;
});
// ,
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 getExpertMeetingSummaries = (param: any) => {
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 2) {
sums[index] = '小计';
return;
}
if (column.property === 'price') {
const values = data.map((item: any) => 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] = '';
}
} else {
sums[index] = '';
}
});
return sums;
};
//
// const calculateExpertMeetingAmount = () => {
// expertMeetingList.value.forEach((item) => {
// const expertCount = item.peopleNumber || 0;
// const days = item.days || 0;
// // 1<2,**600>=2,(*2*600+*(-2)*300)
// if (days < 2) {
// item.price = expertCount * days * 600;
// } else {
// item.price = expertCount * 2 * 600 + expertCount * (days - 2) * 300;
// }
// });
// };
//
const getExpertCommSummaries = (param: any) => {
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 2) {
sums[index] = '小计';
return;
}
if (column.property === 'price') {
const values = data.map((item: any) => 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] = '';
}
} else {
sums[index] = '';
}
});
return sums;
};
//
// const calculateExpertCommAmount = () => {
// expertCommList.value.forEach((item) => {
// // = * * 70 / 10000
// item.price = (item.peopleNumber || 0) * (item.frequency || 0) * 70;
// });
// };
const getTechAmount = () => {
return {
expertConsultTotal: (Number(expertConsultSubtotal.value) || 0) * 10000,
techConsultTotal: (Number(techConsultSubtotal.value) || 0) * 10000
};
};
//
const confirmTechAdd = () => {
const currentSortOrder =
techConsultList.value && techConsultList.value.length > 0 ? Math.max(...techConsultList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < techAddRowCount.value; i++) {
const newItem: rdBudgetTechCostVO = {
sortOrder: currentSortOrder + i + 1,
techType: TECH_TYPE.TECH_CONSULT,
techContent: '',
unitId: undefined,
remark: '',
price: 0
};
techConsultList.value.push(newItem);
}
};
const handleTechSelectionChange = (selection: rdBudgetTechCostVO[]) => {
selectedTechRows.value = selection;
};
//
const handleTechDelete = (index: number, row: rdBudgetTechCostVO) => {
techConsultList.value.splice(index, 1);
//
techConsultList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.techCostId) {
toDeletedTechCostIdList.value.push(row.techCostId);
}
};
//
const handleTechBatchDelete = () => {
if (selectedTechRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedTechRows.value.forEach((selectedRow) => {
if (selectedRow.techCostId) {
toDeletedTechCostIdList.value.push(selectedRow.techCostId);
}
});
techConsultList.value = techConsultList.value.filter((item) => !selectedTechRows.value.includes(item));
//
techConsultList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedTechRows.value = [];
};
// -
const confirmExpertMeetingAdd = () => {
const currentSortOrder =
expertMeetingList.value && expertMeetingList.value.length > 0 ? Math.max(...expertMeetingList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < expertMeetingAddRowCount.value; i++) {
const newItem: rdBudgetTechCostVO = {
sortOrder: currentSortOrder + i + 1,
techType: TECH_TYPE.EXPERT_MEETING,
techContent: '',
peopleNumber: undefined,
days: undefined,
price: 0
};
expertMeetingList.value.push(newItem);
}
};
const handleExpertMeetingSelectionChange = (selection: rdBudgetTechCostVO[]) => {
selectedExpertMeetingRows.value = selection;
};
//
const handleExpertMeetingDelete = (index: number, row: rdBudgetTechCostVO) => {
expertMeetingList.value.splice(index, 1);
//
expertMeetingList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.techCostId) {
toDeletedTechCostIdList.value.push(row.techCostId);
}
};
//
const handleExpertMeetingBatchDelete = () => {
if (selectedExpertMeetingRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedExpertMeetingRows.value.forEach((selectedRow) => {
if (selectedRow.techCostId) {
toDeletedTechCostIdList.value.push(selectedRow.techCostId);
}
});
expertMeetingList.value = expertMeetingList.value.filter((item) => !selectedExpertMeetingRows.value.includes(item));
//
expertMeetingList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedExpertMeetingRows.value = [];
};
// -
const confirmExpertCommAdd = () => {
const currentSortOrder =
expertCommList.value && expertCommList.value.length > 0 ? Math.max(...expertCommList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < expertCommAddRowCount.value; i++) {
const newItem: rdBudgetTechCostVO = {
sortOrder: currentSortOrder + i + 1,
techType: TECH_TYPE.EXPERT_COMM,
techContent: '',
peopleNumber: undefined,
frequency: undefined,
price: 0
};
expertCommList.value.push(newItem);
}
};
const handleExpertCommSelectionChange = (selection: rdBudgetTechCostVO[]) => {
selectedExpertCommRows.value = selection;
};
//
const handleExpertCommDelete = (index: number, row: rdBudgetTechCostVO) => {
expertCommList.value.splice(index, 1);
//
expertCommList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.techCostId) {
toDeletedTechCostIdList.value.push(row.techCostId);
}
};
//
const handleExpertCommBatchDelete = () => {
if (selectedExpertCommRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedExpertCommRows.value.forEach((selectedRow) => {
if (selectedRow.techCostId) {
toDeletedTechCostIdList.value.push(selectedRow.techCostId);
}
});
expertCommList.value = expertCommList.value.filter((item) => !selectedExpertCommRows.value.includes(item));
//
expertCommList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedExpertCommRows.value = [];
};
// projectId
watch(
() => props.projectId,
(newProjectId) => {
if (newProjectId) {
}
},
{ immediate: true }
);
//
defineExpose({
techConsultList,
expertMeetingList,
expertCommList,
toDeletedTechCostIdList,
getTechAmount
});
</script>
<style scoped>
.labor-service-container {
padding: 0;
}
:deep(.el-table) {
margin-bottom: 16px;
}
:deep(.el-input-number) {
width: 100%;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.bg-gray-50 {
background-color: #f5f7fa;
}
.p-2 {
padding: 8px;
}
.p-3 {
padding: 12px;
}
.rounded {
border-radius: 4px;
}
.text-right {
text-align: right;
}
.font-medium {
font-weight: 500;
}
.font-semibold {
font-weight: 600;
}
</style>

@ -23,17 +23,31 @@
</div>
</template>
<el-table :data="testData" border style="width: 100%" row-key="id" @selection-change="handleSelectionChange">
<el-table
:data="testData"
border
style="width: 100%"
row-key="id"
show-summary
:summary-method="
() => {
const cols = ['', '', '合计(万元)', '', '', '', '', ''];
cols[cols.length - 1] = formatNumber(totalAmount);
return cols;
}
"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="testingContent" label="测试项目" min-width="180">
<el-table-column prop="testingContent" label="测试化验加工的内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.testingContent" placeholder="请输入测试项目" :data-row-index="scope.$index" />
<el-input v-model="scope.row.testingContent" placeholder="请输入测试化验加工内容" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="testingUnitName" label="测试化验单位" width="180">
<el-table-column prop="testingUnitName" label="测试化验加工单位" width="200">
<template #default="scope">
<el-input v-model="scope.row.testingUnitName" placeholder="请输入测试化验单位" />
<el-input v-model="scope.row.testingUnitName" placeholder="请输入测试化验加工单位" />
</template>
</el-table-column>
<el-table-column prop="unitName" label="单位" width="180">
@ -64,10 +78,8 @@
</el-table-column>
</el-table>
<div class="total-row">
<div class="total-item">
<span>测试化验加工费总计(万元){{ formatNumber(totalAmount) }}</span>
</div>
<div class="mt-4 bg-gray-50 p-3 rounded text-left font-semibold">
<p>是指支付给外单位包括公司内部经济核算单位的检验测试化验及加工等费用</p>
</div>
</el-card>
</div>
@ -215,7 +227,6 @@ const refreshData = () => {
const resetData = () => {
testData.value.splice(0, testData.value.length);
totalAmount.value = 0;
idCounter = 1;
};
//

@ -39,10 +39,10 @@
'',
'',
'',
'合计(万元)',
format2TenThousandNumber(travelTransportTotal),
format2TenThousandNumber(travelAccommodationTotal),
format2TenThousandNumber(travelSubsidyTotal),
'合计',
formatNumber(travelTransportTotal),
formatNumber(travelAccommodationTotal),
formatNumber(travelSubsidyTotal),
formatNumber(travelSubtotalTotal),
''
];
@ -111,118 +111,21 @@
</el-table>
</el-card>
<!-- 会议费预算明细表 -->
<el-card class="mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium">会议费预算明细表</h3>
<div class="flex gap-2 items-center">
<span class="mr-2">行数:</span>
<el-input-number v-model="meetingAddRowCount" :min="1" :max="10" :step="1" size="small" style="width: 100px" />
<el-button type="primary" @click="confirmMeetingAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleMeetingBatchDelete" size="small" :disabled="selectedMeetingRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="meetingList"
style="width: 100%"
border
@selection-change="handleMeetingSelectionChange"
show-summary
:summary-method="
() => {
const cols = ['', '', '', '', '', '', '', '', '合计(万元)', '', ''];
cols[cols.length - 2] = formatNumber(meetingFeeTotal);
return cols;
}
"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" type="index" width="80" />
<!-- 复合列公司组织会议 -->
<el-table-column label="公司组织会议">
<el-table-column prop="meetingContent" label="会议内容" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.meetingContent" placeholder="请输入会议内容" size="small" />
</template>
</el-table-column>
<el-table-column prop="rentalFee" label="场地日租金(元)" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.rentalFee" :min="0" :precision="2" :step="10" size="small" @change="calculateMeetingFee" />
</template>
</el-table-column>
<el-table-column prop="dailyExpense" label="日均杂费(元)" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.dailyExpense" :min="0" :precision="2" :step="10" size="small" @change="calculateMeetingFee" />
</template>
</el-table-column>
<el-table-column prop="days" label="天数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.days" :min="0" :precision="0" :step="1" size="small" @change="calculateMeetingFee" />
</template>
</el-table-column>
<el-table-column prop="expertExpense" label="专家交通住宿费" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.expertExpense" :min="0" :precision="2" :step="10" size="small" @change="calculateMeetingFee" />
</template>
</el-table-column>
</el-table-column>
<!-- 复合列外出参加会议 -->
<el-table-column label="外出参加会议">
<el-table-column prop="peopleNumber" label="人数" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.peopleNumber" :min="0" :precision="0" :step="1" size="small" @change="calculateMeetingFee" />
</template>
</el-table-column>
<el-table-column prop="perPersonExpense" label="人均交费" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.perPersonExpense" :min="0" :precision="2" :step="10" size="small" @change="calculateMeetingFee" />
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="meetingPrice" label="会议费(万元)" width="110">
<template #default="scope">
{{ format2TenThousandNumber(scope.row.meetingPrice) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleMeetingDelete(scope.$index, scope.row)" icon="Delete">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 国际交流费预算明细表 -->
<!-- 交通算明细表 -->
<el-card>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium">国际交流费预算明细表</h3>
<h3 class="text-lg font-medium">交通费明细表</h3>
<div class="flex gap-2 items-center">
<span class="mr-2">行数:</span>
<el-input-number v-model="internationalAddRowCount" :min="1" :max="10" :step="1" size="small" style="width: 100px" />
<el-button type="primary" @click="confirmInternationalAdd" size="small">
<el-input-number v-model="transportationAddRowCount" :min="1" :max="10" :step="1" size="small" style="width: 100px" />
<el-button type="primary" @click="confirmTransportationAdd" size="small">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" @click="handleExchangeBatchDelete" size="small" :disabled="selectedInternationalRows.length === 0">
<el-button type="danger" @click="handleTransportationBatchDelete" size="small" :disabled="selectedTransportationRows.length === 0">
<el-icon>
<Delete />
</el-icon>
@ -233,97 +136,61 @@
<el-table
v-loading="loading"
:data="exchangeList"
:data="transportationList"
style="width: 100%"
border
@selection-change="handleInternationalSelectionChange"
@selection-change="handleTransportationSelectionChange"
show-summary
:summary-method="
() => {
const cols = ['', '', '', '', '', '', '', '', '合计(万元)', ''];
cols[cols.length - 1] = formatNumber(internationalSubtotalTotal);
const cols = ['', '', '', '合计(万元)', '',''];
cols[cols.length - 1] = formatNumber(transportationSubtotalTotal);
return cols;
}
"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="sortOrder" label="序号" type="index" width="80" />
<el-table-column prop="communicationType" label="合作交流类型" min-width="180">
<el-table-column prop="tripLocation" label="出行起止地点" width="200">
<template #default="scope">
<el-select
v-model="scope.row.communicationType"
placeholder="请选择交流类型"
size="small"
filterable
allow-create
default-first-option
@change="calculateInternationalSubtotal"
>
<el-option label="学术访问" value="学术访问" />
<el-option label="技术培训" value="技术培训" />
<el-option label="国际会议" value="国际会议" />
<el-option label="合作研发" value="合作研发" />
<el-option label="出国考察" value="出国考察" />
</el-select>
<el-input v-model="scope.row.tripLocation" placeholder="请输入出行起止地点" />
</template>
</el-table-column>
<el-table-column prop="countryRegion" label="国家和地区" width="120">
<el-table-column prop="reason" label="出行任务" min-width="180">
<template #default="scope">
<el-input v-model="scope.row.countryRegion" placeholder="请输入国家和地区" size="small" />
<el-input v-model="scope.row.reason" type="textarea" placeholder="请输入出行任务" size="small" :rows="2" resize="vertical" />
</template>
</el-table-column>
<el-table-column prop="institution" label="机构" width="150">
<template #default="scope">
<el-input v-model="scope.row.institution" placeholder="请输入机构" size="small" />
</template>
</el-table-column>
<el-table-column prop="peopleNumber" label="人数(人)" width="120">
<el-table-column prop="subtotalCosts" label="金额(元)" width="180">
<template #default="scope">
<el-input-number
v-model="scope.row.peopleNumber"
:min="0"
:precision="0"
:step="1"
size="small"
@change="calculateInternationalSubtotal"
/>
</template>
</el-table-column>
<el-table-column prop="days" label="时间(天)" width="150">
<template #default="scope">
<el-input-number v-model="scope.row.days" :min="0" :precision="2" :step="1" size="small" @change="calculateInternationalSubtotal" />
</template>
</el-table-column>
<el-table-column prop="travelAccommodationExpense" label="往返路费及住宿费" width="180">
<template #default="scope">
<el-input-number
v-model="scope.row.travelAccommodationExpense"
v-model="scope.row.subtotalCosts"
:min="0"
:precision="2"
:step="10"
size="small"
@change="calculateInternationalSubtotal"
/>
</template>
</el-table-column>
<el-table-column prop="subsidy" label="补贴(元)" width="120">
<template #default="scope">
{{ formatNumber(scope.row.subsidy) }}
</template>
</el-table-column>
<el-table-column prop="price" label="小计(万元)" width="120">
<template #default="scope">
{{ format2TenThousandNumber(scope.row.price) }}
{{ format2TenThousandNumber(scope.row.subtotalCosts) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" size="small" @click="handleExchangeDelete(scope.$index, scope.row)" icon="Delete">
<el-button type="danger" size="small" @click="handleTransportationDelete(scope.$index, scope.row)" icon="Delete">
删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<div class="mt-4 bg-gray-50 p-3 rounded text-left font-semibold">
<p>开展实验试验考察业务调研学术交流等发生的外埠差旅费市内交通费用等开支标准按照公司有关规定执行</p>
</div>
</div>
</template>
@ -331,14 +198,17 @@
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
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';
// Props
const props = defineProps<{
projectId?: string;
}>();
const TRIP_TYPE = {
TRAVEL: '1', //
TRANSPORTATION: '2' //
};
//
const loading = ref(false);
@ -348,17 +218,11 @@ const selectedTravelRows = ref<rdBudgetTravelCostVO[]>([]);
const travelAddRowCount = ref(1);
const toDeletedTravelCostIdList = ref([]);
//
const meetingList = ref<rdBudgetMeetingCostVO[]>([]);
const selectedMeetingRows = ref<rdBudgetMeetingCostVO[]>([]);
const meetingAddRowCount = ref(1);
const toDeletedMeetingCostIdList = ref([]);
//
const exchangeList = ref<rdBudgetExchangeCostVO[]>([]);
const selectedInternationalRows = ref<rdBudgetExchangeCostVO[]>([]);
const internationalAddRowCount = ref(1);
const toDeletedExchangeCostIdList = ref([]);
//
const transportationList = ref<rdBudgetTravelCostVO[]>([]);
const selectedTransportationRows = ref<rdBudgetTravelCostVO[]>([]);
const transportationAddRowCount = ref(1);
const toDeletedTransportationCostIdList = ref([]);
//
//
@ -385,19 +249,10 @@ const travelSubtotalTotal = computed(() => {
}, 0);
});
// 2
const meetingFeeTotal = computed(() => {
return meetingList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.meetingPrice) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
// 2
const internationalSubtotalTotal = computed(() => {
return exchangeList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.price) || 0) / 10000;
// 2
const transportationSubtotalTotal = computed(() => {
return transportationList.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.subtotalCosts) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
@ -419,47 +274,19 @@ const format2TenThousandNumber = (value: number) => {
const calculateTravelSubtotal = () => {
travelList.value.forEach((item) => {
// 宿 = * * 宿
item.stayCosts = (item.peopleNumber || 0) * (item.days || 0) * (item.stayStandard || 0);
item.stayCosts = (item.peopleNumber || 0) * (item.frequency || 0) * (item.days || 0) * (item.stayStandard || 0);
// = 90 * *
item.subsidyCosts = 90 * (item.peopleNumber || 0) * (item.days || 0);
item.subsidyCosts = 90 * (item.peopleNumber || 0) * (item.frequency || 0) * (item.days || 0);
// = ( + 宿 + ) / 10000
item.subtotalCosts = (item.travelExpenses || 0) + item.stayCosts + item.subsidyCosts;
});
};
//
const calculateMeetingFee = () => {
meetingList.value.forEach((item) => {
// (( + ) * + 宿) / 10000
// ( * ) / 10000
//=+
item.meetingPrice =
((item.rentalFee || 0) + (item.dailyExpense || 0)) * (item.days || 0) +
(item.expertExpense || 0) +
(item.peopleNumber || 0) * (item.perPersonExpense || 0);
});
};
//
const calculateInternationalSubtotal = () => {
exchangeList.value.forEach((item) => {
// =40**
if (item.communicationType === '出国考察') {
item.subsidy = 40 * (item.peopleNumber || 0) * (item.days || 0);
} else if (!item.subsidy) {
// 0
item.subsidy = 0;
}
// = (宿 + ) / 10000
item.price = (item.travelAccommodationExpense || 0) + (item.subsidy || 0);
});
};
const getTravelMeetingExchangeAmount = () => {
const getTravelAmount = () => {
return {
travelSubtotalTotal: (Number(travelSubtotalTotal.value) || 0) * 10000,
meetingFeeTotal: (Number(meetingFeeTotal.value) || 0) * 10000,
internationalSubtotalTotal: (Number(internationalSubtotalTotal.value) || 0) * 10000
transportationSubtotalTotal: (Number(transportationSubtotalTotal.value) || 0) * 10000
};
};
@ -469,12 +296,13 @@ const confirmTravelAdd = () => {
for (let i = 0; i < travelAddRowCount.value; i++) {
const newItem: rdBudgetTravelCostVO = {
sortOrder: currentSortOrder + i + 1,
tripType: TRIP_TYPE.TRAVEL,
tripLocation: '',
reason: '',
frequency: undefined,
peopleNumber: undefined,
days: undefined,
stayStandard: undefined,
stayStandard: 180,
travelExpenses: undefined,
stayCosts: 0,
subsidyCosts: 0,
@ -540,23 +368,19 @@ const handleTravelBatchDelete = () => {
selectedTravelRows.value = [];
};
//
const confirmMeetingAdd = () => {
const currentSortOrder = meetingList.value && meetingList.value.length > 0 ? Math.max(...meetingList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < meetingAddRowCount.value; i++) {
const newItem: rdBudgetMeetingCostVO = {
//
const confirmTransportationAdd = () => {
const currentSortOrder = transportationList.value && transportationList.value.length > 0 ? Math.max(...transportationList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < transportationAddRowCount.value; i++) {
const newItem: rdBudgetTravelCostVO = {
sortOrder: currentSortOrder + i + 1,
meetingContent: '',
rentalFee: undefined,
dailyExpense: undefined,
days: undefined,
expertExpense: undefined,
peopleNumber: undefined,
perPersonExpense: undefined,
meetingPrice: 0
tripType: TRIP_TYPE.TRANSPORTATION,
tripLocation: '',
reason: '',
};
meetingList.value.push(newItem);
transportationList.value.push(newItem);
}
//
@ -568,83 +392,7 @@ const confirmMeetingAdd = () => {
//
const rows = document.querySelectorAll('.el-table__body tr');
const newRow = rows[rows.length - meetingAddRowCount.value];
if (newRow) {
const firstInput = newRow.querySelector('input, select');
if (firstInput) {
firstInput.focus();
}
}
}, 0);
};
const handleMeetingSelectionChange = (selection: rdBudgetMeetingCostVO[]) => {
selectedMeetingRows.value = selection;
};
//
const handleMeetingDelete = (index: number, row: rdBudgetMeetingCostVO) => {
meetingList.value.splice(index, 1);
//
meetingList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.meetingCostId) {
toDeletedMeetingCostIdList.value.push(row.meetingCostId);
}
calculateMeetingFee();
};
//
const handleMeetingBatchDelete = () => {
if (selectedMeetingRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedMeetingRows.value.forEach((selectedRow) => {
if (selectedRow.meetingCostId) {
toDeletedMeetingCostIdList.value.push(selectedRow.meetingCostId);
}
});
meetingList.value = meetingList.value.filter((item) => !selectedMeetingRows.value.includes(item));
//
meetingList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
calculateMeetingFee();
selectedMeetingRows.value = [];
};
//
const confirmInternationalAdd = () => {
const currentSortOrder = exchangeList.value && exchangeList.value.length > 0 ? Math.max(...exchangeList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < internationalAddRowCount.value; i++) {
const newItem: rdBudgetExchangeCostVO = {
sortOrder: currentSortOrder + i + 1,
communicationType: '',
countryRegion: '',
institution: '',
peopleNumber: undefined,
days: undefined,
travelAccommodationExpense: undefined,
subsidy: undefined,
price: 0
};
exchangeList.value.push(newItem);
}
//
setTimeout(() => {
const table = document.querySelector('.el-table__body-wrapper');
if (table) {
table.scrollTop = table.scrollHeight;
}
//
const rows = document.querySelectorAll('.el-table__body tr');
const newRow = rows[rows.length - internationalAddRowCount.value];
const newRow = rows[rows.length - transportationAddRowCount.value];
if (newRow) {
const firstInput = newRow.querySelector('input, select');
if (firstInput) {
@ -655,42 +403,40 @@ const confirmInternationalAdd = () => {
};
const handleInternationalSelectionChange = (selection: rdBudgetExchangeCostVO[]) => {
selectedInternationalRows.value = selection;
const handleTransportationSelectionChange = (selection: rdBudgetTravelCostVO[]) => {
selectedTransportationRows.value = selection;
};
//
const handleExchangeDelete = (index: number, row: rdBudgetExchangeCostVO) => {
exchangeList.value.splice(index, 1);
const handleTransportationDelete = (index: number, row: rdBudgetTravelCostVO) => {
transportationList.value.splice(index, 1);
//
exchangeList.value.forEach((item, index) => {
transportationList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.exchangeCostId) {
toDeletedExchangeCostIdList.value.push(row.exchangeCostId);
if (row.travelCostId) {
toDeletedTravelCostIdList.value.push(row.travelCostId);
}
calculateInternationalSubtotal();
};
//
const handleExchangeBatchDelete = () => {
if (selectedInternationalRows.value.length === 0) {
const handleTransportationBatchDelete = () => {
if (selectedTransportationRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedInternationalRows.value.forEach((selectedRow) => {
if (selectedRow.exchangeCostId) {
toDeletedExchangeCostIdList.value.push(selectedRow.exchangeCostId);
selectedTransportationRows.value.forEach((selectedRow) => {
if (selectedRow.travelCostId) {
toDeletedTravelCostIdList.value.push(selectedRow.travelCostId);
}
});
exchangeList.value = exchangeList.value.filter((item) => !selectedInternationalRows.value.includes(item));
transportationList.value = transportationList.value.filter((item) => !selectedTransportationRows.value.includes(item));
//
exchangeList.value.forEach((item, index) => {
transportationList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
calculateInternationalSubtotal();
selectedInternationalRows.value = [];
selectedTransportationRows.value = [];
};
// projectId
@ -707,11 +453,8 @@ watch(
defineExpose({
travelList,
toDeletedTravelCostIdList,
meetingList,
toDeletedMeetingCostIdList,
exchangeList,
toDeletedExchangeCostIdList,
getTravelMeetingExchangeAmount
transportationList,
getTravelAmount
});
</script>

Loading…
Cancel
Save