1.0.42.1:

前端:
   市场项目预算保存和更新功能完成;
   研发项目预算保存功能完成
dev
xs 2 months ago
parent 58a29f63af
commit e4e009717c

@ -19,6 +19,11 @@ export interface rdBudgetMaterialCostVO {
*/
materialName: string;
/**
*
*/
materialType: string;
/**
* ,base_unit_info
*/

@ -134,6 +134,37 @@ export interface budgetInfoVO {
*/
remark: string;
erpBudgetDetailList?: [];
erpBudgetMaterialCostList?: [];
erpBudgetLaborCostList?: [];
erpBudgetInstallCostList?: [];
erpBudgetTravelCostList?: [];
erpBudgetOtherCostList?: [];
erpRdBudgetEquipmentCostList?: [];
erpRdBudgetMaterialCostList?: [];
erpRdBudgetTravelCostList?: [];
erpRdBudgetMeetingCostList?: [];
erpRdBudgetExchangeCostList?: [];
erpRdBudgetTechCostList?: [];
erpRdBudgetLaborCostList?: [];
erpRdBudgetLiteratureCostList?: [];
erpRdBudgetTestingCostList?: [];
erpRdBudgetOtherCostList?: [];
}
export interface budgetInfoForm extends BaseEntity {
@ -272,10 +303,70 @@ export interface budgetInfoForm extends BaseEntity {
*/
remark?: string;
erpBudgetDetailList?: [];
erpBudgetMaterialCostList?: [];
erpBudgetLaborCostList?: [];
erpBudgetInstallCostList?: [];
erpBudgetTravelCostList?: [];
erpBudgetOtherCostList?: [];
toDeletedMaterialCostIdList?: [];
toDeletedLaborCostIdList?: [];
toDeletedInstallCostIdList?: [];
toDeletedTravelCostIdList?: [];
toDeletedOtherCostIdList?: [];
erpRdBudgetEquipmentCostList?: [];
erpRdBudgetMaterialCostList?: [];
erpRdBudgetTravelCostList?: [];
erpRdBudgetMeetingCostList?: [];
erpRdBudgetExchangeCostList?: [];
erpRdBudgetTechCostList?: [];
erpRdBudgetLaborCostList?: [];
erpRdBudgetLiteratureCostList?: [];
erpRdBudgetTestingCostList?: [];
erpRdBudgetOtherCostList?: [];
toDeletedRdEquipmentCostIdList?: [];
toDeletedRdMaterialCostIdList?: [];
toDeletedRdTravelCostIdList?: [];
toDeletedRdMeetingCostIdList?: [];
toDeletedRdExchangeCostIdList?: [];
toDeletedRdTechCostIdList?: [];
toDeletedRdLaborCostIdList?: [];
toDeletedRdLiteratureCostIdList?: [];
toDeletedRdTestingCostIdList?: [];
toDeletedRdOtherCostIdList?: [];
}
export interface budgetInfoQuery extends PageQuery {
/**
* ID
*/
@ -406,6 +497,3 @@ export interface budgetInfoQuery extends PageQuery {
*/
params?: any;
}

@ -209,6 +209,19 @@ export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
});
};
/**
*
* @param query
*/
export const getUserList = (query: UserQuery): AxiosPromise<UserVO[]> => {
return request({
url: '/system/user/getUserList',
method: 'get',
params: query
});
};
export default {
listUser,
getUser,
@ -225,5 +238,6 @@ export default {
getAuthRole,
updateAuthRole,
deptTreeSelect,
listUserByDeptId
listUserByDeptId,
getUserList
};

@ -116,6 +116,12 @@ export const constantRoutes: RouteRecordRaw[] = [
name: 'ErpProjectPlanView',
meta: { title: '项目计划查看', activeMenu: '/oa/erp/erpProjectPlan' }
},
{
path: 'budgetInfo/edit',
component: () => import('@/views/oa/erp/budgetInfo/edit.vue'),
name: 'BudgetEdit',
meta: { title: '项目预算申请', activeMenu: '/oa/erp/budgetInfo' }
},
{
path: 'erpProjectPlan/gantt/:projectPlanId',
@ -156,6 +162,7 @@ export const constantRoutes: RouteRecordRaw[] = [
}
]
},
{
path: '/oa/erp',
component: Layout,

@ -0,0 +1,47 @@
/**
*
* @param {any} obj1
* @param {any} obj2
* @returns {Boolean}
*/
export function isDeepEqual(obj1: any, obj2: any) {
// 检查类型是否相同
if (typeof obj1 !== typeof obj2) return false;
// 如果不是对象或为空,直接比较值
if (obj1 === null || obj2 === null || typeof obj1 !== 'object') {
return obj1 === obj2;
}
// 处理数组
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) return false;
// 先对数组进行排序比较(如果需要基于特定字段比较)
const sortedObj1 = [...obj1].sort((a, b) => a.sortNumber - b.sortNumber);
const sortedObj2 = [...obj2].sort((a, b) => a.sortNumber - b.sortNumber);
// 递归比较每个元素
for (let i = 0; i < sortedObj1.length; i++) {
if (!isDeepEqual(sortedObj1[i], sortedObj2[i])) {
return false;
}
}
return true;
}
// 处理普通对象
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
// 检查所有键值对是否相同
for (const key of keys1) {
if (!keys2.includes(key) || !isDeepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}

@ -45,43 +45,47 @@
<!-- 中间标签页区域 -->
<!-- 研发项目预算 -->
<el-card class="mb-6" v-if="searchForm.projectCategory === PROJECT_CATEGORY.RD">
<el-card class="mb-6" v-if="searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="预算表" name="budgetTable">
<RdBudgetTable ref="rdBudgetTableRef" :projectId="searchForm.projectId" :costData="costData" :projectInfo="projectInfo" />
</el-tab-pane>
<el-tab-pane label="设备费" name="equipmentCost">
<RdEquipmentCost ref="rdEquipmentCostRef" :projectId="searchForm.projectId" @update="handleEquipmentCostUpdate" />
<RdEquipmentCost ref="rdEquipmentCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="材料费" name="materialCost">
<RdMaterialCost ref="rdMaterialCostRef" :projectId="searchForm.projectId" @update="handleMaterialCostUpdate" />
<RdMaterialCost ref="rdMaterialCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="会议差旅交流" name="travelMeetingExchange">
<RdTravelMeetingExchange ref="rdTravelMeetingExchangeRef" :projectId="searchForm.projectId" @update="handleTravelMeetingExchangeUpdate" />
<RdTravelMeetingExchange ref="rdTravelMeetingExchangeRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="人工劳务咨询" name="laborService">
<RdLaborService ref="rdLaborServiceRef" :projectId="searchForm.projectId" @update="handleLaborServiceUpdate" />
<RdLaborService ref="rdLaborServiceRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="资料文献费" name="literatureCost">
<RdLiteratureCost ref="rdLiteratureCostRef" :projectId="searchForm.projectId" @update="handleLiteratureCostUpdate" />
<RdLiteratureCost ref="rdLiteratureCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="测试化验费" name="testingCost">
<RdTestingCost ref="rdTestingCostRef" :projectId="searchForm.projectId" @update="handleTestingCostUpdate" />
<RdTestingCost ref="rdTestingCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
<el-tab-pane label="其他" name="otherCost">
<RdOtherCost ref="rdOtherCostRef" :projectId="searchForm.projectId" @update="handleOtherCostUpdate" />
<RdOtherCost ref="rdOtherCostRef" :projectId="searchForm.projectId" />
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 市场项目预算 -->
<el-card class="tab-card" shadow="never" v-if="searchForm.projectCategory === PROJECT_CATEGORY.MARKET">
<el-card
class="mb-6"
shadow="never"
v-if="searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART"
>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="预算表" name="budget">
<BudgetTable ref="budgetTableRef" :projectInfo="projectInfo" />
</el-tab-pane>
<el-tab-pane label="材料费" name="material">
<MaterialCost ref="materialCostRef" />
<MaterialCost ref="materialCostRef" :budgetId="budgetId" />
</el-tab-pane>
<el-tab-pane label="人工费" name="labor">
<LaborCost ref="laborCostRef" />
@ -147,17 +151,20 @@
</el-dialog>
<!-- 提交审批组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<submitVerify ref="submitVerifyRef" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
<script setup lang="ts" name="BudgetInfoEdit">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { listProjectInfo, addErpBudgetInfo, updateErpBudgetInfo, getErpBudgetInfo } from '@/api/oa/erp/budgetInfo';
import { ProjectInfoVO, ProjectInfoQuery } from '@/api/oa/erp/projectInfo/types';
import { isDeepEqual } from '@/utils/objHandle';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { business_direction, project_status, project_category } = toRefs<any>(
@ -215,9 +222,32 @@ const installationCostRef = ref();
const travelCostRef = ref();
const otherCostRef = ref();
const oriBudgetMaterialCostList = ref<budgetMaterialCostVO[]>([]);
const oriBudgetLaborCostList = ref<budgetLaborCostVO[]>([]);
const oriBudgetInstallCostList = ref<budgetInstallCostVO[]>([]);
const oriBudgetTravelCostList = ref<budgetTravelCostVO[]>([]);
const oriBudgetOtherCostList = ref<budgetOtherCostVO[]>([]);
const oriRdBudgetEquipmentCostList = ref<rdBudgetEquipmentCostVO[]>([]);
const oriRdBudgetMaterialCostList = ref<rdBudgetMaterialCostVO[]>([]);
const oriRdBudgetTravelCostList = ref<rdBudgetTravelCostVO[]>([]);
const oriRdBudgetMeetingCostList = ref<rdBudgetMeetingCostVO[]>([]);
const oriRdBudgetExchangeCostList = ref<rdBudgetExchangeCostVO[]>([]);
const oriRdBudgetTechConsultCostList = ref<rdBudgetTechCostVO[]>([]); //
const oriRdBudgetExpertMeetingCostList = ref<rdBudgetTechCostVO[]>([]); //
const oriRdBudgetExpertCommCostList = ref<rdBudgetTechCostVO[]>([]); //
const oriRdBudgetLaborCostList = ref<rdBudgetLaborCostVO[]>([]); //
const oriRdBudgetServiceCostList = ref<rdBudgetLaborCostVO[]>([]); //
const oriRdBudgetLiteratureMaterialCostList = ref<rdBudgetLiteratureCostVO[]>([]); //
const oriRdBudgetLiteratureSofwareCostList = ref<rdBudgetLiteratureCostVO[]>([]); //
const oriRdBudgetTestingCostList = ref<rdBudgetTestingCostVO[]>([]);
const oriRdBudgetOtherCostList = ref<rdBudgetOtherCostVO[]>([]);
const PROJECT_CATEGORY = reactive({
MARKET: '1', //
RD: '2' //
MARKET: '1', //
MARKET_PART: '2', //
RD: '3', //
PRE_PRODUCTION: '4' //
});
const BUSINESS_STATUS = reactive({
@ -226,6 +256,28 @@ const BUSINESS_STATUS = reactive({
AVAILABLE: '3' //
});
const MATERIAL_TYPE = {
MAIN: '1', //
OTHER: '2' //
};
const TECH_TYPE = reactive({
TECH_CONSULT: '1', //
EXPERT_MEETING: '2', //-
EXPERT_COMM: '3' //-
});
const LABOR_TYPE = reactive({
LABOR: '1', //
SERVICE: '2' //
});
const LITERATURE_TYPE = {
MATERIAL: '1', //
DOCUMENT: '2', //
SOFTWARE: '3' //
};
//
// activeTabcomputedref使
const activeTab = ref('budget');
@ -236,7 +288,7 @@ const searchForm = reactive({
projectId: '',
projectName: '',
projectCode: '',
projectCategory: '1',
projectCategory: '',
flowStatus: ''
});
@ -244,9 +296,9 @@ const searchForm = reactive({
watch(
() => searchForm.projectCategory,
(newCategory) => {
if (newCategory === PROJECT_CATEGORY.RD) {
if (newCategory === PROJECT_CATEGORY.RD || newCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
activeTab.value = 'budgetTable';
} else if (newCategory === PROJECT_CATEGORY.MARKET) {
} else if (newCategory === PROJECT_CATEGORY.MARKET || newCategory === PROJECT_CATEGORY.MARKET_PART) {
activeTab.value = 'budget';
}
}
@ -293,7 +345,7 @@ const projectList = ref<ProjectInfoVO[]>([]);
const projectTotal = ref(0);
const projectLoading = ref(true);
const projectSearchForm = reactive({
keyword: '',
projectCategory: '',
pageNum: 1,
pageSize: 10
});
@ -302,7 +354,7 @@ const projectSearchForm = reactive({
const getProjectList = async () => {
projectLoading.value = true;
const res = await listProjectInfo(projectSearchForm);
console.log(res);
console.log(searchForm);
projectList.value = res.rows;
projectTotal.value = res.total;
projectLoading.value = false;
@ -311,6 +363,13 @@ const getProjectList = async () => {
//
const handleTabClick = (tab: any) => {
activeTab.value = tab.paneName;
if ((budgetId.value && routeParams.value.type === 'update') || !budgetId.value) {
if (searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
updateRdBudgetTable();
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART) {
updateBudgetTable();
}
}
};
//
@ -438,12 +497,47 @@ const updateBudgetTable = () => {
const travelTotal = travelCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
const otherTotal = otherCostRef.value?.getTotalAmount() || { budgetAmount: 0, reducedAmount: 0 };
//
budgetTableRef.value.updateBudgetDetailData('材料费', materialTotal.budgetAmount, materialTotal.reducedAmount);
budgetTableRef.value.updateBudgetDetailData('人工费', laborTotal.budgetAmount, laborTotal.reducedAmount);
budgetTableRef.value.updateBudgetDetailData('安装费', installationTotal.budgetAmount, installationTotal.reducedAmount);
budgetTableRef.value.updateBudgetDetailData('差旅费', travelTotal.budgetAmount, travelTotal.reducedAmount);
budgetTableRef.value.updateBudgetDetailData('其他费用', otherTotal.budgetAmount, otherTotal.reducedAmount);
// ,
budgetTableRef.value.updateBudgetDetailData('材料费', materialTotal.budgetAmount / 10000, materialTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('人工费', laborTotal.budgetAmount / 10000, laborTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('安装费', installationTotal.budgetAmount / 10000, installationTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('差旅费', travelTotal.budgetAmount / 10000, travelTotal.reducedAmount / 10000);
budgetTableRef.value.updateBudgetDetailData('其他费用', otherTotal.budgetAmount / 10000, otherTotal.reducedAmount / 10000);
};
//
const updateRdBudgetTable = () => {
// tab
const equipmentAmount = rdEquipmentCostRef.value?.getTotalAmount();
const equipmentTotal = equipmentAmount.equipmentTotal || 0;
const materialTotal = rdMaterialCostRef.value?.getTotalAmount() || 0;
const travelMeetingExchangeAmount = rdTravelMeetingExchangeRef.value?.getTravelMeetingExchangeAmount();
const travelTotal = travelMeetingExchangeAmount.travelSubtotalTotal || 0;
const meetingFeeTotal = travelMeetingExchangeAmount.meetingFeeTotal || 0;
const internationalSubtotalTotal = travelMeetingExchangeAmount.internationalSubtotalTotal || 0;
const laborAmount = rdLaborServiceRef.value?.getLaborAmount();
const techConsultTotal = laborAmount.techConsultTotal || 0;
const laborTotal = laborAmount.laborTotal || 0;
const serviceTotal = laborAmount.serviceTotal || 0;
const literatureCost = rdLiteratureCostRef.value?.getLiteratureCost() || 0;
const literatureTotal = literatureCost.literatureTotal || 0;
const testingCost = rdTestingCostRef.value?.getTestingAmount() || 0;
const testingTotal = testingCost.testingTotal || 0;
const otherCost = rdOtherCostRef.value?.getOtherCost() || 0;
const otherTotal = otherCost.otherTotal || 0;
// ,
rdBudgetTableRef.value.updateRdBudgetDetailData('设备费', equipmentTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('材料费', materialTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('差旅费', travelTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('会议费', meetingFeeTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('国际合作与交流费', internationalSubtotalTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('咨询开发费', techConsultTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('人工费', laborTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('劳务费', serviceTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('资料/文献费', literatureTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('测试化验费', testingTotal / 10000);
rdBudgetTableRef.value.updateRdBudgetDetailData('其他费用', otherTotal / 10000);
};
// tab
@ -470,9 +564,9 @@ const updateBudgetDataOnTabChange = () => {
};
// tab
watch(activeTab, () => {
updateBudgetDataOnTabChange();
});
// watch(activeTab, () => {
// updateBudgetDataOnTabChange();
// });
//
const exportBudget = () => {
if (!searchForm.projectId) {
@ -490,67 +584,156 @@ const handleSave = async (status: string, mode: boolean) => {
return;
}
if (searchForm.projectCategory === PROJECT_CATEGORY.RD) {
handleEquipmentCostUpdate();
handleMaterialCostUpdate();
handleTravelMeetingExchangeUpdate();
handleLaborServiceUpdate();
handleLiteratureCostUpdate();
handleTestingCostUpdate();
handleOtherCostUpdate();
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET) {
updateBudgetTable();
}
try {
//
if (searchForm.projectCategory === PROJECT_CATEGORY.RD) {
const allData = {
projectId: searchForm.projectId,
costData: { ...costData },
equipmentCostData: rdEquipmentCostRef.value?.equipmentList || [],
materialCostData: rdMaterialCostRef.value?.materialList || [],
travelMeetingExchangeData: {
travelList: rdTravelMeetingExchangeRef.value?.travelList || [],
meetingList: rdTravelMeetingExchangeRef.value?.meetingList || [],
internationalExchangeList: rdTravelMeetingExchangeRef.value?.internationalExchangeList || []
},
laborServiceData: {
techConsultList: rdLaborServiceRef.value?.techConsultList || [],
expertMeetingList: rdLaborServiceRef.value?.expertMeetingList || [],
expertCommList: rdLaborServiceRef.value?.expertCommList || [],
laborList: rdLaborServiceRef.value?.laborList || [],
serviceList: rdLaborServiceRef.value?.serviceList || []
},
literatureCostData: {
materialsList: rdLiteratureCostRef.value?.materialsList || [],
literatureRetrieval: rdLiteratureCostRef.value?.literatureRetrieval || {},
softwareList: rdLiteratureCostRef.value?.softwareList || []
},
testingCostData: rdTestingCostRef.value?.testingList || [],
otherCostData: rdOtherCostRef.value?.otherCostList || []
};
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET) {
const budgetDetailData = budgetTableRef.value?.budgetDetailData;
let budgetForm = budgetTableRef.value?.budgetForm;
buttonLoading.value = true;
const budgetForm = reactive<budgetInfoForm>({});
if (searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
updateRdBudgetTable();
const budgetDetailData = rdBudgetTableRef.value?.budgetDetailData;
Object.assign(budgetForm,
rdBudgetTableRef.value?.rdBudgetInfoForm || {},
rdBudgetTableRef.value?.footerForm || {}
);
budgetForm.budgetStatus = status === 'draft' ? BUSINESS_STATUS.DRAFT : BUSINESS_STATUS.WAITING;
budgetForm.flowStatus = status === 'draft' ? 'draft' : 'waiting';
budgetForm.erpRdBudgetEquipmentCostList = [];
budgetForm.erpRdBudgetMaterialCostList = [];
budgetForm.erpRdBudgetTravelCostList = [];
budgetForm.erpRdBudgetMeetingCostList = [];
budgetForm.erpRdBudgetExchangeCostList = [];
budgetForm.erpRdBudgetTechCostList = [];
budgetForm.erpRdBudgetLaborCostList = [];
budgetForm.erpRdBudgetLiteratureCostList = [];
budgetForm.erpRdBudgetTestingCostList = [];
budgetForm.erpRdBudgetOtherCostList = [];
// console.log(budgetForm);
budgetForm.erpBudgetDetailList = budgetDetailData;
budgetForm.erpBudgetMaterialCostList = materialCostRef.value?.budgetMaterialCostList;
console.log(budgetForm);
buttonLoading.value = true;
if (budgetForm.budgetId) {
await updateErpBudgetInfo(budgetForm).finally(() => (buttonLoading.value = false));
} else {
await addErpBudgetInfo(budgetForm).finally(() => (buttonLoading.value = false));
if (!isDeepEqual(oriRdBudgetEquipmentCostList.value, rdEquipmentCostRef.value?.equipmentData)) {
budgetForm.erpRdBudgetEquipmentCostList = rdEquipmentCostRef.value?.equipmentData;
}
if (!isDeepEqual(oriRdBudgetMaterialCostList.value, rdMaterialCostRef.value?.allMaterialData)) {
budgetForm.erpRdBudgetMaterialCostList = rdMaterialCostRef.value?.allMaterialData;
}
if (!isDeepEqual(oriRdBudgetTravelCostList.value, rdTravelMeetingExchangeRef.value?.travelList)) {
budgetForm.erpRdBudgetTravelCostList = rdTravelMeetingExchangeRef.value?.travelList;
}
if (!isDeepEqual(oriRdBudgetMeetingCostList.value, rdTravelMeetingExchangeRef.value?.meetingList)) {
budgetForm.erpRdBudgetMeetingCostList = rdTravelMeetingExchangeRef.value?.meetingList;
}
if (!isDeepEqual(oriRdBudgetExchangeCostList.value, rdTravelMeetingExchangeRef.value?.exchangeList)) {
budgetForm.erpRdBudgetExchangeCostList = rdTravelMeetingExchangeRef.value?.exchangeList;
}
budgetForm.erpRdBudgetTechCostList = [
...(!isDeepEqual(oriRdBudgetTechConsultCostList.value, rdLaborServiceRef.value?.techConsultList)
? rdLaborServiceRef.value?.techConsultList || []
: []),
...(!isDeepEqual(oriRdBudgetExpertMeetingCostList.value, rdLaborServiceRef.value?.expertMeetingList)
? rdLaborServiceRef.value?.expertMeetingList || []
: []),
...(!isDeepEqual(oriRdBudgetExpertCommCostList.value, rdLaborServiceRef.value?.expertCommList)
? rdLaborServiceRef.value?.expertCommList || []
: [])
];
console.log('---');
console.log(budgetForm.erpRdBudgetTechCostList);
budgetForm.erpRdBudgetLaborCostList = [
...(!isDeepEqual(oriRdBudgetLaborCostList.value, rdLaborServiceRef.value?.laborList) ? rdLaborServiceRef.value?.laborList || [] : []),
...(!isDeepEqual(oriRdBudgetServiceCostList.value, rdLaborServiceRef.value?.serviceList) ? rdLaborServiceRef.value?.serviceList || [] : [])
];
budgetForm.erpRdBudgetLiteratureCostList = [
...(!isDeepEqual(oriRdBudgetLiteratureMaterialCostList.value, rdLiteratureCostRef.value?.materialsList)
? rdLiteratureCostRef.value?.materialsList || []
: []),
...(!isDeepEqual(oriRdBudgetLiteratureSofwareCostList.value, rdLiteratureCostRef.value?.softwareList)
? rdLiteratureCostRef.value?.softwareList || []
: [])
];
budgetForm.erpRdBudgetLiteratureCostList.push(rdLiteratureCostRef.value?.literatureRetrieval);
if (!isDeepEqual(oriRdBudgetTestingCostList.value, rdTestingCostRef.value?.testData)) {
budgetForm.erpRdBudgetTestingCostList = rdTestingCostRef.value?.testData;
}
if (!isDeepEqual(oriRdBudgetOtherCostList.value, rdOtherCostRef.value?.otherCostList)) {
budgetForm.erpRdBudgetOtherCostList = rdOtherCostRef.value?.otherCostList;
}
if (budgetForm.budgetId) {
budgetForm.toDeletedRdEquipmentCostIdList = rdEquipmentCostRef.value?.toDeletedEquipmentCostIdList;
budgetForm.toDeletedRdMaterialCostIdList = rdMaterialCostRef.value?.toDeletedMaterialCostIdList;
budgetForm.toDeletedRdTravelCostIdList = rdTravelMeetingExchangeRef.value?.toDeletedTravelCostIdList;
budgetForm.toDeletedRdMeetingCostIdList = rdTravelMeetingExchangeRef.value?.toDeletedMeetingCostIdList;
budgetForm.toDeletedRdExchangeCostIdList = rdTravelMeetingExchangeRef.value?.toDeletedExchangeCostIdList;
budgetForm.toDeletedRdTechCostIdList = rdLaborServiceRef.value?.toDeletedTechCostIdList;
budgetForm.toDeletedRdLaborCostIdList = rdLaborServiceRef.value?.toDeletedLaborCostIdList;
budgetForm.toDeletedRdLiteratureCostIdList = rdLiteratureCostRef.value?.toDeletedLiteratureCostIdList;
budgetForm.toDeletedRdTestingCostIdList = rdTestingCostRef.value?.toDeletedTestCostIdList;
budgetForm.toDeletedRdOtherCostIdList = rdOtherCostRef.value?.toDeletedOtherCostIdList;
}
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART) {
updateBudgetTable();
const budgetDetailData = budgetTableRef.value?.budgetDetailData;
Object.assign(budgetForm, budgetTableRef.value?.budgetForm);
budgetForm.budgetStatus = status === 'draft' ? BUSINESS_STATUS.DRAFT : BUSINESS_STATUS.WAITING;
budgetForm.flowStatus = status === 'draft' ? 'draft' : 'waiting';
budgetForm.erpBudgetMaterialCostList = [];
budgetForm.erpBudgetLaborCostList = [];
budgetForm.erpBudgetInstallCostList = [];
budgetForm.erpBudgetTravelCostList = [];
budgetForm.erpBudgetOtherCostList = [];
console.log(budgetForm);
budgetForm.erpBudgetDetailList = budgetDetailData;
if (!isDeepEqual(oriBudgetMaterialCostList.value, materialCostRef.value?.budgetMaterialCostList)) {
budgetForm.erpBudgetMaterialCostList = materialCostRef.value?.budgetMaterialCostList;
}
if (!isDeepEqual(oriBudgetLaborCostList.value, laborCostRef.value?.budgetLaborCostList)) {
budgetForm.erpBudgetLaborCostList = laborCostRef.value?.budgetLaborCostList;
}
if (!isDeepEqual(oriBudgetInstallCostList.value, installationCostRef.value?.installCostList)) {
budgetForm.erpBudgetInstallCostList = installationCostRef.value?.installCostList;
}
if (!isDeepEqual(oriBudgetTravelCostList.value, travelCostRef.value?.travelCostList)) {
budgetForm.erpBudgetTravelCostList = travelCostRef.value?.travelCostList;
}
if (!isDeepEqual(oriBudgetOtherCostList.value, otherCostRef.value?.otherCostList)) {
budgetForm.erpBudgetOtherCostList = otherCostRef.value?.otherCostList;
}
if (budgetForm.budgetId) {
budgetForm.toDeletedMaterialCostIdList = materialCostRef.value?.toDeletedMaterialCostIdList;
budgetForm.toDeletedLaborCostIdList = laborCostRef.value?.toDeletedLaborCostIdList;
budgetForm.toDeletedInstallCostIdList = installationCostRef.value?.toDeletedInstallCostIdList;
budgetForm.toDeletedTravelCostIdList = travelCostRef.value?.toDeletedTravelCostIdList;
budgetForm.toDeletedOtherCostIdList = otherCostRef.value?.toDeletedOtherCostIdList;
}
proxy?.$modal.msgSuccess('操作成功');
}
if (budgetForm.budgetId) {
await updateErpBudgetInfo(budgetForm).finally(() => (buttonLoading.value = false));
} else {
await addErpBudgetInfo(budgetForm).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
// tab
const obj = { path: '/budget/erp/budgetInfo', query: { t: Date.now(), pageNum: routeParams.value.pageNum } };
proxy?.$tab.closeOpenPage(obj);
// router.go(-1);
// handleClose();
} catch (error) {
ElMessage.error('保存失败');
console.error('保存失败:', error);
} finally {
buttonLoading.value = false;
}
};
@ -562,6 +745,7 @@ const handleClose = () => {
};
const route = useRoute();
const budgetId = ref();
//
onMounted(() => {
//
@ -570,15 +754,135 @@ onMounted(() => {
nextTick(async () => {
//
routeParams.value = route.query;
const id = routeParams.value.id as string | number;
if (id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
budgetId.value = routeParams.value.id as string | number;
if (budgetId.value && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
proxy?.$modal.loading('正在加载数据,请稍后...');
const res = await getErpBudgetInfo(id);
const res = await getErpBudgetInfo(budgetId.value);
Object.assign(searchForm, res.data);
Object.assign(budgetTableRef.value?.budgetForm,res.data)
console.log(res.data)
if (searchForm.projectCategory === PROJECT_CATEGORY.RD || searchForm.projectCategory === PROJECT_CATEGORY.PRE_PRODUCTION) {
oriRdBudgetEquipmentCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetEquipmentCostList));
const erpRdBudgetMaterialCostList = res.data.erpRdBudgetMaterialCostList;
const groupedRdBudgetMaterialCostListByMaterialType = erpRdBudgetMaterialCostList.reduce((acc, item) => {
const materialType = item.materialType;
if (!acc[materialType]) {
acc[materialType] = [];
}
acc[materialType].push(item);
return acc;
}, {});
const rdBudgetMainMaterialCostList = groupedRdBudgetMaterialCostListByMaterialType[MATERIAL_TYPE.MAIN] || [];
const rdBudgetOtherMaterialCostList = groupedRdBudgetMaterialCostListByMaterialType[MATERIAL_TYPE.OTHER] || [];
oriRdBudgetMaterialCostList.value = JSON.parse(JSON.stringify(erpRdBudgetMaterialCostList));
oriRdBudgetTravelCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetTravelCostList));
oriRdBudgetMeetingCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetMeetingCostList));
oriRdBudgetExchangeCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetExchangeCostList));
const erpRdBudgetTechCostList = res.data.erpRdBudgetTechCostList;
const groupedRdBudgetTechCostListByTechType = erpRdBudgetTechCostList.reduce((acc, item) => {
const techType = item.techType;
if (!acc[techType]) {
acc[techType] = [];
}
acc[techType].push(item);
return acc;
}, {});
const rdBudgetTechConsultCostList = groupedRdBudgetTechCostListByTechType[TECH_TYPE.TECH_CONSULT] || [];
const rdBudgetExpertMeetingCostList = groupedRdBudgetTechCostListByTechType[TECH_TYPE.EXPERT_MEETING] || [];
const rdBudgetExpertCommCostList = groupedRdBudgetTechCostListByTechType[TECH_TYPE.EXPERT_COMM] || [];
oriRdBudgetTechConsultCostList.value = JSON.parse(JSON.stringify(rdBudgetTechConsultCostList)); //
oriRdBudgetExpertMeetingCostList.value = JSON.parse(JSON.stringify(rdBudgetExpertMeetingCostList)); //
oriRdBudgetExpertCommCostList.value = JSON.parse(JSON.stringify(rdBudgetExpertCommCostList)); //
const erpRdBudgetLaborCostList = res.data.erpRdBudgetLaborCostList;
const groupedRdBudgetLaborCostListByLaborType = erpRdBudgetLaborCostList.reduce((acc, item) => {
const laborType = item.laborType;
if (!acc[laborType]) {
acc[laborType] = [];
}
acc[laborType].push(item);
return acc;
}, {});
const rdBudgetLaborCostList = groupedRdBudgetLaborCostListByLaborType[LABOR_TYPE.LABOR] || [];
const rdBudgetServiceCostList = groupedRdBudgetLaborCostListByLaborType[LABOR_TYPE.SERVICE] || [];
oriRdBudgetLaborCostList.value = JSON.parse(JSON.stringify(rdBudgetLaborCostList)); //
oriRdBudgetServiceCostList.value = JSON.parse(JSON.stringify(rdBudgetServiceCostList)); //
const erpRdBudgetLiteratureCostList = res.data.erpRdBudgetLiteratureCostList;
const groupedRdBudgetLiteratureCostListByLiteratureType = erpRdBudgetLiteratureCostList.reduce((acc, item) => {
const literatureType = item.literatureType;
if (!acc[literatureType]) {
acc[literatureType] = [];
}
acc[literatureType].push(item);
return acc;
}, {});
const rdBudgetLiteratureMaterialCostList = groupedRdBudgetLiteratureCostListByLiteratureType[LITERATURE_TYPE.MATERIAL] || [];
const rdBudgetLiteratureSoftwareCostList = groupedRdBudgetLiteratureCostListByLiteratureType[LITERATURE_TYPE.SOFTWARE] || [];
oriRdBudgetLiteratureMaterialCostList.value = JSON.parse(JSON.stringify(rdBudgetLiteratureMaterialCostList)); //
oriRdBudgetLiteratureSofwareCostList.value = JSON.parse(JSON.stringify(rdBudgetLiteratureSoftwareCostList)); //
const rdBudgetLiteratureRetrievalCostList = groupedRdBudgetLiteratureCostListByLiteratureType[LITERATURE_TYPE.DOCUMENT] || []; //
oriRdBudgetTestingCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetTestingCostList));
oriRdBudgetOtherCostList.value = JSON.parse(JSON.stringify(res.data.erpRdBudgetOtherCostList));
nextTick(() => {
console.log(rdBudgetTableRef.value?.budgetForm);
Object.assign(rdBudgetTableRef.value?.rdBudgetInfoForm, res.data);
Object.assign(rdBudgetTableRef.value?.footerForm, res.data);
Object.assign(rdBudgetTableRef.value?.budgetDetailData || [], res.data.erpBudgetDetailList);
Object.assign(rdEquipmentCostRef.value?.equipmentData || [], res.data.erpRdBudgetEquipmentCostList);
Object.assign(rdMaterialCostRef.value?.materialData || [], rdBudgetMainMaterialCostList);
Object.assign(rdMaterialCostRef.value?.otherMaterial || [], rdBudgetOtherMaterialCostList[0]);
Object.assign(rdTravelMeetingExchangeRef.value?.travelList || [], res.data.erpRdBudgetTravelCostList);
Object.assign(rdTravelMeetingExchangeRef.value?.meetingList || [], res.data.erpRdBudgetMeetingCostList);
Object.assign(rdTravelMeetingExchangeRef.value?.exchangeList || [], res.data.erpRdBudgetExchangeCostList);
Object.assign(rdLaborServiceRef.value?.techConsultList || [], rdBudgetTechConsultCostList);
Object.assign(rdLaborServiceRef.value?.expertMeetingList || [], rdBudgetExpertMeetingCostList);
Object.assign(rdLaborServiceRef.value?.expertCommList || [], rdBudgetExpertCommCostList);
Object.assign(rdLaborServiceRef.value?.laborList || [], rdBudgetLaborCostList);
Object.assign(rdLaborServiceRef.value?.serviceList || [], rdBudgetServiceCostList);
Object.assign(rdLiteratureCostRef.value?.materialsList || [], rdBudgetLiteratureMaterialCostList);
Object.assign(rdLiteratureCostRef.value?.softwareList || [], rdBudgetLiteratureSoftwareCostList);
Object.assign(
rdLiteratureCostRef.value?.literatureRetrieval || [],
rdBudgetLiteratureRetrievalCostList.length > 0 ? rdBudgetLiteratureRetrievalCostList[0] : {}
);
Object.assign(rdTestingCostRef.value?.testData || [], res.data.erpRdBudgetTestingCostList);
Object.assign(rdOtherCostRef.value?.otherCostList || [], res.data.erpRdBudgetOtherCostList);
});
} else if (searchForm.projectCategory === PROJECT_CATEGORY.MARKET || searchForm.projectCategory === PROJECT_CATEGORY.MARKET_PART) {
//
oriBudgetMaterialCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetMaterialCostList));
oriBudgetLaborCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetLaborCostList));
oriBudgetInstallCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetInstallCostList));
oriBudgetTravelCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetTravelCostList));
oriBudgetOtherCostList.value = JSON.parse(JSON.stringify(res.data.erpBudgetOtherCostList));
nextTick(() => {
console.log(budgetTableRef.value?.budgetForm);
Object.assign(budgetTableRef.value?.budgetForm, res.data);
Object.assign(budgetTableRef.value?.budgetDetailData, res.data.erpBudgetDetailList);
Object.assign(materialCostRef.value?.budgetMaterialCostList, res.data.erpBudgetMaterialCostList);
Object.assign(laborCostRef.value?.budgetLaborCostList, res.data.erpBudgetLaborCostList);
Object.assign(installationCostRef.value?.installCostList, res.data.erpBudgetInstallCostList);
Object.assign(travelCostRef.value?.travelCostList, res.data.erpBudgetTravelCostList);
Object.assign(otherCostRef.value?.otherCostList, res.data.erpBudgetOtherCostList);
});
}
proxy?.$modal.closeLoading();
} else {
searchForm.projectCategory = routeParams.value.projectCategory;
//
// if (form.value.contractCode) {
// isCodeGenerated.value = true;
@ -587,6 +891,7 @@ onMounted(() => {
// isCodeGenerated.value = false;
// }
}
projectSearchForm.projectCategory = searchForm.projectCategory;
});
});
@ -594,6 +899,22 @@ 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';
//
const routeParams = ref<Record<string, any>>({});
//
@ -608,6 +929,12 @@ const approvalVerifyOpen = async () => {
const handleApprovalRecord = () => {
approvalRecordRef.value.init(searchForm.budgetId);
};
//
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
router.go(-1);
};
</script>
<style scoped>

@ -4,22 +4,13 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="项目ID" prop="projectId">
<el-input v-model="queryParams.projectId" placeholder="请输入项目ID" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="等同于项目ID做唯一索引使用在作废时此值要清空" prop="approvedFlag">
<el-input
v-model="queryParams.approvedFlag"
placeholder="请输入等同于项目ID做唯一索引使用在作废时此值要清空"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="版本,新版本+1" prop="budgetVersion">
<el-input v-model="queryParams.budgetVersion" placeholder="请输入版本,新版本+1" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="冗余,项目类别" prop="projectCategory">
<el-input v-model="queryParams.projectCategory" placeholder="请输入冗余,项目类别" clearable @keyup.enter="handleQuery" />
<!-- <el-form-item label="版本,新版本+1" prop="budgetVersion">-->
<!-- <el-input v-model="queryParams.budgetVersion" placeholder="请输入版本,新版本+1" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<el-form-item label="项目类别" prop="projectCategory">
<el-select v-model="queryParams.projectCategory" placeholder="请选择项目类别">
<el-option v-for="dict in project_category" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="项目号" prop="projectCode">
<el-input v-model="queryParams.projectCode" placeholder="请输入项目号" clearable @keyup.enter="handleQuery" />
@ -28,56 +19,11 @@
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目经理" prop="managerId">
<el-input v-model="queryParams.managerId" placeholder="请输入项目经理" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目经理姓名" prop="managerName">
<el-input v-model="queryParams.managerName" placeholder="请输入项目经理姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="产品经理关联sys_user" prop="productManagerId">
<el-input v-model="queryParams.productManagerId" placeholder="请输入产品经理关联sys_user" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="产品经理姓名" prop="productManagerName">
<el-input v-model="queryParams.productManagerName" placeholder="请输入产品经理姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="审核(评审组长)ID关联sys_user" prop="approveUserId">
<el-input v-model="queryParams.approveUserId" placeholder="请输入审核(评审组长)ID关联sys_user" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="审核(评审组长)姓名" prop="approveUserName">
<el-input v-model="queryParams.approveUserName" placeholder="请输入审核(评审组长)姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="合同额(元)" prop="contractAmount">
<el-input v-model="queryParams.contractAmount" placeholder="请输入合同额(元)" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="合同净额(元)" prop="netContractAmount">
<el-input v-model="queryParams.netContractAmount" placeholder="请输入合同净额(元)" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="预算成本(元)" prop="budgetCost">
<el-input v-model="queryParams.budgetCost" placeholder="请输入预算成本(元)" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="预算毛利率乘以100保存" prop="budgetRate">
<el-input v-model="queryParams.budgetRate" placeholder="请输入预算毛利率乘以100保存" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="降成本后预算成本(元)" prop="reduceBudgetCost">
<el-input v-model="queryParams.reduceBudgetCost" placeholder="请输入降成本后预算成本(元)" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="降成本后预算毛利率" prop="reduceBudgetRate">
<el-input v-model="queryParams.reduceBudgetRate" placeholder="请输入降成本后预算毛利率" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目预算期间" prop="duringOperation">
<el-input v-model="queryParams.duringOperation" placeholder="请输入项目预算期间" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="单位ID,关联base_unit_info" prop="unitId">
<el-input v-model="queryParams.unitId" placeholder="请输入单位ID,关联base_unit_info" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="单位名称" prop="unitName">
<el-input v-model="queryParams.unitName" placeholder="请输入单位名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="是否出口" prop="exportFlag">
<el-input v-model="queryParams.exportFlag" placeholder="请输入是否出口" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="合同ID(预留)" prop="contractId">
<el-input v-model="queryParams.contractId" placeholder="请输入合同ID(预留)" clearable @keyup.enter="handleQuery" />
<el-select v-model="queryParams.managerId" filterable placeholder="请选择项目经理">
<el-option v-for="item in userList" :key="item.userId" :label="item.userName" :value="item.userId"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
@ -91,7 +37,10 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['oa:erp/budgetInfo:add']"> </el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['oa:erp/budgetInfo:add']"> </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAddRd" v-hasPermi="['oa:erp/budgetInfo:add']"> </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['oa:erp/budgetInfo:edit']"
@ -112,19 +61,16 @@
<el-table v-loading="loading" border :data="budgetInfoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="预算ID" align="center" prop="budgetId" v-if="columns[0].visible" />
<el-table-column label="项目ID" align="center" prop="projectId" v-if="columns[2].visible" />
<el-table-column label="等同于项目ID做唯一索引使用在作废时此值要清空" align="center" prop="approvedFlag" v-if="columns[3].visible" />
<el-table-column label="版本,新版本+1" align="center" prop="budgetVersion" v-if="columns[4].visible" />
<el-table-column label="冗余,项目类别" align="center" prop="projectCategory" v-if="columns[5].visible" />
<!-- <el-table-column label="版本,新版本+1" align="center" prop="budgetVersion" v-if="columns[4].visible" />-->
<el-table-column label="项目类别" align="center" prop="projectCategory" v-if="columns[5].visible">
<template #default="scope">
<dict-tag :options="project_category" :value="scope.row.projectCategory" />
</template>
</el-table-column>
<el-table-column label="项目号" align="center" prop="projectCode" v-if="columns[6].visible" />
<el-table-column label="项目名称" align="center" prop="projectName" v-if="columns[7].visible" />
<el-table-column label="项目经理" align="center" prop="managerId" v-if="columns[8].visible" />
<el-table-column label="项目经理姓名" align="center" prop="managerName" v-if="columns[9].visible" />
<el-table-column label="产品经理关联sys_user" align="center" prop="productManagerId" v-if="columns[10].visible" />
<el-table-column label="产品经理姓名" align="center" prop="productManagerName" v-if="columns[11].visible" />
<el-table-column label="审核(评审组长)ID关联sys_user" align="center" prop="approveUserId" v-if="columns[12].visible" />
<el-table-column label="审核(评审组长)姓名" align="center" prop="approveUserName" v-if="columns[13].visible" />
<el-table-column label="合同额(元)" align="center" prop="contractAmount" v-if="columns[14].visible" />
<el-table-column label="合同净额(元)" align="center" prop="netContractAmount" v-if="columns[15].visible" />
<el-table-column label="预算成本(元)" align="center" prop="budgetCost" v-if="columns[16].visible" />
@ -132,13 +78,8 @@
<el-table-column label="降成本后预算成本(元)" align="center" prop="reduceBudgetCost" v-if="columns[18].visible" />
<el-table-column label="降成本后预算毛利率" align="center" prop="reduceBudgetRate" v-if="columns[19].visible" />
<el-table-column label="项目预算期间" align="center" prop="duringOperation" v-if="columns[20].visible" />
<el-table-column label="单位ID,关联base_unit_info" align="center" prop="unitId" v-if="columns[21].visible" />
<el-table-column label="单位名称" align="center" prop="unitName" v-if="columns[22].visible" />
<el-table-column label="是否出口" align="center" prop="exportFlag" v-if="columns[23].visible" />
<el-table-column label="预算状态(1暂存 2审批中 3可用)" align="center" prop="budgetStatus" v-if="columns[24].visible" />
<el-table-column label="流程状态" align="center" prop="flowStatus" v-if="columns[25].visible" />
<el-table-column label="合同ID(预留)" align="center" prop="contractId" v-if="columns[26].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[27].visible" />
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
@ -153,89 +94,6 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改项目预算对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="erp/budgetInfoFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="项目ID" prop="projectId">
<el-input v-model="form.projectId" placeholder="请输入项目ID" />
</el-form-item>
<el-form-item label="等同于项目ID做唯一索引使用在作废时此值要清空" prop="approvedFlag">
<el-input v-model="form.approvedFlag" placeholder="请输入等同于项目ID做唯一索引使用在作废时此值要清空" />
</el-form-item>
<el-form-item label="版本,新版本+1" prop="budgetVersion">
<el-input v-model="form.budgetVersion" placeholder="请输入版本,新版本+1" />
</el-form-item>
<el-form-item label="冗余,项目类别" prop="projectCategory">
<el-input v-model="form.projectCategory" placeholder="请输入冗余,项目类别" />
</el-form-item>
<el-form-item label="项目号" prop="projectCode">
<el-input v-model="form.projectCode" placeholder="请输入项目号" />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="项目经理" prop="managerId">
<el-input v-model="form.managerId" placeholder="请输入项目经理" />
</el-form-item>
<el-form-item label="项目经理姓名" prop="managerName">
<el-input v-model="form.managerName" placeholder="请输入项目经理姓名" />
</el-form-item>
<el-form-item label="产品经理关联sys_user" prop="productManagerId">
<el-input v-model="form.productManagerId" placeholder="请输入产品经理关联sys_user" />
</el-form-item>
<el-form-item label="产品经理姓名" prop="productManagerName">
<el-input v-model="form.productManagerName" placeholder="请输入产品经理姓名" />
</el-form-item>
<el-form-item label="审核(评审组长)ID关联sys_user" prop="approveUserId">
<el-input v-model="form.approveUserId" placeholder="请输入审核(评审组长)ID关联sys_user" />
</el-form-item>
<el-form-item label="审核(评审组长)姓名" prop="approveUserName">
<el-input v-model="form.approveUserName" placeholder="请输入审核(评审组长)姓名" />
</el-form-item>
<el-form-item label="合同额(元)" prop="contractAmount">
<el-input v-model="form.contractAmount" placeholder="请输入合同额(元)" />
</el-form-item>
<el-form-item label="合同净额(元)" prop="netContractAmount">
<el-input v-model="form.netContractAmount" placeholder="请输入合同净额(元)" />
</el-form-item>
<el-form-item label="预算成本(元)" prop="budgetCost">
<el-input v-model="form.budgetCost" placeholder="请输入预算成本(元)" />
</el-form-item>
<el-form-item label="预算毛利率乘以100保存" prop="budgetRate">
<el-input v-model="form.budgetRate" placeholder="请输入预算毛利率乘以100保存" />
</el-form-item>
<el-form-item label="降成本后预算成本(元)" prop="reduceBudgetCost">
<el-input v-model="form.reduceBudgetCost" placeholder="请输入降成本后预算成本(元)" />
</el-form-item>
<el-form-item label="降成本后预算毛利率" prop="reduceBudgetRate">
<el-input v-model="form.reduceBudgetRate" placeholder="请输入降成本后预算毛利率" />
</el-form-item>
<el-form-item label="项目预算期间" prop="duringOperation">
<el-input v-model="form.duringOperation" placeholder="请输入项目预算期间" />
</el-form-item>
<el-form-item label="单位ID,关联base_unit_info" prop="unitId">
<el-input v-model="form.unitId" placeholder="请输入单位ID,关联base_unit_info" />
</el-form-item>
<el-form-item label="单位名称" prop="unitName">
<el-input v-model="form.unitName" placeholder="请输入单位名称" />
</el-form-item>
<el-form-item label="是否出口" prop="exportFlag">
<el-input v-model="form.exportFlag" placeholder="请输入是否出口" />
</el-form-item>
<el-form-item label="合同ID(预留)" prop="contractId">
<el-input v-model="form.contractId" placeholder="请输入合同ID(预留)" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@ -243,10 +101,16 @@
import { listErpBudgetInfo, getErpBudgetInfo, delErpBudgetInfo, addErpBudgetInfo, updateErpBudgetInfo } from '@/api/oa/erp/budgetInfo';
import { budgetInfoVO, budgetInfoQuery, budgetInfoForm } from '@/api/oa/erp/budgetInfo/types';
import router from '@/router';
import { getUserList } from '@/api/system/user';
import { UserVO } from '@/api/system/user/types';
import { reactive } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { project_category } = toRefs<any>(proxy?.useDict('project_category'));
const budgetInfoList = ref<budgetInfoVO[]>([]);
const userList = ref<UserVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
@ -374,6 +238,19 @@ const data = reactive<PageData<budgetInfoForm, budgetInfoQuery>>({
const { queryParams, form, rules } = toRefs(data);
const PROJECT_CATEGORY = reactive({
MARKET: '1', //
MARKET_PART: '2', //
RD: '3', //
PRE_PRODUCTION: '4' //
});
/** 查询用户列表 */
const getUsers = async () => {
const res = await getUserList({});
userList.value = res.data;
};
/** 查询项目预算列表 */
const getList = async () => {
loading.value = true;
@ -414,18 +291,39 @@ const handleSelectionChange = (selection: budgetInfoVO[]) => {
multiple.value = !selection.length;
};
/** 新增按钮操作 */
/** 新增市场项目预算按钮操作 */
const handleAdd = () => {
const params = {pageNum: queryParams.value.pageNum,type:'add'};
proxy.$tab.openPage('/oa/erp/budgetInfo/edit',"项目预算申请", params);
router.push({
path: '/budget/budget-add/index',
query: {
type: 'add',
projectCategory: PROJECT_CATEGORY.MARKET,
pageNum: queryParams.value.pageNum
}
});
// const params = {pageNum: queryParams.value.pageNum,type:'add'};
// proxy.$tab.openPage('/oa/erp/budgetInfo/edit',"", params);
// router.push({ path: '/budget/budget-add/index', query: { pageNum: queryParams.value.pageNum,type:'add' } });
};
/** 新增研发项目按钮操作 */
const handleAddRd = () => {
router.push({
path: '/budget/budget-add/index',
query: {
type: 'add',
pageNum: queryParams.value.pageNum,
projectCategory: PROJECT_CATEGORY.RD
}
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: budgetInfoVO) => {
const params = {pageNum: queryParams.value.pageNum,type:'update',id:row.budgetId};
proxy.$tab.openPage('/oa/erp/budgetInfo/edit',"项目预算申请", params);
const params = { pageNum: queryParams.value.pageNum, type: 'update', id: row.budgetId };
proxy.$tab.openPage('/oa/erp/budgetInfo/edit', '项目预算申请', params);
};
/** 提交按钮 */
@ -467,5 +365,6 @@ const handleExport = () => {
onMounted(() => {
getList();
getUsers();
});
</script>

@ -20,48 +20,58 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目经理">
<el-input v-model="budgetForm.managerId" />
<el-select v-model="budgetForm.managerId" filterable placeholder="请选择项目经理">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品经理">
<el-input v-model="budgetForm.productManagerId" />
<el-select v-model="budgetForm.productManagerId" filterable placeholder="请选择产品经理">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同额">
<el-input v-model="budgetForm.contractAmount" />
<el-input-number v-model="budgetForm.contractAmount" @change="computedAmountAndRate" style="width:100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同净额">
<el-input v-model="budgetForm.netContractAmount" />
<div class="custom-display">
{{ budgetForm.netContractAmount || '0.00' }}
</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预算成本">
<el-input v-model="budgetForm.budgetCost" />
<div class="custom-display">
{{ budgetForm.budgetCost || '0.00' }}
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目毛利率">
<el-input v-model="budgetForm.budgetRate" />
<div class="custom-display">{{ budgetForm.budgetRate }}%</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="降成本后预算成本">
<el-input v-model="budgetForm.reduceBudgetCost" />
<div class="custom-display">
{{ budgetForm.reduceBudgetCost }}
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="降成本后预算毛利率">
<el-input v-model="budgetForm.reduceBudgetRate" />
<div class="custom-display">{{ budgetForm.reduceBudgetRate }}%</div>
</el-form-item>
</el-col>
</el-row>
@ -73,47 +83,41 @@
</el-col>
<el-col :span="12">
<el-form-item label="单位">
<el-input v-model="budgetForm.unitId" />
<el-input v-model="budgetForm.unitName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 表格 -->
<el-table
:data="budgetDetailData"
border
style="width: 100%"
:summary-method="getSummaries"
show-summary
>
<el-table :data="budgetDetailData" border style="width: 100%" :summary-method="getSummaries" show-summary>
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="budgetItem" label="预算科目名称" min-width="150" />
<el-table-column prop="budgetCost" label="预算成本" width="160" align="right">
<template #default="scope">
<el-input
v-if="scope.row.budgetItem === '运输费' || scope.row.budgetItem === '售后服务费' || scope.row.budgetItem === '其他成本'"
v-model="scope.row.budgetCost"
@input="updateBudgetCost(scope.row)"
/>
<span v-else>{{ formatNumber(scope.row.budgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="reduceBudgetCost" label="降成本后预算成本" width="160" align="right">
<template #default="scope">
<el-input
v-if="scope.row.budgetItem === '运输费' || scope.row.budgetItem === '售后服务费' || scope.row.budgetItem === '其他成本'"
v-model="scope.row.reduceBudgetCost"
@input="updateReduceBudgetCost(scope.row)"
/>
<span v-else>{{ formatNumber(scope.row.reduceBudgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="referenceProjectName" label="参考项目" width="280">
<template #default="scope">
<el-input v-model="scope.row.referenceProjectName" />
</template>
</el-table-column>
<el-table-column prop="budgetCost" label="预算成本" width="180" align="right">
<template #default="scope">
<el-input-number
v-if="scope.row.budgetItem === '运输费' || scope.row.budgetItem === '售后服务费' || scope.row.budgetItem === '其他成本'"
v-model="scope.row.budgetCost"
precision="2"
/>
<span v-else>{{ formatNumber(scope.row.budgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="reduceBudgetCost" label="降成本后预算成本" width="180" align="right">
<template #default="scope">
<el-input-number
v-if="scope.row.budgetItem === '运输费' || scope.row.budgetItem === '售后服务费' || scope.row.budgetItem === '其他成本'"
v-model="scope.row.reduceBudgetCost"
precision="2"
/>
<span v-else>{{ formatNumber(scope.row.reduceBudgetCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="referenceProjectName" label="参考项目" width="280">
<template #default="scope">
<el-input v-model="scope.row.referenceProjectName" />
</template>
</el-table-column>
</el-table>
<!-- 备注 -->
@ -122,7 +126,9 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ref, reactive, computed, watch } from 'vue';
import { UserVO } from '@/api/system/user/types';
import { getUserList } from '@/api/system/user';
//
const budgetForm = reactive({
@ -147,13 +153,13 @@ const budgetForm = reactive({
reduceBudgetRate: undefined,
duringOperation: undefined,
unitId: undefined,
unitName: undefined,
unitName: '万元',
exportFlag: undefined,
budgetStatus: undefined,
flowStatus: undefined,
contractId: undefined,
remark: undefined,
})
remark: undefined
});
//
const budgetDetailData = ref([
@ -163,7 +169,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 2,
@ -171,7 +177,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 3,
@ -179,7 +185,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 4,
@ -187,7 +193,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 5,
@ -195,7 +201,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 6,
@ -203,7 +209,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 7,
@ -211,7 +217,7 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
},
{
sortOrder: 8,
@ -219,73 +225,86 @@ const budgetDetailData = ref([
budgetCost: 0,
reduceBudgetCost: 0,
referenceProjectId: undefined,
referenceProjectName:''
referenceProjectName: ''
}
])
]);
//
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00'
return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
if (!value) return '0.00';
return parseFloat(value).toFixed(2);
};
// ,
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
return (parseFloat(value) / 10000).toFixed(2);
};
//
const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 1) {
sums[index] = '合计'
return
sums[index] = '合计';
return;
}
if (index === 0 || index === 4) {
sums[index] = ''
return
sums[index] = '';
return;
}
const values = data.map((item: any) => Number(item[column.property]))
const values = data.map((item: any) => {
return Number(item[column.property]);
});
if (!values.every((value: number) => Number.isNaN(value))) {
sums[index] = values.reduce((prev: number, curr: number) => {
const value = Number(curr)
const value = Number(curr);
if (!Number.isNaN(value)) {
return prev + curr
return prev + curr;
} else {
return prev
return prev;
}
}, 0)
sums[index] = formatNumber(sums[index])
}, 0);
sums[index] = formatNumber(sums[index]);
} else {
sums[index] = ''
sums[index] = '';
}
})
return sums
}
});
budgetForm.budgetCost = sums[2];
budgetForm.reduceBudgetCost = sums[3];
computedAmountAndRate();
return sums;
};
//
const updateBudgetDetailData = (type: string, budgetCost: number, reduceBudgetCost: number) => {
const item = budgetDetailData.value.find(item => item.budgetItem === type)
const item = budgetDetailData.value.find((item) => item.budgetItem === type);
if (item) {
item.budgetCost = budgetCost
item.reduceBudgetCost = reduceBudgetCost
item.budgetCost = budgetCost;
item.reduceBudgetCost = reduceBudgetCost;
}
}
};
//
const updateBudgetCost = (row: any) => {
row.budgetCost = parseFloat(row.budgetCost) || 0
}
row.budgetCost = parseFloat(row.budgetCost) || 0;
};
//
const updateReduceBudgetCost = (row: any) => {
row.reduceBudgetCost = parseFloat(row.reduceBudgetCost) || 0
}
row.reduceBudgetCost = parseFloat(row.reduceBudgetCost) || 0;
};
//
defineExpose({
updateBudgetDetailData,
budgetForm,
budgetDetailData
})
});
//
const props = defineProps({
@ -293,18 +312,65 @@ const props = defineProps({
type: Object,
default: () => ({})
}
})
});
//
watch(() => props.projectInfo, (newProjectInfo) => {
if (newProjectInfo) {
budgetForm.projectId = newProjectInfo.projectId || ''
budgetForm.projectName = newProjectInfo.projectName || ''
budgetForm.projectCode = newProjectInfo.projectCode || ''
budgetForm.projectCategory = newProjectInfo.projectCategory || ''
}
}, { deep: true, immediate: true })
watch(
() => props.projectInfo,
(newProjectInfo) => {
if (newProjectInfo) {
budgetForm.projectId = newProjectInfo.projectId || '';
budgetForm.projectName = newProjectInfo.projectName || '';
budgetForm.projectCode = newProjectInfo.projectCode || '';
budgetForm.projectCategory = newProjectInfo.projectCategory || '';
}
},
{ deep: true, immediate: true }
);
const userList = ref<UserVO[]>([]);
/** 查询用户列表 */
const getUsers = async () => {
const res = await getUserList({});
userList.value = res.data;
};
const computedAmountAndRate = async () => {
if (budgetForm.contractAmount) {
budgetForm.netContractAmount = (budgetForm.contractAmount / 1.13).toFixed(2);
}
if (budgetForm.netContractAmount && budgetForm.budgetCost && budgetForm.netContractAmount > 0 && budgetForm.budgetCost > 0) {
budgetForm.budgetRate = (((budgetForm.netContractAmount - budgetForm.budgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
}
if (budgetForm.netContractAmount && budgetForm.reduceBudgetCost && budgetForm.netContractAmount > 0 && budgetForm.reduceBudgetCost > 0) {
budgetForm.reduceBudgetRate = (((budgetForm.netContractAmount - budgetForm.reduceBudgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
}
};
onMounted(() => {
getUsers();
});
// budgetForm.netContractAmount = computed(() => {
// if (budgetForm.contractAmount) {
// return (budgetForm.contractAmount / 1.13).toFixed(2);
// }
// return undefined;
// });
//
// budgetForm.budgetRate = computed(() => {
// if (budgetForm.netContractAmount && budgetForm.budgetCost && budgetForm.netContractAmount > 0 && budgetForm.budgetCost > 0) {
// return (((budgetForm.netContractAmount - budgetForm.budgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
// }
// return undefined;
// });
//
// budgetForm.reduceBudgetRate = computed(() => {
// if (budgetForm.netContractAmount && budgetForm.reduceBudgetCost && budgetForm.netContractAmount > 0 && budgetForm.reduceBudgetCost > 0) {
// return (((budgetForm.netContractAmount - budgetForm.reduceBudgetCost) * 100) / budgetForm.netContractAmount).toFixed(2);
// }
// return undefined;
// });
</script>
<style scoped>
@ -329,4 +395,20 @@ watch(() => props.projectInfo, (newProjectInfo) => {
color: #666;
text-align: center;
}
.custom-display {
width: 100%;
height: 32px; /* 与el-input默认高度一致 */
line-height: 32px;
padding: 1px 11px; /* 与el-input内边距一致 */
border: 1px solid #dcdfe6; /* 与el-input边框一致 */
border-radius: 4px; /* 与el-input圆角一致 */
background-color: #f5f7fa; /* 禁用状态的背景色 */
color: #606266; /* 文字颜色 */
font-size: 14px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

@ -1,17 +1,28 @@
<template>
<div class="installation-cost">
<!-- 标题 -->
<div class="title">安装费预算明细表</div>
<!-- 操作按钮 -->
<div class="actions">
<el-button type="primary" @click="showAddDialog = true">添加</el-button>
<el-button type="danger" @click="handleBatchDelete"></el-button>
</div>
<el-card class="table-card">
<template #header>
<div class="card-header">
<span class="title">安装费预算明细表</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="handleAddRows">
<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="installationData"
:data="installCostList"
border
style="width: 100%"
:summary-method="getSummaries"
@ -22,95 +33,82 @@
<!-- 预算成本 -->
<el-table-column label="预算成本" align="center">
<el-table-column prop="personnelCategory" label="人员类别" width="120">
<el-table-column prop="sortOrder" label="序号" width="60" align="center" />
<el-table-column prop="personnelCategory" label="人员类别" width="150">
<template #default="scope">
<el-select v-model="scope.row.personnelCategory" placeholder="请选择">
<el-option
v-for="item in personnelCategories"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input v-model="scope.row.personnelCategory" placeholder="请输入人员类别" />
</template>
</el-table-column>
<el-table-column prop="personCount" label="人数" width="80" align="right">
<el-table-column prop="peopleNumber" label="人数" width="120">
<template #default="scope">
<el-input v-model="scope.row.personCount" @input="calculateBudgetAmount(scope.row)" />
<el-input-number v-model="scope.row.peopleNumber" :min="0" :precision="0" size="small" @change="calculateBudgetAmount(scope.row)" style="width:106px;"/>
</template>
</el-table-column>
<el-table-column prop="cumulativeTime" label="累计时间(月)" width="120" align="right">
<el-table-column prop="cumulativeTime" label="累计时间(月)" width="120">
<template #default="scope">
<el-input v-model="scope.row.cumulativeTime" @input="calculateBudgetAmount(scope.row)" />
<el-input-number v-model="scope.row.cumulativeTime" :min="0" :precision="2" size="small" @change="calculateBudgetAmount(scope.row)" style="width:106px;"/>
</template>
</el-table-column>
<el-table-column prop="monthlyRatio" label="月平均投入比例(%)" width="150" align="right">
<el-table-column prop="monthRate" label="月平均投入比例(%)" width="150">
<template #default="scope">
<el-input v-model="scope.row.monthlyRatio" @input="calculateBudgetAmount(scope.row)" />
<el-input-number v-model="scope.row.monthRate" :min="0" :precision="2" size="small" @change="calculateBudgetAmount(scope.row)" style="width:136px;"/>
</template>
</el-table-column>
<el-table-column prop="laborStandard" label="人工标准(元/人月)" width="150" align="right">
<el-table-column prop="artificialStandard" label="人工标准(元/人月)" width="150">
<template #default="scope">
<el-input v-model="scope.row.laborStandard" @input="calculateBudgetAmount(scope.row)" />
<el-input-number v-model="scope.row.artificialStandard" :min="0" :precision="2" :step="10" size="small" @change="calculateBudgetAmount(scope.row)" style="width:136px;"/>
</template>
</el-table-column>
<el-table-column prop="budgetAmount" label="金额(万元)" width="120" align="right">
<el-table-column prop="price" label="金额(万元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.budgetAmount) }}
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
</el-table-column>
<!-- 降成本后预算成本 -->
<el-table-column label="降成本后预算成本" align="center">
<el-table-column prop="reducedPersonnelCategory" label="人员类别" width="120">
<el-table-column prop="reducePersonnelCategory" label="人员类别" width="150">
<template #default="scope">
<el-select v-model="scope.row.reducedPersonnelCategory" placeholder="请选择">
<el-option
v-for="item in personnelCategories"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input v-model="scope.row.reducePersonnelCategory" placeholder="请输入人员类别" />
</template>
</el-table-column>
<el-table-column prop="reducedPersonCount" label="人数" width="80" align="right">
<el-table-column prop="reducePeopleNumber" label="人数" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedPersonCount" @input="calculateReducedAmount(scope.row)" />
<el-input-number v-model="scope.row.reducePeopleNumber" :min="0" :precision="0" size="small" @change="calculateReducedAmount(scope.row)" style="width:106px;"/>
</template>
</el-table-column>
<el-table-column prop="reducedCumulativeTime" label="累计时间(月)" width="120" align="right">
<el-table-column prop="reduceCumulativeTime" label="累计时间(月)" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedCumulativeTime" @input="calculateReducedAmount(scope.row)" />
<el-input-number v-model="scope.row.reduceCumulativeTime" :min="0" :precision="2" size="small" @change="calculateReducedAmount(scope.row)" style="width:106px;"/>
</template>
</el-table-column>
<el-table-column prop="reducedMonthlyRatio" label="月平均投入比例(%)" width="150" align="right">
<el-table-column prop="reduceMonthRate" label="月平均投入比例(%)" width="150">
<template #default="scope">
<el-input v-model="scope.row.reducedMonthlyRatio" @input="calculateReducedAmount(scope.row)" />
<el-input-number v-model="scope.row.reduceMonthRate" :min="0" :precision="2" size="small" @change="calculateReducedAmount(scope.row)" style="width:136px;"/>
</template>
</el-table-column>
<el-table-column prop="reducedLaborStandard" label="人工标准(元/人月)" width="150" align="right">
<el-table-column prop="reduceArtificialStandard" label="人工标准(元/人月)" width="150">
<template #default="scope">
<el-input v-model="scope.row.reducedLaborStandard" @input="calculateReducedAmount(scope.row)" />
<el-input-number v-model="scope.row.reduceArtificialStandard" :min="0" :precision="2" :step="10" size="small" @change="calculateReducedAmount(scope.row)" style="width:136px;"/>
</template>
</el-table-column>
<el-table-column prop="reducedAmount" label="金额(万元)" width="120" align="right">
<el-table-column prop="reducePrice" label="金额(万元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reducedAmount) }}
{{ format2TenThousandNumber(scope.row.reducePrice) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="costReductionPlan" label="降成本方案" width="200">
<el-table-column prop="reduceProposal" label="降成本方案" width="200">
<template #default="scope">
<el-input v-model="scope.row.costReductionPlan" type="textarea" :rows="2" />
<el-input v-model="scope.row.reduceProposal" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index)"></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,30 +129,18 @@
<el-button type="primary" @click="handleAddRows"></el-button>
</template>
</el-dialog>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { budgetInstallCostVO } from '@/api/oa/erp/budgetInfo/market/budgetInstallCost/types';
import { budgetMaterialCostVO } from '@/api/oa/erp/budgetInfo/market/budgetMaterialCost/types';
//
const installationData = ref([
{
personnelCategory: '安装工程师',
personCount: 0,
cumulativeTime: 0,
monthlyRatio: 0,
laborStandard: 0,
budgetAmount: 0,
reducedPersonnelCategory: '安装工程师',
reducedPersonCount: 0,
reducedCumulativeTime: 0,
reducedMonthlyRatio: 0,
reducedLaborStandard: 0,
reducedAmount: 0,
costReductionPlan: ''
}
])
const installCostList = ref<budgetInstallCostVO[]>([]);
const toDeletedInstallCostIdList = ref([]);
//
const selectedRows = ref([])
@ -177,30 +163,36 @@ const personnelCategories = ref([
{ label: '其他', value: '其他' }
])
//
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00'
return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
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 calculateBudgetAmount = (row: any) => {
const personCount = parseFloat(row.personCount) || 0
const peopleNumber = parseFloat(row.peopleNumber) || 0
const cumulativeTime = parseFloat(row.cumulativeTime) || 0
const monthlyRatio = parseFloat(row.monthlyRatio) || 0
const laborStandard = parseFloat(row.laborStandard) || 0
const monthRate = parseFloat(row.monthRate) || 0
const artificialStandard = parseFloat(row.artificialStandard) || 0
row.budgetAmount = personCount * cumulativeTime * (monthlyRatio / 100) * laborStandard / 10000 //
row.price = peopleNumber * cumulativeTime * (monthRate / 100) * artificialStandard
}
//
const calculateReducedAmount = (row: any) => {
const personCount = parseFloat(row.reducedPersonCount) || 0
const cumulativeTime = parseFloat(row.reducedCumulativeTime) || 0
const monthlyRatio = parseFloat(row.reducedMonthlyRatio) || 0
const laborStandard = parseFloat(row.reducedLaborStandard) || 0
const peopleNumber = parseFloat(row.reducePeopleNumber) || 0
const cumulativeTime = parseFloat(row.reduceCumulativeTime) || 0
const monthRate = parseFloat(row.reduceMonthRate) || 0
const artificialStandard = parseFloat(row.reduceArtificialStandard) || 0
row.reducedAmount = personCount * cumulativeTime * (monthlyRatio / 100) * laborStandard / 10000 //
row.reducePrice = peopleNumber * cumulativeTime * (monthRate / 100) * artificialStandard
}
//
@ -210,21 +202,26 @@ const handleSelectionChange = (selection: any[]) => {
//
const handleAddRows = () => {
const currentSortOrder =
installCostList.value && installCostList.value.length > 0
? Math.max(...installCostList.value.map((item) => item.sortOrder))
: 0;
for (let i = 0; i < addRowCount.value; i++) {
installationData.value.push({
installCostList.value.push({
sortOrder: currentSortOrder + i + 1,
personnelCategory: '',
personCount: 0,
peopleNumber: 0,
cumulativeTime: 0,
monthlyRatio: 0,
laborStandard: 0,
budgetAmount: 0,
monthRate: 0,
artificialStandard: 0,
price: 0,
reducedPersonnelCategory: '',
reducedPersonCount: 0,
reducedCumulativeTime: 0,
reducedMonthlyRatio: 0,
reducedLaborStandard: 0,
reducedAmount: 0,
costReductionPlan: ''
reducePeopleNumber: 0,
reduceCumulativeTime: 0,
reduceMonthRate: 0,
reduceArtificialStandard: 0,
reducePrice: 0,
reduceProposal: ''
})
}
showAddDialog.value = false
@ -232,8 +229,16 @@ const handleAddRows = () => {
}
//
const handleDelete = (index: number) => {
installationData.value.splice(index, 1)
const handleDelete = (index: number, row: budgetInstallCostVO) => {
installCostList.value.splice(index, 1)
//
installCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if(row.installCostId){
toDeletedInstallCostIdList.value.push(row.installCostId);
}
}
//
@ -242,7 +247,19 @@ const handleBatchDelete = () => {
ElMessage.warning('请选择要删除的行')
return
}
installationData.value = installationData.value.filter(item => !selectedRows.value.includes(item))
selectedRows.value.forEach((row) => {
if (row.installCostId) {
toDeletedInstallCostIdList.value.push(row.installCostId);
}
});
installCostList.value = installCostList.value.filter(item => !selectedRows.value.includes(item))
//
installCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = []
}
@ -251,11 +268,11 @@ const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '合计'
if (index === 2) {
sums[index] = '合计(万元)'
return
}
if (column.property === 'budgetAmount' || column.property === 'reducedAmount') {
if (column.property === 'price' || column.property === 'reducePrice') {
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) => {
@ -266,7 +283,7 @@ const getSummaries = (param: any) => {
return prev
}
}, 0)
sums[index] = formatNumber(sums[index])
sums[index] = format2TenThousandNumber(sums[index])
} else {
sums[index] = ''
}
@ -279,8 +296,8 @@ const getSummaries = (param: any) => {
//
const getTotalAmount = () => {
const totalBudgetAmount = installationData.value.reduce((sum, item) => sum + item.budgetAmount, 0)
const totalReducedAmount = installationData.value.reduce((sum, item) => sum + item.reducedAmount, 0)
const totalBudgetAmount = installCostList.value.reduce((sum, item) => sum + Number(item.price), 0)
const totalReducedAmount = installCostList.value.reduce((sum, item) => sum + Number(item.reducePrice), 0)
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
@ -290,7 +307,8 @@ const getTotalAmount = () => {
//
defineExpose({
getTotalAmount,
installationData
installCostList,
toDeletedInstallCostIdList
})
</script>
@ -299,6 +317,12 @@ defineExpose({
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
text-align: center;
font-size: 16px;

@ -1,17 +1,28 @@
<template>
<div class="labor-cost">
<!-- 标题 -->
<div class="title">人工费预算明细表</div>
<!-- 操作按钮 -->
<div class="actions">
<el-button type="primary" @click="showAddDialog = true">添加</el-button>
<el-button type="danger" @click="handleBatchDelete"></el-button>
</div>
<el-card class="table-card">
<template #header>
<div class="card-header">
<span class="title">人工费预算明细表</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="handleAddRows">
<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="laborData"
:data="budgetLaborCostList"
border
style="width: 100%"
:summary-method="getSummaries"
@ -22,105 +33,148 @@
<!-- 预算成本 -->
<el-table-column label="预算成本" align="center">
<el-table-column prop="personnelCategory" label="人员类别" width="120">
<el-table-column prop="sortOrder" label="序号" width="60" align="center" />
<el-table-column prop="personnelCategory" label="人员类别" width="150">
<template #default="scope">
<el-select v-model="scope.row.personnelCategory" placeholder="请选择">
<el-option
v-for="item in personnelCategories"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input v-model="scope.row.personnelCategory" placeholder="请输入人员类别" />
</template>
</el-table-column>
<el-table-column prop="personCount" label="人数" width="80" align="right">
<el-table-column prop="peopleNumber" label="人数" width="120">
<template #default="scope">
<el-input v-model="scope.row.personCount" @input="calculateBudgetAmount(scope.row)" />
<el-input-number
v-model="scope.row.peopleNumber"
:min="0"
:precision="0"
size="small"
@change="calculateBudgetAmount(scope.row)"
style="width: 106px"
/>
</template>
</el-table-column>
<el-table-column prop="cumulativeTime" label="累计时间(月)" width="120" align="right">
<el-table-column prop="cumulativeTime" label="累计时间(月)" width="120">
<template #default="scope">
<el-input v-model="scope.row.cumulativeTime" @input="calculateBudgetAmount(scope.row)" />
<el-input-number
v-model="scope.row.cumulativeTime"
:min="0"
:precision="2"
size="small"
@change="calculateBudgetAmount(scope.row)"
style="width: 106px"
/>
</template>
</el-table-column>
<el-table-column prop="monthlyRatio" label="月平均投入比例(%)" width="150" align="right">
<el-table-column prop="monthRate" label="月平均投入比例(%)" width="150">
<template #default="scope">
<el-input v-model="scope.row.monthlyRatio" @input="calculateBudgetAmount(scope.row)" />
<el-input-number
v-model="scope.row.monthRate"
:min="0"
:max="100"
:precision="2"
size="small"
@change="calculateBudgetAmount(scope.row)"
style="width: 136px"
/>
</template>
</el-table-column>
<el-table-column prop="laborStandard" label="人工标准(元/人月)" width="150" align="right">
<el-table-column prop="artificialStandard" label="人工标准(元/人月)" width="150">
<template #default="scope">
<el-input v-model="scope.row.laborStandard" @input="calculateBudgetAmount(scope.row)" />
<el-input-number
v-model="scope.row.artificialStandard"
:min="0"
:precision="2"
:step="10"
size="small"
@change="calculateBudgetAmount(scope.row)"
style="width: 136px"
/>
</template>
</el-table-column>
<el-table-column prop="budgetAmount" label="金额(万元)" width="120" align="right">
<el-table-column prop="price" label="金额(万元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.budgetAmount) }}
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
</el-table-column>
<!-- 降成本后预算成本 -->
<el-table-column label="降成本后预算成本" align="center">
<el-table-column prop="reducedPersonnelCategory" label="人员类别" width="120">
<el-table-column prop="reducePersonnelCategory" label="人员类别" width="150">
<template #default="scope">
<el-select v-model="scope.row.reducedPersonnelCategory" placeholder="请选择">
<el-option
v-for="item in personnelCategories"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input v-model="scope.row.reducePersonnelCategory" placeholder="请输入人员类别" />
</template>
</el-table-column>
<el-table-column prop="reducedPersonCount" label="人数" width="80" align="right">
<el-table-column prop="reducePeopleNumber" label="人数" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedPersonCount" @input="calculateReducedAmount(scope.row)" />
<el-input-number
v-model="scope.row.reducePeopleNumber"
:min="0"
:precision="0"
size="small"
@change="calculateReducedAmount(scope.row)"
style="width: 106px"
/>
</template>
</el-table-column>
<el-table-column prop="reducedCumulativeTime" label="累计时间(月)" width="120" align="right">
<el-table-column prop="reduceCumulativeTime" label="累计时间(月)" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedCumulativeTime" @input="calculateReducedAmount(scope.row)" />
<el-input-number
v-model="scope.row.reduceCumulativeTime"
:min="0"
:precision="2"
size="small"
@change="calculateReducedAmount(scope.row)"
style="width: 106px"
/>
</template>
</el-table-column>
<el-table-column prop="reducedMonthlyRatio" label="月平均投入比例(%)" width="150" align="right">
<el-table-column prop="reduceMonthRate" label="月平均投入比例(%)" width="150">
<template #default="scope">
<el-input v-model="scope.row.reducedMonthlyRatio" @input="calculateReducedAmount(scope.row)" />
<el-input-number
v-model="scope.row.reduceMonthRate"
:min="0"
:max="100"
:precision="2"
size="small"
@change="calculateReducedAmount(scope.row)"
style="width: 136px"
/>
</template>
</el-table-column>
<el-table-column prop="reducedLaborStandard" label="人工标准(元/人月)" width="150" align="right">
<el-table-column prop="reduceArtificialStandard" label="人工标准(元/人月)" width="150">
<template #default="scope">
<el-input v-model="scope.row.reducedLaborStandard" @input="calculateReducedAmount(scope.row)" />
<el-input-number
v-model="scope.row.reduceArtificialStandard"
:min="0"
:precision="2"
:step="10"
size="small"
@change="calculateReducedAmount(scope.row)"
style="width: 136px"
/>
</template>
</el-table-column>
<el-table-column prop="reducedAmount" label="金额(万元)" width="120" align="right">
<el-table-column prop="reducePrice" label="金额(万元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reducedAmount) }}
{{ format2TenThousandNumber(scope.row.reducePrice) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="costReductionPlan" label="降成本方案" width="200">
<el-table-column prop="reduceProposal" label="降成本方案" width="200">
<template #default="scope">
<el-input v-model="scope.row.costReductionPlan" type="textarea" :rows="2" />
<el-input v-model="scope.row.reduceProposal" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加行数弹窗 -->
<el-dialog
v-model="showAddDialog"
title="添加行数"
width="400px"
>
<el-dialog v-model="showAddDialog" title="添加行数" width="400px">
<el-form>
<el-form-item label="添加行数">
<el-input-number v-model="addRowCount" :min="1" :max="10" />
@ -131,37 +185,23 @@
<el-button type="primary" @click="handleAddRows"></el-button>
</template>
</el-dialog>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref } from 'vue';
import { budgetLaborCostVO } from '@/api/oa/erp/budgetInfo/market/budgetLaborCost/types';
//
const laborData = ref([
{
personnelCategory: '项目经理',
personCount: 0,
cumulativeTime: 0,
monthlyRatio: 0,
laborStandard: 0,
budgetAmount: 0,
reducedPersonnelCategory: '项目经理',
reducedPersonCount: 0,
reducedCumulativeTime: 0,
reducedMonthlyRatio: 0,
reducedLaborStandard: 0,
reducedAmount: 0,
costReductionPlan: ''
}
])
const budgetLaborCostList = ref<budgetLaborCostVO[]>([]);
const toDeletedLaborCostIdList = ref([]);
//
const selectedRows = ref([])
const selectedRows = ref([]);
//
const showAddDialog = ref(false)
const addRowCount = ref(1)
const showAddDialog = ref(false);
const addRowCount = ref(1);
//
const personnelCategories = ref([
@ -175,128 +215,164 @@ const personnelCategories = ref([
{ label: '材料员', value: '材料员' },
{ label: '预算员', value: '预算员' },
{ label: '其他', value: '其他' }
])
]);
//
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00'
return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
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 calculateBudgetAmount = (row: any) => {
const personCount = parseFloat(row.personCount) || 0
const cumulativeTime = parseFloat(row.cumulativeTime) || 0
const monthlyRatio = parseFloat(row.monthlyRatio) || 0
const laborStandard = parseFloat(row.laborStandard) || 0
const peopleNumber = parseFloat(row.peopleNumber) || 0;
const cumulativeTime = parseFloat(row.cumulativeTime) || 0;
const monthRate = parseFloat(row.monthRate) || 0;
const artificialStandard = parseFloat(row.artificialStandard) || 0;
row.budgetAmount = personCount * cumulativeTime * (monthlyRatio / 100) * laborStandard / 10000 //
}
row.price = peopleNumber * cumulativeTime * (monthRate / 100) * artificialStandard; //yuan
};
//
const calculateReducedAmount = (row: any) => {
const personCount = parseFloat(row.reducedPersonCount) || 0
const cumulativeTime = parseFloat(row.reducedCumulativeTime) || 0
const monthlyRatio = parseFloat(row.reducedMonthlyRatio) || 0
const laborStandard = parseFloat(row.reducedLaborStandard) || 0
const peopleNumber = parseFloat(row.reducePeopleNumber) || 0;
const cumulativeTime = parseFloat(row.reduceCumulativeTime) || 0;
const monthRate = parseFloat(row.reduceMonthRate) || 0;
const artificialStandard = parseFloat(row.reduceArtificialStandard) || 0;
row.reducedAmount = personCount * cumulativeTime * (monthlyRatio / 100) * laborStandard / 10000 //
}
row.reducePrice = peopleNumber * cumulativeTime * (monthRate / 100) * artificialStandard;
};
//
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
selectedRows.value = selection;
};
//
const handleAddRows = () => {
const currentSortOrder =
budgetLaborCostList.value && budgetLaborCostList.value.length > 0 ? Math.max(...budgetLaborCostList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < addRowCount.value; i++) {
laborData.value.push({
budgetLaborCostList.value.push({
sortOrder: currentSortOrder + i + 1,
personnelCategory: '',
personCount: 0,
peopleNumber: 0,
cumulativeTime: 0,
monthlyRatio: 0,
laborStandard: 0,
budgetAmount: 0,
reducedPersonnelCategory: '',
reducedPersonCount: 0,
reducedCumulativeTime: 0,
reducedMonthlyRatio: 0,
reducedLaborStandard: 0,
reducedAmount: 0,
costReductionPlan: ''
})
monthRate: 0,
artificialStandard: 0,
price: 0,
reducePersonnelCategory: '',
reducePeopleNumber: 0,
reduceCumulativeTime: 0,
reduceMonthRate: 0,
reduceArtificialStandard: 0,
reducePrice: 0,
reduceProposal: ''
});
}
showAddDialog.value = false
addRowCount.value = 1
}
showAddDialog.value = false;
addRowCount.value = 1;
};
//
const handleDelete = (index: number) => {
laborData.value.splice(index, 1)
}
const handleDelete = (index: number, row: budgetLaborCostVO) => {
budgetLaborCostList.value.splice(index, 1);
//
budgetLaborCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.laborCostId) {
toDeletedLaborCostIdList.value.push(row.laborCostId);
}
};
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行')
return
ElMessage.warning('请选择要删除的行');
return;
}
laborData.value = laborData.value.filter(item => !selectedRows.value.includes(item))
selectedRows.value = []
}
budgetLaborCostList.value = budgetLaborCostList.value.filter((item) => !selectedRows.value.includes(item));
selectedRows.value.forEach((row) => {
if (row.laborCostId) {
toDeletedLaborCostIdList.value.push(row.laborCostId);
}
});
//
budgetLaborCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = [];
};
//
const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '合计'
return
if (index === 2) {
sums[index] = '合计(万元)';
return;
}
if (column.property === 'budgetAmount' || column.property === 'reducedAmount') {
const values = data.map((item: any) => Number(item[column.property]))
if (column.property === 'price' || column.property === 'reducePrice') {
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)
const value = Number(curr);
if (!Number.isNaN(value)) {
return prev + curr
return prev + curr;
} else {
return prev
return prev;
}
}, 0)
sums[index] = formatNumber(sums[index])
}, 0);
sums[index] = format2TenThousandNumber(sums[index]);
} else {
sums[index] = ''
sums[index] = '';
}
} else {
sums[index] = ''
sums[index] = '';
}
})
return sums
}
});
return sums;
};
//
const getTotalAmount = () => {
const totalBudgetAmount = laborData.value.reduce((sum, item) => sum + item.budgetAmount, 0)
const totalReducedAmount = laborData.value.reduce((sum, item) => sum + item.reducedAmount, 0)
const totalBudgetAmount = budgetLaborCostList.value.reduce((sum, item) => sum + Number(item.price), 0);
const totalReducedAmount = budgetLaborCostList.value.reduce((sum, item) => sum + Number(item.reducePrice), 0);
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
}
}
};
};
//
defineExpose({
getTotalAmount,
laborData
})
budgetLaborCostList,
toDeletedLaborCostIdList
});
</script>
<style scoped>
.labor-cost {
padding: 20px;
padding: 6px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {

@ -1,26 +1,40 @@
<template>
<el-card class="table-card">
<template #header>
<div class="card-header">
<span class="title">材料费预算明细表</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="handleAddRows"></el-button>
<el-button type="primary" size="small" @click="handleShowMaterial"></el-button>
<el-button type="danger" size="small" @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button>
<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="handleAddRows">
<el-icon>
<Plus />
</el-icon>
添加</el-button>
<el-button type="primary" size="small" @click="handleShowMaterial">
<el-icon>
<Search />
</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="budgetMaterialCostList" border style="width: 100%"
:summary-method="getSummaries"
show-summary
:span-method="spanMethod"
@selection-change="handleSelectionChange">
<el-table
:data="budgetMaterialCostList"
border
style="width: 100%"
:summary-method="getSummaries"
show-summary
:span-method="spanMethod"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<!-- 基础信息 -->
@ -33,26 +47,13 @@
</el-table-column>
<el-table-column prop="materialName" label="材料名称" width="200">
<template #default="scope">
<el-input v-model="scope.row.materialName" type="textarea" :rows="1" :disabled="scope.row.materialId!==null"/>
<el-input v-model="scope.row.materialName" type="textarea" :rows="1" :disabled="scope.row.materialId !== null" />
</template>
</el-table-column>
<el-table-column prop="unitName" label="单位" width="120">
<template #default="scope">
<el-select
v-model="scope.row.unitName"
placeholder="单位"
filterable
allow-create
default-first-option
style="width: 100%"
clearable
>
<el-option
v-for="unit in unitList"
:key="unit.unitName"
:label="unit.unitName"
:value="unit.unitName"
/>
<el-select v-model="scope.row.unitName" placeholder="单位" filterable allow-create default-first-option style="width: 100%" clearable>
<el-option v-for="unit in unitList" :key="unit.unitName" :label="unit.unitName" :value="unit.unitName" />
</el-select>
</template>
</el-table-column>
@ -60,17 +61,17 @@
<!-- 预算成本 -->
<el-table-column label="预算成本" align="center">
<el-table-column prop="unitPrice" label="单价(元)" width="160" align="right">
<el-table-column prop="unitPrice" label="单价(元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.unitPrice" @input="calculateBudgetAmount(scope.row)" style="width:130px"/>
<el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" size="small" @change="calculateBudgetAmount(scope.row)" style="width: 130px" />
</template>
</el-table-column>
<el-table-column prop="amount" label="购置数量" width="160" align="right">
<el-table-column prop="amount" label="购置数量" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.amount" @input="calculateBudgetAmount(scope.row)" style="width:130px"/>
<el-input-number v-model="scope.row.amount" :min="0" :precision="2" size="small" @change="calculateBudgetAmount(scope.row)" style="width: 130px" />
</template>
</el-table-column>
<el-table-column prop="price" label="金额(元)" width="120" align="right">
<el-table-column prop="price" label="金额(元)" width="120">
<template #default="scope">
{{ formatNumber(scope.row.price) }}
</template>
@ -79,24 +80,24 @@
<!-- 降成本后预算成本 -->
<el-table-column label="降成本后预算成本" align="center">
<el-table-column prop="reduceUnitPrice" label="单价(元)" width="160" align="right">
<el-table-column prop="reduceUnitPrice" label="单价(元)" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.reduceUnitPrice" @input="calculateReducedAmount(scope.row)" style="width:130px"/>
<el-input-number v-model="scope.row.reduceUnitPrice" :min="0" :precision="2" size="small" @change="calculateReducedAmount(scope.row)" style="width: 130px" />
</template>
</el-table-column>
<el-table-column prop="reduceAmount" label="购置数量" width="160" align="right">
<el-table-column prop="reduceAmount" label="购置数量" width="160">
<template #default="scope">
<el-input-number v-model="scope.row.reduceAmount" @input="calculateReducedAmount(scope.row)" style="width:130px"/>
<el-input-number v-model="scope.row.reduceAmount" :min="0" :precision="2" size="small" @change="calculateReducedAmount(scope.row)" style="width: 130px" />
</template>
</el-table-column>
<el-table-column prop="reducePrice" label="金额(元)" width="120" align="right">
<el-table-column prop="reducePrice" label="金额(元)" width="120">
<template #default="scope">
{{ formatNumber(scope.row.reducePrice) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="reduceProposal" label="降成本方案" width="200">
<el-table-column prop="reduceProposal" label="降成本方案" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.reduceProposal" type="textarea" :rows="2" />
</template>
@ -104,7 +105,7 @@
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -115,15 +116,15 @@
<!-- 左侧物料列表 -->
<div class="material-list-section">
<!-- 搜索框 -->
<!-- <div class="search-section">-->
<!-- <el-input v-model="materialSearchKeyword" placeholder="请输入物料号、材料名称或规格进行搜索" clearable @input="handleSearch">-->
<!-- <template #prefix>-->
<!-- <el-icon>-->
<!-- <Search />-->
<!-- </el-icon>-->
<!-- </template>-->
<!-- </el-input>-->
<!-- </div>-->
<!-- <div class="search-section">-->
<!-- <el-input v-model="materialSearchKeyword" placeholder="请输入物料号、材料名称或规格进行搜索" clearable @input="handleSearch">-->
<!-- <template #prefix>-->
<!-- <el-icon>-->
<!-- <Search />-->
<!-- </el-icon>-->
<!-- </template>-->
<!-- </el-input>-->
<!-- </div>-->
<!-- 物料表格 -->
<el-table
@ -140,9 +141,9 @@
<el-table-column prop="materialBrand" label="品牌" />
<el-table-column prop="materialModel" label="型号" />
<el-table-column prop="unitName" label="单位" width="80" />
<el-table-column prop="purchasePrice" label="单价(元)" width="100" align="right">
<el-table-column prop="purchasePrice" label="单价(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.unitPrice) }}
{{ formatNumber(scope.row.purchasePrice) }}
</template>
</el-table-column>
</el-table>
@ -194,7 +195,6 @@
</template>
</el-dialog>
</el-card>
</template>
<script setup lang="ts">
@ -206,8 +206,14 @@ import { getBaseUnitInfoList } from '@/api/oa/base/unitInfo';
import { UnitInfoVO } from '@/api/oa/base/unitInfo/types';
import { MaterialInfoVO, MaterialInfoQuery } from '@/api/oa/base/materialInfo/types';
// IDprops
const props = defineProps<{
budgetId: string;
}>();
//
const budgetMaterialCostList = ref<budgetMaterialCostVO[]>([]);
const toDeletedMaterialCostIdList = ref([]);
//
const selectedRows = ref([]);
@ -234,22 +240,21 @@ const materialQueryParams = ref({
params: {}
});
//
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection;
};
const handleShowMaterial = () =>{
showMaterialDialog.value = true;
getMaterialList();
}
const handleShowMaterial = () => {
showMaterialDialog.value = true;
getMaterialList();
};
/** 查询物料信息列表 */
const getMaterialList = async () => {
materialLoading.value = true;
const res = await listMaterialInfo(materialQueryParams.value);
console.log(res)
console.log(res);
materialList.value = res.rows;
materialTotal.value = res.total;
updateTableSelection();
@ -258,19 +263,20 @@ const getMaterialList = async () => {
//
const handleAddRows = () => {
const currentSortOrder = budgetMaterialCostList.value && budgetMaterialCostList.value.length > 0
? Math.max(...budgetMaterialCostList.value.map((item) => item.sortOrder))
: 0;
const currentSortOrder =
budgetMaterialCostList.value && budgetMaterialCostList.value.length > 0
? Math.max(...budgetMaterialCostList.value.map((item) => item.sortOrder))
: 0;
for (let i = 0; i < addRowCount.value; i++) {
budgetMaterialCostList.value.push({
sortOrder: currentSortOrder + i + 1,
materialId:null,
materialId: null,
materialCode: '',
materialName: '',
unitId: undefined,
unitName:'',
unitPrice:0,
amount:0,
unitName: '',
unitPrice: 0,
amount: 0,
reduceUnitPrice: 0,
reduceAmount: 0,
reducePrice: 0,
@ -287,7 +293,6 @@ const handleSearch = () => {
updateTableSelection();
};
//
const updateTableSelection = () => {
isUpdatingSelection.value = true;
@ -359,12 +364,15 @@ const handleMaterialSelectionChange = (selection: any[]) => {
};
//
const handleDelete = (index: number) => {
const handleDelete = (index: number, row: budgetMaterialCostVO) => {
budgetMaterialCostList.value.splice(index, 1);
//
budgetMaterialCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if(row.materialCostId){
toDeletedMaterialCostIdList.value.push(row.materialCostId);
}
};
//
@ -373,6 +381,12 @@ const handleBatchDelete = () => {
ElMessage.warning('请选择要删除的行');
return;
}
selectedRows.value.forEach(selectedRow =>{
if(selectedRow.materialCostId){
toDeletedMaterialCostIdList.value.push(selectedRow.materialCostId);
}
})
budgetMaterialCostList.value = budgetMaterialCostList.value.filter((item) => !selectedRows.value.includes(item));
//
budgetMaterialCostList.value.forEach((item, index) => {
@ -404,6 +418,7 @@ const confirmMaterialSelection = () => {
const currentSortOrder = budgetMaterialCostList.value.length > 0 ? Math.max(...budgetMaterialCostList.value.map((item) => item.sortOrder)) : 0;
budgetMaterialCostList.value.push({
sortOrder: currentSortOrder + 1,
materialId: material.materialId,
materialCode: material.materialCode,
materialName: material.materialName,
unitName: material.unitName,
@ -427,12 +442,16 @@ const confirmMaterialSelection = () => {
materialSearchKeyword.value = '';
};
//
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00';
return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
return parseFloat(value).toFixed(2);
};
// ,
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
return (parseFloat(value) / 10000).toFixed(2);
};
//
@ -449,7 +468,6 @@ const calculateReducedAmount = (row: any) => {
row.reducePrice = unitPrice * quantity;
};
//
const getSummaries = (param: any) => {
const { columns, data } = param;
@ -462,8 +480,10 @@ const getSummaries = (param: any) => {
if (
column.property === 'sortOrder' ||
column.property === 'materialCode' ||
column.property === 'unit' ||
column.property === 'costReductionPlan'
column.property === 'unitName' ||
column.property === 'unitPrice' ||
column.property === 'reduceUnitPrice' ||
column.property === 'reduceProposal'
) {
sums[index] = '';
return;
@ -483,7 +503,11 @@ const getSummaries = (param: any) => {
return prev;
}
}, 0);
sums[index] = formatNumber(sums[index]);
if (column.property === 'price' || column.property === 'reducePrice') {
sums[index] = format2TenThousandNumber(sums[index]);
} else {
sums[index] = formatNumber(sums[index]);
}
} else {
sums[index] = '';
}
@ -498,23 +522,22 @@ const spanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
//
if (columnIndex < 4) {
//
return columnIndex === 0 ? [1, 4] : [0, 0]
return columnIndex === 0 ? [1, 4] : [0, 0];
}
}
return [1, 1]
return [1, 1];
};
//
const getTotalAmount = () => {
const totalBudgetAmount = budgetMaterialCostList.value.reduce((sum, item) => sum + item.price, 0);
const totalReducedAmount = budgetMaterialCostList.value.reduce((sum, item) => sum + item.reducePrice, 0);
const totalBudgetAmount = budgetMaterialCostList.value.reduce((sum, item) => sum + Number(item.price), 0);
const totalReducedAmount = budgetMaterialCostList.value.reduce((sum, item) => sum + Number(item.reducePrice), 0);
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
};
};
const unitList = ref<UnitInfoVO[]>([]);
/** 查询单位信息列表 */
const getUnitList = async () => {
@ -528,7 +551,8 @@ onMounted(() => {
//
defineExpose({
getTotalAmount,
budgetMaterialCostList
budgetMaterialCostList,
toDeletedMaterialCostIdList
});
</script>

@ -1,73 +1,73 @@
<template>
<div class="other-cost">
<!-- 标题 -->
<div class="title">其他费用预算表</div>
<!-- 操作按钮 -->
<div class="actions">
<el-button type="primary" @click="showAddDialog = true">添加</el-button>
<el-button type="danger" @click="handleBatchDelete"></el-button>
</div>
<el-card class="table-card">
<template #header>
<div class="card-header">
<span class="title">其他费用预算表</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="handleAddRows">
<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="otherData"
border
style="width: 100%"
:summary-method="getSummaries"
show-summary
@selection-change="handleSelectionChange"
>
<el-table :data="otherCostList" border style="width: 100%" :summary-method="getSummaries" show-summary @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!-- 预算成本 -->
<el-table-column label="预算成本" align="center">
<el-table-column prop="serialNumber" label="序号" width="80" align="center" />
<el-table-column prop="project" label="项目" width="200">
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="itemDesc" label="项目" width="200">
<template #default="scope">
<el-input v-model="scope.row.project" />
<el-input v-model="scope.row.itemDesc" />
</template>
</el-table-column>
<el-table-column prop="budgetAmount" label="金额(元)" width="120" align="right">
<el-table-column prop="price" label="金额(元)" width="160">
<template #default="scope">
<el-input v-model="scope.row.budgetAmount" @input="formatBudgetAmount(scope.row)" />
<el-input-number v-model="scope.row.price" :min="0" :precision="2" size="small" />
</template>
</el-table-column>
</el-table-column>
<!-- 降成本后预算成本 -->
<el-table-column label="降成本后预算成本" align="center">
<el-table-column prop="reducedProject" label="项目" width="200">
<el-table-column prop="reduceItemDesc" label="项目" width="200">
<template #default="scope">
<el-input v-model="scope.row.reducedProject" />
<el-input v-model="scope.row.reduceItemDesc" />
</template>
</el-table-column>
<el-table-column prop="reducedAmount" label="金额(元)" width="120" align="right">
<el-table-column prop="reducePrice" label="金额(元)" width="160">
<template #default="scope">
<el-input v-model="scope.row.reducedAmount" @input="formatReducedAmount(scope.row)" />
<el-input-number v-model="scope.row.reducePrice" :min="0" :precision="2" size="small" />
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="costReductionPlan" label="降成本方案" width="200">
<el-table-column prop="reduceProposal" label="降成本方案" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.costReductionPlan" type="textarea" :rows="2" />
<el-input v-model="scope.row.reduceProposal" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加行数弹窗 -->
<el-dialog
v-model="showAddDialog"
title="添加行数"
width="400px"
>
<el-dialog v-model="showAddDialog" title="添加行数" width="400px">
<el-form>
<el-form-item label="添加行数">
<el-input-number v-model="addRowCount" :min="1" :max="10" />
@ -78,134 +78,155 @@
<el-button type="primary" @click="handleAddRows"></el-button>
</template>
</el-dialog>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref } from 'vue';
import { budgetTravelCostVO } from '@/api/oa/erp/budgetInfo/market/budgetTravelCost/types';
import { budgetOtherCostVO } from '@/api/oa/erp/budgetInfo/market/budgetOtherCost/types';
import { budgetMaterialCostVO } from '@/api/oa/erp/budgetInfo/market/budgetMaterialCost/types';
const loaded = ref(false);
//
const otherData = ref([
{
serialNumber: 1,
project: '',
budgetAmount: 0,
reducedProject: '',
reducedAmount: 0,
costReductionPlan: ''
}
])
const otherCostList = ref<budgetOtherCostVO[]>([]);
const toDeletedOtherCostIdList = ref([]);
//
const selectedRows = ref([])
const selectedRows = ref([]);
//
const showAddDialog = ref(false)
const addRowCount = ref(1)
const showAddDialog = ref(false);
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 formatBudgetAmount = (row: any) => {
const value = parseFloat(row.budgetAmount) || 0
row.budgetAmount = value
}
const value = parseFloat(row.price) || 0;
row.price = value;
};
//
const formatReducedAmount = (row: any) => {
const value = parseFloat(row.reducedAmount) || 0
row.reducedAmount = value
}
const value = parseFloat(row.reducePrice) || 0;
row.reducePrice = value;
};
//
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
selectedRows.value = selection;
};
//
const handleAddRows = () => {
const currentMaxSerial = Math.max(...otherData.value.map(item => item.serialNumber))
const currentSortOrder = otherCostList.value && otherCostList.value.length > 0 ? Math.max(...otherCostList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < addRowCount.value; i++) {
otherData.value.push({
serialNumber: currentMaxSerial + i + 1,
project: '',
budgetAmount: 0,
reducedProject: '',
reducedAmount: 0,
costReductionPlan: ''
})
otherCostList.value.push({
sortOrder: currentSortOrder + i + 1,
itemDesc: '',
price: 0,
reduceItemDesc: '',
reducePrice: 0,
reduceProposal: ''
});
}
showAddDialog.value = false
addRowCount.value = 1
}
showAddDialog.value = false;
addRowCount.value = 1;
};
//
const handleDelete = (index: number) => {
otherData.value.splice(index, 1)
const handleDelete = (index: number, row: budgetOtherCostVO) => {
otherCostList.value.splice(index, 1);
//
otherData.value.forEach((item, index) => {
item.serialNumber = index + 1
})
}
otherCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.otherCostId) {
toDeletedOtherCostIdList.value.push(row.otherCostId);
}
};
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行')
return
ElMessage.warning('请选择要删除的行');
return;
}
otherData.value = otherData.value.filter(item => !selectedRows.value.includes(item))
selectedRows.value.forEach((row) => {
if (row.otherCostId) {
toDeletedOtherCostIdList.value.push(row.otherCostId);
}
});
otherCostList.value = otherCostList.value.filter((item) => !selectedRows.value.includes(item));
//
otherData.value.forEach((item, index) => {
item.serialNumber = index + 1
})
selectedRows.value = []
}
otherCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = [];
};
//
const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '合计'
return
if (index === 2) {
sums[index] = '合计';
return;
}
if (column.property === 'budgetAmount' || column.property === 'reducedAmount') {
const values = data.map((item: any) => Number(item[column.property]))
if (column.property === 'price' || column.property === 'reducePrice') {
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)
const value = Number(curr);
if (!Number.isNaN(value)) {
return prev + curr
return prev + curr;
} else {
return prev
return prev;
}
}, 0)
sums[index] = sums[index].toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}, 0);
sums[index] = sums[index].toFixed(2);
} else {
sums[index] = ''
sums[index] = '';
}
} else {
sums[index] = ''
sums[index] = '';
}
})
return sums
}
});
return sums;
};
//
const getTotalAmount = () => {
const totalBudgetAmount = otherData.value.reduce((sum, item) => sum + item.budgetAmount, 0)
const totalReducedAmount = otherData.value.reduce((sum, item) => sum + item.reducedAmount, 0)
const totalBudgetAmount = otherCostList.value.reduce((sum, item) => sum + item.price, 0);
const totalReducedAmount = otherCostList.value.reduce((sum, item) => sum + item.reducePrice, 0);
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
}
}
};
};
//
defineExpose({
getTotalAmount,
otherData
})
otherCostList,
toDeletedOtherCostIdList
});
</script>
<style scoped>
@ -213,6 +234,12 @@ defineExpose({
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
text-align: center;
font-size: 16px;
@ -228,8 +255,3 @@ defineExpose({
margin-right: 10px;
}
</style>

@ -1,154 +1,154 @@
<template>
<div class="travel-cost">
<!-- 标题 -->
<div class="title">差旅费预算明细表</div>
<!-- 操作按钮 -->
<div class="actions">
<el-button type="primary" @click="showAddDialog = true">添加</el-button>
<el-button type="danger" @click="handleBatchDelete"></el-button>
</div>
<el-card class="table-card">
<template #header>
<div class="card-header">
<span class="title">差旅费预算明细表</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="handleAddRows">
<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="travelData"
border
style="width: 100%"
:summary-method="getSummaries"
show-summary
@selection-change="handleSelectionChange"
>
<el-table :data="travelCostList" border style="width: 100%" :summary-method="getSummaries" show-summary @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!-- 预算成本 -->
<el-table-column label="预算成本" align="center">
<el-table-column prop="serialNumber" label="序号" width="80" align="center" />
<el-table-column prop="destination" label="出差地点" width="120">
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="tripLocation" label="出差地点" width="120">
<template #default="scope">
<el-input v-model="scope.row.destination" />
<el-input v-model="scope.row.tripLocation" />
</template>
</el-table-column>
<el-table-column prop="purpose" label="事由" width="150">
<el-table-column prop="reason" label="事由" width="150">
<template #default="scope">
<el-input v-model="scope.row.purpose" type="textarea" :rows="2" />
<el-input v-model="scope.row.reason" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column prop="times" label="次数" width="80" align="right">
<el-table-column prop="frequency" label="次数" width="120">
<template #default="scope">
<el-input v-model="scope.row.times" @input="calculateBudgetSubtotal(scope.row)" />
<el-input-number v-model="scope.row.frequency" :min="0" :precision="0" size="small" @change="calculateBudgetSubtotal(scope.row)" style="width: 106px" />
</template>
</el-table-column>
<el-table-column prop="personCount" label="人数" width="80" align="right">
<el-table-column prop="peopleNumber" label="人数" width="120">
<template #default="scope">
<el-input v-model="scope.row.personCount" @input="calculateBudgetSubtotal(scope.row)" />
<el-input-number v-model="scope.row.peopleNumber" :min="0" :precision="0" size="small" @change="calculateBudgetSubtotal(scope.row)" style="width: 106px" />
</template>
</el-table-column>
<el-table-column prop="days" label="天数" width="80" align="right">
<el-table-column prop="days" label="天数" width="130">
<template #default="scope">
<el-input v-model="scope.row.days" @input="calculateBudgetSubtotal(scope.row)" />
<el-input-number v-model="scope.row.days" :min="0" :precision="0" size="small" @change="calculateBudgetSubtotal(scope.row)" style="width: 116px" />
</template>
</el-table-column>
<el-table-column prop="accommodationStandard" label="住宿标准(元)" width="120" align="right">
<el-table-column prop="stayStandard" label="住宿标准(元)" width="140">
<template #default="scope">
<el-input v-model="scope.row.accommodationStandard" @input="calculateBudgetSubtotal(scope.row)" />
<el-input-number v-model="scope.row.stayStandard" :min="0" :precision="2" size="small" :step="10" @change="calculateBudgetSubtotal(scope.row)" style="width: 126px" />
</template>
</el-table-column>
<el-table-column prop="roundTripFee" label="往返路费(元)" width="120" align="right">
<el-table-column prop="travelExpenses" label="往返路费(元)" width="140">
<template #default="scope">
<el-input v-model="scope.row.roundTripFee" @input="calculateBudgetSubtotal(scope.row)" />
<el-input-number v-model="scope.row.travelExpenses" :min="0" :precision="2" size="small" :step="10" @change="calculateBudgetSubtotal(scope.row)" style="width: 126px" />
</template>
</el-table-column>
<el-table-column prop="accommodationFee" label="住宿费(元)" width="120" align="right">
<el-table-column prop="stayCosts" label="住宿费(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.accommodationFee) }}
{{ formatNumber(scope.row.stayCosts) }}
</template>
</el-table-column>
<el-table-column prop="subsidy" label="补贴(元)" width="120" align="right">
<el-table-column prop="subsidyCosts" label="补贴(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.subsidy) }}
{{ formatNumber(scope.row.subsidyCosts) }}
</template>
</el-table-column>
<el-table-column prop="budgetSubtotal" label="小计(万元)" width="120" align="right">
<el-table-column prop="subtotalCosts" label="小计(万元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.budgetSubtotal) }}
{{ format2TenThousandNumber(scope.row.subtotalCosts) }}
</template>
</el-table-column>
</el-table-column>
<!-- 降成本后预算成本 -->
<el-table-column label="降成本后预算成本" align="center">
<el-table-column prop="reducedSerialNumber" label="序号" width="80" align="center" />
<el-table-column prop="reducedDestination" label="出差地点" width="120">
<el-table-column prop="reduceSortOrder" label="序号" width="80" align="center" />
<el-table-column prop="reduceTripLocation" label="出差地点" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedDestination" />
<el-input v-model="scope.row.reduceTripLocation" />
</template>
</el-table-column>
<el-table-column prop="reducedPurpose" label="事由" width="150">
<el-table-column prop="reduceReason" label="事由" width="150">
<template #default="scope">
<el-input v-model="scope.row.reducedPurpose" type="textarea" :rows="2" />
<el-input v-model="scope.row.reduceReason" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column prop="reducedTimes" label="次数" width="80" align="right">
<el-table-column prop="reduceFrequency" label="次数" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedTimes" @input="calculateReducedSubtotal(scope.row)" />
<el-input-number v-model="scope.row.reduceFrequency" :min="0" :precision="0" size="small" @change="calculateReducedSubtotal(scope.row)" style="width: 106px" />
</template>
</el-table-column>
<el-table-column prop="reducedPersonCount" label="人数" width="80" align="right">
<el-table-column prop="reducePeopleNumber" label="人数" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedPersonCount" @input="calculateReducedSubtotal(scope.row)" />
<el-input-number v-model="scope.row.reducePeopleNumber" :min="0" :precision="0" size="small" @input="calculateReducedSubtotal(scope.row)" style="width: 106px" />
</template>
</el-table-column>
<el-table-column prop="reducedDays" label="天数" width="80" align="right">
<el-table-column prop="reduceDayNumber" label="天数" width="120">
<template #default="scope">
<el-input v-model="scope.row.reducedDays" @input="calculateReducedSubtotal(scope.row)" />
<el-input-number v-model="scope.row.reduceDayNumber" :min="0" :precision="0" size="small" @change="calculateReducedSubtotal(scope.row)" style="width: 106px" />
</template>
</el-table-column>
<el-table-column prop="reducedAccommodationStandard" label="住宿标准(元)" width="120" align="right">
<el-table-column prop="reduceStayStandard" label="住宿标准(元)" width="140">
<template #default="scope">
<el-input v-model="scope.row.reducedAccommodationStandard" @input="calculateReducedSubtotal(scope.row)" />
<el-input-number v-model="scope.row.reduceStayStandard" :min="0" :precision="2" size="small" :step="10" @input="calculateReducedSubtotal(scope.row)" style="width: 126px" />
</template>
</el-table-column>
<el-table-column prop="reducedRoundTripFee" label="往返路费(元)" width="120" align="right">
<el-table-column prop="reduceTravelExpenses" label="往返路费(元)" width="140">
<template #default="scope">
<el-input v-model="scope.row.reducedRoundTripFee" @input="calculateReducedSubtotal(scope.row)" />
<el-input-number v-model="scope.row.reduceTravelExpenses" :min="0" :precision="2" size="small" :step="10" @change="calculateReducedSubtotal(scope.row)" style="width: 126px" />
</template>
</el-table-column>
<el-table-column prop="reducedAccommodationFee" label="住宿费(元)" width="120" align="right">
<el-table-column prop="reduceStayCosts" label="住宿费(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reducedAccommodationFee) }}
{{ formatNumber(scope.row.reduceStayCosts) }}
</template>
</el-table-column>
<el-table-column prop="reducedSubsidy" label="补贴(元)" width="120" align="right">
<el-table-column prop="reduceSubsidyCosts" label="补贴(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reducedSubsidy) }}
{{ formatNumber(scope.row.reduceSubsidyCosts) }}
</template>
</el-table-column>
<el-table-column prop="reducedSubtotal" label="小计(万元)" width="120" align="right">
<el-table-column prop="reduceSubtotalCosts" label="小计(万元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reducedSubtotal) }}
{{ format2TenThousandNumber(scope.row.reduceSubtotalCosts) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="costReductionPlan" label="降成本方案" width="200">
<el-table-column prop="reduceProposal" label="降成本方案" width="200">
<template #default="scope">
<el-input v-model="scope.row.costReductionPlan" type="textarea" :rows="2" />
<el-input v-model="scope.row.reduceProposal" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDelete(scope.$index)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加行数弹窗 -->
<el-dialog
v-model="showAddDialog"
title="添加行数"
width="400px"
>
<el-dialog v-model="showAddDialog" title="添加行数" width="400px">
<el-form>
<el-form-item label="添加行数">
<el-input-number v-model="addRowCount" :min="1" :max="10" />
@ -159,203 +159,213 @@
<el-button type="primary" @click="handleAddRows"></el-button>
</template>
</el-dialog>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref } from 'vue';
import { budgetTravelCostVO } from '@/api/oa/erp/budgetInfo/market/budgetTravelCost/types';
//
const travelData = ref([
{
serialNumber: 1,
destination: '',
purpose: '',
times: 0,
personCount: 0,
days: 0,
accommodationStandard: 0,
roundTripFee: 0,
accommodationFee: 0,
subsidy: 0,
budgetSubtotal: 0,
reducedSerialNumber: 1,
reducedDestination: '',
reducedPurpose: '',
reducedTimes: 0,
reducedPersonCount: 0,
reducedDays: 0,
reducedAccommodationStandard: 0,
reducedRoundTripFee: 0,
reducedAccommodationFee: 0,
reducedSubsidy: 0,
reducedSubtotal: 0,
costReductionPlan: ''
}
])
const travelCostList = ref<budgetTravelCostVO[]>([]);
const toDeletedTravelCostIdList = ref([]);
//
const selectedRows = ref([])
const selectedRows = ref([]);
//
const showAddDialog = ref(false)
const addRowCount = ref(1)
const showAddDialog = ref(false);
const addRowCount = ref(1);
//
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00'
return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
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 calculateBudgetSubtotal = (row: any) => {
const times = parseFloat(row.times) || 0
const personCount = parseFloat(row.personCount) || 0
const days = parseFloat(row.days) || 0
const accommodationStandard = parseFloat(row.accommodationStandard) || 0
const roundTripFee = parseFloat(row.roundTripFee) || 0
const frequency = parseFloat(row.frequency) || 0;
const peopleNumber = parseFloat(row.peopleNumber) || 0;
const days = parseFloat(row.days) || 0;
const stayStandard = parseFloat(row.stayStandard) || 0;
const travelExpenses = parseFloat(row.travelExpenses) || 0;
// 宿 = × × 宿
row.accommodationFee = personCount * days * accommodationStandard
row.stayCosts = peopleNumber * days * stayStandard;
// = 50 × ×
row.subsidy = 50 * personCount * days
row.subsidyCosts = 50 * peopleNumber * days;
// = ( + 宿 + ) / 10000
row.budgetSubtotal = (roundTripFee + row.accommodationFee + row.subsidy) / 10000 //
}
// = ( + 宿 + )
row.subtotalCosts = travelExpenses + row.stayCosts + row.subsidyCosts;
};
//
const calculateReducedSubtotal = (row: any) => {
const times = parseFloat(row.reducedTimes) || 0
const personCount = parseFloat(row.reducedPersonCount) || 0
const days = parseFloat(row.reducedDays) || 0
const accommodationStandard = parseFloat(row.reducedAccommodationStandard) || 0
const roundTripFee = parseFloat(row.reducedRoundTripFee) || 0
const frequency = parseFloat(row.reduceFrequency) || 0;
const peopleNumber = parseFloat(row.reducePeopleNumber) || 0;
const days = parseFloat(row.reduceDayNumber) || 0;
const stayStandard = parseFloat(row.reduceStayStandard) || 0;
const travelExpenses = parseFloat(row.reduceTravelExpenses) || 0;
// 宿 = × × 宿
row.reducedAccommodationFee = personCount * days * accommodationStandard
row.reduceStayCosts = peopleNumber * days * stayStandard;
// = 50 × ×
row.reducedSubsidy = 50 * personCount * days
row.reduceSubsidyCosts = 50 * peopleNumber * days;
// = ( + 宿 + ) / 10000
row.reducedSubtotal = (roundTripFee + row.reducedAccommodationFee + row.reducedSubsidy) / 10000 //
}
// = ( + 宿 + )
row.reduceSubtotalCosts = travelExpenses + row.reduceStayCosts + row.reduceSubsidyCosts;
};
//
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
selectedRows.value = selection;
};
//
const handleAddRows = () => {
const currentMaxSerial = Math.max(...travelData.value.map(item => item.serialNumber))
const currentSortOrder =
travelCostList.value && travelCostList.value.length > 0
? Math.max(...travelCostList.value.map((item) => item.sortOrder))
: 0;
for (let i = 0; i < addRowCount.value; i++) {
travelData.value.push({
serialNumber: currentMaxSerial + i + 1,
destination: '',
purpose: '',
times: 0,
personCount: 0,
travelCostList.value.push({
sortOrder: currentSortOrder + i + 1,
tripLocation: '',
reason: '',
frequency: 0,
peopleNumber: 0,
days: 0,
accommodationStandard: 0,
roundTripFee: 0,
accommodationFee: 0,
subsidy: 0,
budgetSubtotal: 0,
reducedSerialNumber: currentMaxSerial + i + 1,
reducedDestination: '',
reducedPurpose: '',
reducedTimes: 0,
reducedPersonCount: 0,
reducedDays: 0,
reducedAccommodationStandard: 0,
reducedRoundTripFee: 0,
reducedAccommodationFee: 0,
reducedSubsidy: 0,
reducedSubtotal: 0,
costReductionPlan: ''
})
stayStandard: 0,
travelExpenses: 0,
stayCosts: 0,
subsidyCosts: 0,
subtotalCosts: 0,
reduceSortOrder: currentSortOrder + i + 1,
reduceTripLocation: '',
reduceReason: '',
reduceFrequency: 0,
reducePeopleNumber: 0,
reduceDayNumber: 0,
reduceStayStandard: 0,
reduceTravelExpenses: 0,
reduceStayCosts: 0,
reduceSubsidyCosts: 0,
reduceSubtotalCosts: 0,
reduceProposal: ''
});
}
showAddDialog.value = false
addRowCount.value = 1
}
showAddDialog.value = false;
addRowCount.value = 1;
};
//
const handleDelete = (index: number) => {
travelData.value.splice(index, 1)
const handleDelete = (index: number, row: budgetTravelCostVO) => {
travelCostList.value.splice(index, 1);
//
travelData.value.forEach((item, index) => {
item.serialNumber = index + 1
item.reducedSerialNumber = index + 1
})
}
travelCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
item.reduceSortOrder = index + 1;
});
if (row.travelCostId) {
toDeletedTravelCostIdList.value.push(row.travelCostId);
}
};
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行')
return
ElMessage.warning('请选择要删除的行');
return;
}
travelData.value = travelData.value.filter(item => !selectedRows.value.includes(item))
selectedRows.value.forEach((row) => {
if (row.travelCostId) {
toDeletedTravelCostIdList.value.push(row.travelCostId);
}
});
travelCostList.value = travelCostList.value.filter((item) => !selectedRows.value.includes(item));
//
travelData.value.forEach((item, index) => {
item.serialNumber = index + 1
item.reducedSerialNumber = index + 1
})
selectedRows.value = []
}
travelCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
item.reduceSortOrder = index + 1;
});
selectedRows.value = [];
};
//
const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
const { columns, data } = param;
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '合计'
return
if (index === 7) {
sums[index] = '合计';
return;
}
if (column.property === 'roundTripFee' || column.property === 'accommodationFee' ||
column.property === 'subsidy' || column.property === 'budgetSubtotal' ||
column.property === 'reducedRoundTripFee' || column.property === 'reducedAccommodationFee' ||
column.property === 'reducedSubsidy' || column.property === 'reducedSubtotal') {
const values = data.map((item: any) => Number(item[column.property]))
if (
column.property === 'travelExpenses' ||
column.property === 'stayCosts' ||
column.property === 'subsidyCosts' ||
column.property === 'subtotalCosts' ||
column.property === 'reduceTravelExpenses' ||
column.property === 'reduceStayCosts' ||
column.property === 'reduceSubsidyCosts' ||
column.property === 'reduceSubtotalCosts'
) {
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)
const value = Number(curr);
if (!Number.isNaN(value)) {
return prev + curr
return prev + curr;
} else {
return prev
return prev;
}
}, 0)
sums[index] = formatNumber(sums[index])
}, 0);
if (column.property === 'subtotalCosts' || column.property === 'reduceSubtotalCosts') {
sums[index] = format2TenThousandNumber(sums[index]);
} else {
sums[index] = formatNumber(sums[index]);
}
} else {
sums[index] = ''
sums[index] = '';
}
} else {
sums[index] = ''
sums[index] = '';
}
})
return sums
}
});
return sums;
};
//
const getTotalAmount = () => {
const totalBudgetAmount = travelData.value.reduce((sum, item) => sum + item.budgetSubtotal, 0)
const totalReducedAmount = travelData.value.reduce((sum, item) => sum + item.reducedSubtotal, 0)
const totalBudgetAmount = travelCostList.value.reduce((sum, item) => sum + item.subtotalCosts, 0);
const totalReducedAmount = travelCostList.value.reduce((sum, item) => sum + item.reduceSubtotalCosts, 0);
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
}
}
};
};
//
defineExpose({
getTotalAmount,
travelData
})
travelCostList,
toDeletedTravelCostIdList
});
</script>
<style scoped>
@ -363,6 +373,12 @@ defineExpose({
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
text-align: center;
font-size: 16px;

@ -2,37 +2,37 @@
<div class="budget-table-container">
<h2 class="title-center">项目经费预算表</h2>
<el-form :model="formData" label-width="120px" class="mb-4">
<el-form :model="rdBudgetInfoForm" label-width="120px" class="mb-4">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目名称">
<el-input v-model="formData.projectName" readonly />
<el-input v-model="rdBudgetInfoForm.projectName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目号">
<el-input v-model="formData.projectCode" readonly />
<el-input v-model="rdBudgetInfoForm.projectCode" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目预算期间">
<el-input v-model="formData.durationOperation" />
<el-input v-model="rdBudgetInfoForm.duringOperation" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="金额单位">
<el-input v-model="formData.unit" readonly value="万元" />
<el-input v-model="rdBudgetInfoForm.unitName" disabled value="万元" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-table :data="budgetData" style="width: 100%" border>
<el-table-column prop="index" label="序号" width="80" />
<el-table-column prop="subject" label="预算科目名称" />
<el-table-column prop="amount" label="项目经费" width="260" align="right">
<el-table :data="budgetDetailData" style="width: 100%" border show-summary :summary-method="getSummaries">
<el-table-column prop="sortOrder" label="序号" width="80" />
<el-table-column prop="budgetItem" label="预算科目名称" />
<el-table-column prop="budgetCost" label="项目经费" width="260" align="right">
<template #default="scope">
{{ scope.row.amount.toFixed(2) }}
<span>{{ formatNumber(scope.row.budgetCost) }}</span>
</template>
</el-table-column>
</el-table>
@ -41,49 +41,30 @@
<el-row>
<el-col :span="12">
<el-form-item label="编制(项目经理)">
<el-select
v-model="footerForm.creator"
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 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.reviewer"
placeholder="请选择评审组长"
filterable
clearable
:filter-method="(query) => reviewerSearchQuery = query"
:remote="false"
>
<el-option
v-for="user in filteredReviewerList"
: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 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>
@ -94,28 +75,60 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed, watch } from 'vue';
import { UserVO } from '@/api/system/user/types';
import { getUserList } from '@/api/system/user';
//
const formData = reactive({
projectName: '',
projectCode: '',
budgetPeriod: null,
unit: '万元'
//
const rdBudgetInfoForm = reactive({
budgetId: undefined,
projectId: undefined,
approvedFlag: undefined,
budgetVersion: undefined,
projectCategory: undefined,
projectCode: undefined,
projectName: undefined,
managerId: undefined,
managerName: undefined,
productManagerId: undefined,
productManagerName: undefined,
approveUserId: undefined,
approveUserName: undefined,
contractAmount: undefined,
netContractAmount: undefined,
budgetCost: undefined,
budgetRate: undefined,
reduceBudgetCost: undefined,
reduceBudgetRate: undefined,
duringOperation: undefined,
unitId: undefined,
unitName: '万元',
exportFlag: undefined,
budgetStatus: undefined,
flowStatus: undefined,
contractId: undefined,
remark: undefined
});
const footerForm = reactive({
creator: '',
reviewer: ''
managerId: '',
approveUserId: ''
});
//
const userList = ref([
{ id: '1', name: '张三', department: '研发部' },
{ id: '2', name: '李四', department: '市场部' },
{ id: '3', name: '王五', department: '财务部' },
{ id: '4', name: '赵六', department: '人力资源部' },
{ id: '5', name: '钱七', department: '研发部' }
]);
// const userList = ref([
// { id: '1', name: '', department: '' },
// { id: '2', name: '', department: '' },
// { id: '3', name: '', department: '' },
// { id: '4', name: '', department: '' },
// { id: '5', name: '', department: '' }
// ]);
const userList = ref<UserVO[]>([]);
/** 查询用户列表 */
const getUsers = async () => {
const res = await getUserList({});
userList.value = res.data;
};
//
const creatorSearchQuery = ref('');
@ -123,83 +136,98 @@ const reviewerSearchQuery = ref('');
//
const filteredCreatorList = computed(() => {
return userList.value.filter(user =>
user.name.includes(creatorSearchQuery.value)
);
return userList.value.filter((user) => user.name.includes(creatorSearchQuery.value));
});
const filteredReviewerList = computed(() => {
return userList.value.filter(user =>
user.name.includes(reviewerSearchQuery.value)
);
return userList.value.filter((user) => user.name.includes(reviewerSearchQuery.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 formatUserDisplay = (user) => {
return `${user.department} - ${user.name}`;
};
//
const budgetData = ref([
{ index: 1, subject: '设备费', amount: 0 },
{ index: 2, subject: '材料费', amount: 0 },
{ index: 3, subject: '差旅费', amount: 0 },
{ index: 4, subject: '会议费', amount: 0 },
{ index: 5, subject: '国际合作与交流费', amount: 0 },
{ index: 6, subject: '咨询开发费', amount: 0 },
{ index: 7, subject: '人工费', amount: 0 },
{ index: 8, subject: '劳务费', amount: 0 },
{ index: 9, subject: '资料/文献费', amount: 0 },
{ index: 10, subject: '测试化验费', amount: 0 },
{ index: 11, subject: '其他费用', amount: 0 },
{ index: '', subject: '合计', amount: 0 }
const budgetDetailData = ref([
{ 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 }
]);
//
const totalAmount = computed(() => {
return budgetData.value.slice(0, -1).reduce((sum, item) => sum + item.amount, 0);
return budgetDetailData.value.slice(0, -1).reduce((sum, item) => sum + Number(item.budgetCost), 0);
});
//
const updateCostItem = (subject, amount) => {
const index = budgetData.value.findIndex(item => item.subject === subject)
const index = budgetDetailData.value.findIndex((item) => item.budgetItem === subject);
if (index !== -1) {
// 使
const updatedItem = { ...budgetData.value[index], amount: amount }
budgetData.value.splice(index, 1, updatedItem)
const updatedItem = { ...budgetDetailData.value[index], amount: amount };
budgetDetailData.value.splice(index, 1, updatedItem);
//
const totalIndex = budgetData.value.length - 1
const totalRow = { ...budgetData.value[totalIndex], amount: totalAmount.value }
budgetData.value.splice(totalIndex, 1, totalRow)
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.equipmentCost || 0);
//
updateCostItem('材料费', costData.materialCost || 0)
updateCostItem('材料费', costData.materialCost || 0);
//
updateCostItem('差旅费', costData.travelCost || 0)
updateCostItem('差旅费', costData.travelCost || 0);
//
updateCostItem('会议费', costData.meetingCost || 0)
updateCostItem('会议费', costData.meetingCost || 0);
//
updateCostItem('国际合作与交流费', costData.internationalExchangeCost || 0)
updateCostItem('国际合作与交流费', costData.internationalExchangeCost || 0);
//
updateCostItem('咨询开发费', costData.consultingDevelopmentCost || 0)
updateCostItem('咨询开发费', costData.consultingDevelopmentCost || 0);
//
updateCostItem('人工费', costData.laborCost || 0)
updateCostItem('人工费', costData.laborCost || 0);
//
updateCostItem('劳务费', costData.serviceCost || 0)
updateCostItem('劳务费', costData.serviceCost || 0);
// /
updateCostItem('资料/文献费', costData.literatureCost || 0)
updateCostItem('资料/文献费', costData.literatureCost || 0);
//
updateCostItem('测试化验费', costData.testingCost || 0)
updateCostItem('测试化验费', costData.testingCost || 0);
//
updateCostItem('其他费用', costData.otherCost || 0)
}
updateCostItem('其他费用', costData.otherCost || 0);
};
//
const updateRdBudgetDetailData = (type: string, budgetCost: number) => {
const item = budgetDetailData.value.find((item) => item.budgetItem === type);
if (item) {
item.budgetCost = budgetCost;
}
};
//
const props = defineProps({
@ -207,32 +235,70 @@ const props = defineProps({
type: Object,
default: () => ({})
}
})
});
//
watch(() => props.projectInfo, (newProjectInfo) => {
if (newProjectInfo) {
formData.projectName = newProjectInfo.projectName || ''
formData.projectCode = newProjectInfo.projectCode || ''
formData.budgetPeriod = newProjectInfo.budgetPeriod || null
formData.unit = newProjectInfo.amountUnit || '万元'
}
}, { deep: true, immediate: true })
watch(
() => props.projectInfo,
(newProjectInfo) => {
if (newProjectInfo) {
rdBudgetInfoForm.projectId = newProjectInfo.projectId || '';
rdBudgetInfoForm.projectName = newProjectInfo.projectName || '';
rdBudgetInfoForm.projectCode = newProjectInfo.projectCode || '';
rdBudgetInfoForm.unitName = newProjectInfo.amountUnit || '万元';
rdBudgetInfoForm.projectCategory = newProjectInfo.projectCategory || '';
}
},
{ deep: true, immediate: true }
);
//
const getBudgetData = () => {
return {
formData: { ...formData },
rdBudgetInfoForm: { ...rdBudgetInfoForm },
footerForm: { ...footerForm },
budgetData: budgetData.value.map(item => ({ ...item }))
budgetData: budgetDetailData.value.map((item) => ({ ...item }))
};
};
//
const getSummaries = ({ columns, data }: any) => {
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 1) {
sums[index] = '合计(万元)';
return;
}
if (column.property === 'budgetCost') {
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] = formatNumber(sums[index]);
} else {
sums[index] = '';
}
});
return sums;
};
onMounted(() => {
getUsers();
});
//
defineExpose({
updateCostItem,
updateCostData,
getBudgetData
updateRdBudgetDetailData,
budgetDetailData,
rdBudgetInfoForm,
footerForm
});
</script>

@ -5,10 +5,20 @@
<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-button>
<el-button type="danger" size="small" @click="deleteSelectedRows" :disabled="selectedRows.length === 0">删除</el-button>
<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>
@ -17,57 +27,46 @@
:data="equipmentData"
border
style="width: 100%"
row-key="id"
show-summary
:summary-method="getSummaries"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="equipmentName" label="设备名称" width="200">
<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.id === scope.row.id)"
:data-row-index="equipmentData.findIndex((item) => item.sortOrder === scope.row.sortOrder)"
/>
</template>
</el-table-column>
<el-table-column prop="specification" label="设备型号" width="180">
<el-table-column prop="equipmentSpec" label="设备型号" min-width="280">
<template #default="scope">
<el-input v-model="scope.row.specification" placeholder="请输入设备型号" />
<el-input v-model="scope.row.equipmentSpec" placeholder="请输入设备型号" />
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价(元/台件)" width="120">
<el-table-column prop="unitPrice" label="单价(元/台件)" width="180">
<template #default="scope">
<el-input
v-model.number="scope.row.unitPrice"
type="number"
placeholder="单价"
@change="calculateAmount"
/>
<el-input-number v-model.number="scope.row.unitPrice" placeholder="单价" :min="0" :precision="2" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="quantity" label="数量(台件)" width="100">
<el-table-column prop="amount" label="数量(台件)" width="180">
<template #default="scope">
<el-input
v-model.number="scope.row.quantity"
type="number"
placeholder="数量"
@change="calculateAmount"
/>
<el-input-number v-model.number="scope.row.amount" placeholder="数量" :min="0" :precision="2" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="amount" label="金额(万元)" width="120">
<el-table-column prop="price" label="金额(万元)" width="120">
<template #default="scope">
<el-input v-model.number="scope.row.amount" type="number" placeholder="金额" disabled />
{{ 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="deleteRow(scope.row.id)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -76,242 +75,169 @@
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
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
}>()
projectId: string;
}>();
//
const emit = defineEmits<{
update: [data: { equipmentCost: number }]
}>()
update: [data: { equipmentCost: number }];
}>();
//
const equipmentData = reactive<any[]>([])
//
const totalAmount = ref(0)
const equipmentData = ref<rdBudgetEquipmentCostVO[]>([]);
const toDeletedEquipmentCostIdList = ref([]);
//
const selectedRows = ref<any[]>([])
const selectedRows = ref<any[]>([]);
//
const addRowCount = ref(1)
const addRowCount = ref(1);
// IDID
let idCounter = 1
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00';
return parseFloat(value).toFixed(2);
};
// ID
watch(() => props.projectId, (newProjectId) => {
if (newProjectId) {
loadEquipmentData(newProjectId)
}
}, { immediate: true })
//
const loadEquipmentData = async (projectId: string) => {
try {
// API使
console.log('加载设备费数据:', projectId)
//
equipmentData.splice(0, equipmentData.length)
//
const mockData = [
{
id: idCounter++,
equipmentName: '高精度测试仪',
specification: '型号A-100',
unit: '台',
quantity: 2,
unitPrice: 50000,
amount: 100000,
usage: '产品性能测试',
remarks: ''
},
{
id: idCounter++,
equipmentName: '研发服务器',
specification: '高配版',
unit: '台',
quantity: 1,
unitPrice: 80000,
amount: 80000,
usage: '数据处理',
remarks: ''
},
{
id: idCounter++,
equipmentName: '3D打印机',
specification: '工业级',
unit: '台',
quantity: 1,
unitPrice: 20000,
amount: 20000,
usage: '原型制作',
remarks: ''
}
]
//
mockData.forEach(item => equipmentData.push(item))
//
calculateTotal()
} catch (error) {
ElMessage.error('加载设备费数据失败')
console.error('加载设备费数据失败:', error)
}
}
// ,
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
return (parseFloat(value) / 10000).toFixed(2);
};
//
const handleAddRow = () => {
const startIndex = equipmentData.length
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.push({
id: idCounter++,
equipmentData.value.push({
sortOrder: currentSortOrder + i + 1,
equipmentName: '',
specification: '',
unit: '',
quantity: 0,
unitPrice: 0,
amount: 0,
usage: '',
remarks: ''
})
equipmentSpec: '',
amount: undefined,
unitPrice: undefined,
price: 0
});
}
//
setTimeout(() => {
const tableBody = document.querySelector('.el-table__body-wrapper')
const tableBody = document.querySelector('.el-table__body-wrapper');
if (tableBody) {
tableBody.scrollTop = tableBody.scrollHeight
tableBody.scrollTop = tableBody.scrollHeight;
}
//
setTimeout(() => {
const inputElement = document.querySelector(`[data-row-index="${startIndex}"]`)
const inputElement = document.querySelector(`[data-row-index="${currentSortOrder}"]`);
if (inputElement) {
(inputElement as HTMLInputElement).focus()
(inputElement as HTMLInputElement).focus();
}
}, 100)
}, 0)
}
//
const deleteRow = (id: number) => {
const index = equipmentData.findIndex(item => item.id === id)
if (index > -1) {
equipmentData.splice(index, 1)
calculateTotal()
}
}
}, 100);
}, 0);
};
//
const handleSelectionChange = (rows: any[]) => {
selectedRows.value = rows
}
selectedRows.value = rows;
};
//
const deleteSelectedRows = () => {
//
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
ElMessage.warning('请选择要删除的行');
return;
}
// ID
const selectedIds = selectedRows.value.map(row => row.id)
//
for (let i = equipmentData.length - 1; i >= 0; i--) {
if (selectedIds.includes(equipmentData[i].id)) {
equipmentData.splice(i, 1)
selectedRows.value.forEach((selectedRow) => {
if (selectedRow.materialCostId) {
toDeletedEquipmentCostIdList.value.push(selectedRow.materialCostId);
}
}
//
selectedRows.value = []
});
equipmentData.value = equipmentData.value.filter((item) => !selectedRows.value.includes(item));
//
equipmentData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = [];
//
calculateTotal()
ElMessage.success(`成功删除 ${selectedIds.length} 行数据`)
}
};
//
const calculateAmount = () => {
equipmentData.forEach(item => {
item.amount = Math.round((item.quantity || 0) * (item.unitPrice || 0)/100)/100
})
calculateTotal()
}
equipmentData.value.forEach((item) => {
item.price = Math.round((item.amount || 0) * (item.unitPrice || 0));
});
};
//
const calculateTotal = () => {
totalAmount.value = equipmentData.reduce((sum, item) => sum + (item.amount || 0), 0)
emit('update',{equipmentCost:totalAmount.value})
}
//
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: string[] = []
const sums: any[] = [];
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '合计'
return
if (index === 5) {
sums[index] = '合计';
return;
}
if (column.property === 'amount') {
const values = data.map((item: any) => Number(item[column.property]) || 0)
const sum = values.reduce((prev: number, curr: number) => {
const value = Number(curr)
if (!isNaN(value)) {
return prev + curr
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
return prev;
}
}, 0)
sums[index] = sum.toString()
}, 0);
sums[index] = format2TenThousandNumber(sums[index]);
} else {
sums[index] = ''
sums[index] = '';
}
})
return sums
}
});
return sums;
};
//
const refreshData = () => {
if (props.projectId) {
loadEquipmentData(props.projectId)
}
}
};
//
const resetData = () => {
equipmentData.splice(0, equipmentData.length)
totalAmount.value = 0
idCounter = 1
}
//
const getFormData = () => {
return {
equipmentData: [...equipmentData],
totalAmount: totalAmount.value
}
}
equipmentData.value.splice(0, equipmentData.value.length);
};
//
defineExpose({
refreshData,
resetData,
getFormData
})
equipmentData,
toDeletedEquipmentCostIdList,
getTotalAmount
});
</script>
<style scoped>

File diff suppressed because it is too large Load Diff

@ -5,102 +5,62 @@
<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"
/>
<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-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button @click="deleteSelectedMaterials" size="small" :disabled="selectedMaterialsRows.length === 0">
<el-icon><Delete /></el-icon>
<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 v-loading="loading" :data="materialsList" style="width: 100%" border @selection-change="handleMaterialsSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="project" label="项目" min-width="300">
<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.project"
placeholder="请输入资料项目名称"
size="small"
:data-row-index="scope.$index"
/>
<el-input v-model="scope.row.itemDesc" placeholder="请输入资料项目名称" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="amount" label="金额(万元)" width="120">
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number
v-model="scope.row.amount"
:min="0"
:step="0.01"
:precision="2"
size="small"
@change="updateTotal"
/>
<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">
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button
type="danger"
size="small"
@click="deleteMaterialsRow(scope.row)"
icon="Delete"
/>
<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">
合计: {{ materialsTotal.toFixed(2) }} 万元
</div>
<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="project" label="项目" min-width="300">
<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.project"
placeholder="请输入检索项目名称"
size="small"
disabled
/>
<el-input v-model="scope.row.itemDesc" disabled />
</template>
</el-table-column>
<el-table-column prop="amount" label="金额(万元)" width="120">
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number
v-model="scope.row.amount"
:min="0"
:step="0.01"
:precision="2"
size="small"
@change="updateTotal"
/>
<el-input-number v-model="scope.row.price" :min="0" :step="1" :precision="2" size="small" />
</template>
</el-table-column>
</el-table>
@ -111,76 +71,53 @@
<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"
/>
<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-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button @click="deleteSelectedSoftware" size="small" :disabled="selectedSoftwareRows.length === 0">
<el-icon><Delete /></el-icon>
<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 v-loading="loading" :data="softwareList" style="width: 100%" border @selection-change="handleSoftwareSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="project" label="项目" min-width="300">
<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.project"
placeholder="请输入软件名称"
size="small"
:data-row-index="scope.$index"
/>
<el-input v-model="scope.row.itemDesc" placeholder="请输入软件名称" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="amount" label="金额(万元)" width="120">
<el-table-column prop="price" label="金额(万元)" width="160">
<template #default="scope">
<el-input-number
v-model="scope.row.amount"
:min="0"
:step="0.01"
:precision="2"
size="small"
@change="updateTotal"
/>
<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">
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button
type="danger"
size="small"
@click="deleteSoftwareRow(scope.row)"
icon="Delete"
/>
<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">
合计: {{ softwareTotal.toFixed(2) }} 万元
</div>
<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">
资料文献费总计: {{ totalAmount.toFixed(2) }} 万元
资料文献费总计: {{ formatNumber(totalAmount) }}
万元
</div>
<!-- 对话框组件 -->
@ -189,260 +126,210 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetLiteratureCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetLiteratureCost/types';
//
interface MaterialsItem {
id?: string
project: string
amount: number
}
//
interface LiteratureRetrievalItem {
project: string
amount: number
}
//
interface SoftwareItem {
id?: string
project: string
amount: number
}
const LITERATURE_TYPE = {
MATERIAL: '1', //
DOCUMENT: '2', //
SOFTWARE: '3' //
};
// Props
const props = defineProps<{
projectId?: string
}>()
// Emits
const emit = defineEmits<{
update: [data: {
totalAmount: number
}]
}>()
projectId?: string;
}>();
//
const loading = ref(false)
const loading = ref(false);
//
const materialsList = ref<MaterialsItem[]>([])
const selectedMaterialsRows = ref<MaterialsItem[]>([])
const materialsAddDialogVisible = ref(false)
const materialsAddRowCount = ref(1)
const materialsList = ref<rdBudgetLiteratureCostVO[]>([]);
const selectedMaterialsRows = ref<rdBudgetLiteratureCostVO[]>([]);
const materialsAddRowCount = ref(1);
//
const literatureRetrieval = ref<LiteratureRetrievalItem>({
project: '文献检索费',
amount: 0
})
const literatureRetrieval = ref<rdBudgetLiteratureCostVO>({
literatureType: LITERATURE_TYPE.DOCUMENT,
itemDesc: '文献检索费',
price: 0
});
//
const softwareList = ref<SoftwareItem[]>([])
const selectedSoftwareRows = ref<SoftwareItem[]>([])
const softwareAddDialogVisible = ref(false)
const softwareAddRowCount = ref(1)
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 + (item.amount || 0), 0)
})
return materialsList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const softwareTotal = computed(() => {
return softwareList.value.reduce((sum, item) => sum + (item.amount || 0), 0)
})
return softwareList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const totalAmount = computed(() => {
return materialsTotal.value + literatureRetrieval.value.amount + softwareTotal.value
})
return (materialsTotal.value || 0) + (literatureRetrieval.value.price || 0) + (softwareTotal.value || 0);
});
//
const updateTotal = () => {
emit('update', {
literatureCost: totalAmount.value
})
}
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 deleteSelectedMaterials = () => {
if (selectedMaterialsRows.value.length === 0) {
ElMessage.warning('请先选择要删除的行')
return
}
selectedMaterialsRows.value.forEach(row => {
const index = materialsList.value.findIndex(item => item === row)
if (index !== -1) {
materialsList.value.splice(index, 1)
}
})
selectedMaterialsRows.value = []
updateTotal()
ElMessage.success('删除成功')
}
const confirmMaterialsAdd = () => {
const startIndex = materialsList.value.length
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: MaterialsItem = {
project: '',
amount: 0
}
materialsList.value.push(newItem)
const newItem: rdBudgetLiteratureCostVO = {
sortOrder: currentSortOrder + i + 1,
literatureType: LITERATURE_TYPE.MATERIAL,
itemDesc: '',
price: 0
};
materialsList.value.push(newItem);
}
ElMessage.success(`成功添加 ${materialsAddRowCount.value}`)
//
setTimeout(() => {
const firstNewInput = document.querySelector(`[data-row-index="${startIndex}"] .el-input__inner`)
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"] .el-input__inner`);
if (firstNewInput) {
(firstNewInput as HTMLInputElement).focus()
(firstNewInput as HTMLInputElement).focus();
}
}, 100)
}
}, 100);
};
const deleteMaterialsRow = (row: MaterialsItem) => {
const index = materialsList.value.findIndex(item => item === row)
if (index !== -1) {
materialsList.value.splice(index, 1)
updateTotal()
ElMessage.success('删除成功')
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 handleMaterialsSelectionChange = (selection: MaterialsItem[]) => {
selectedMaterialsRows.value = selection
}
//
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 startIndex = softwareList.value.length
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: SoftwareItem = {
project: '',
amount: 0
}
softwareList.value.push(newItem)
const newItem: rdBudgetLiteratureCostVO = {
sortOrder: currentSortOrder + i + 1,
literatureType: LITERATURE_TYPE.SOFTWARE,
itemDesc: '',
price: 0
};
softwareList.value.push(newItem);
}
ElMessage.success(`成功添加 ${softwareAddRowCount.value}`)
//
setTimeout(() => {
const firstNewInput = document.querySelector(`[data-row-index="${startIndex}"] .el-input__inner`)
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"] .el-input__inner`);
if (firstNewInput) {
(firstNewInput as HTMLInputElement).focus()
(firstNewInput as HTMLInputElement).focus();
}
}, 100)
}
}, 100);
};
const deleteSoftwareRow = (row: SoftwareItem) => {
const index = softwareList.value.findIndex(item => item === row)
if (index !== -1) {
softwareList.value.splice(index, 1)
updateTotal()
ElMessage.success('删除成功')
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 deleteSelectedSoftware = () => {
//
const handleSoftwareBatchDelete = () => {
if (selectedSoftwareRows.value.length === 0) {
ElMessage.warning('请先选择要删除的行')
return
ElMessage.warning('请选择要删除的行');
return;
}
selectedSoftwareRows.value.forEach(row => {
const index = softwareList.value.findIndex(item => item === row)
if (index !== -1) {
softwareList.value.splice(index, 1)
selectedSoftwareRows.value.forEach((selectedRow) => {
if (selectedRow.literatureCostId) {
toDeletedLiteratureCostIdList.value.push(selectedRow.literatureCostId);
}
})
});
selectedSoftwareRows.value = []
updateTotal()
ElMessage.success('删除成功')
}
const handleSoftwareSelectionChange = (selection: SoftwareItem[]) => {
selectedSoftwareRows.value = selection
}
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) {
loadAllData(newProjectId)
}
}, { immediate: true })
//
const loadAllData = async (projectId: string) => {
loading.value = true
try {
// API
await new Promise(resolve => setTimeout(resolve, 500))
//
materialsList.value = [
{
id: '1',
project: '技术资料购买',
amount: 1.2
},
{
id: '2',
project: '资料翻译费用',
amount: 0.8
}
]
//
literatureRetrieval.value = {
project: '文献检索费',
amount: 0.5
watch(
() => props.projectId,
(newProjectId) => {
if (newProjectId) {
}
//
softwareList.value = [
{
id: '1',
project: '开发工具软件',
amount: 3.0
},
{
id: '2',
project: '专业分析软件',
amount: 2.5
}
]
updateTotal()
} catch (error) {
ElMessage.error('加载数据失败')
console.error('加载数据失败:', error)
} finally {
loading.value = false
}
}
},
{ immediate: true }
);
//
defineExpose({
materialsList,
literatureRetrieval,
softwareList,
materialsTotal,
softwareTotal,
totalAmount,
updateTotal,
loadAllData
})
literatureRetrieval,
toDeletedLiteratureCostIdList,
getLiteratureCost
});
</script>
<style scoped>
@ -454,10 +341,6 @@ defineExpose({
margin-bottom: 16px;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-input-number) {
width: 100%;
}

@ -5,94 +5,71 @@
<div class="card-header">
<span>材料费明细</span>
<div class="header-buttons">
<el-input-number
v-model="addRowCount"
:min="1"
:max="100"
:step="1"
size="small"
style="width: 100px; margin-right: 10px;"
/>
<el-button type="primary" size="small" @click="addSpecifiedRows"></el-button>
<el-button type="danger" size="small" @click="deleteSelectedRows" :disabled="!selectedRows.length">删除</el-button>
<el-input-number v-model="addRowCount" :min="1" :max="100" :step="1" size="small" style="width: 100px; margin-right: 10px" />
<el-button type="primary" size="small" @click="addSpecifiedRows">
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button type="danger" size="small" @click="handleBatchDelete" :disabled="!selectedRows.length">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
</template>
<el-table
:data="allMaterialData"
border
style="width: 100%"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="materialName" label="材料名称" width="200">
<el-table :data="allMaterialData" border style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" :selectable="checkSelectable" />
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="materialName" label="材料名称" min-width="200">
<template #default="scope">
<template v-if="!scope.row.isOtherMaterial">
<el-input v-model="scope.row.materialName" placeholder="请输入材料名称" />
</template>
<template v-else>
其他材料费
</template>
<el-input v-model="scope.row.materialName" placeholder="请输入材料名称" :disabled="scope.row.materialName === '其他材料费'" />
</template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="160">
<el-table-column prop="unitId" label="单位" width="160">
<template #default="scope">
<el-select
v-model="scope.row.unit"
placeholder="单位"
filterable
allow-create
default-first-option
style="width: 100%"
clearable
>
<el-option
v-for="option in unitOptions"
:key="option"
:label="option"
:value="option"
/>
<el-select v-model="scope.row.unitId" placeholder="单位" filterable allow-create default-first-option style="width: 100%" clearable>
<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">
<el-table-column prop="unitPrice" label="单价(元/单位数量)" width="180">
<template #default="scope">
<el-input
v-model.number="scope.row.unitPrice"
type="number"
placeholder="单价"
@change="calculateAmount"
/>
<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="quantity" label="购置数量" width="100">
<el-table-column prop="amount" label="购置数量" width="180">
<template #default="scope">
<el-input
v-model.number="scope.row.quantity"
type="number"
<el-input-number
v-model.number="scope.row.amount"
placeholder="购置数量"
:min="0"
:precision="2"
size="small"
@change="calculateAmount"
/>
</template>
</el-table-column>
<el-table-column prop="amount" label="金额(万元)" width="120">
<el-table-column prop="price" label="金额(万元)" width="120">
<template #default="scope">
<el-input v-model.number="scope.row.amount" type="number" placeholder="金额" disabled />
{{ format2TenThousandNumber(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button
v-if="!scope.row.isOtherMaterial"
v-if="scope.row.materialType === MATERIAL_TYPE.MAIN"
type="danger"
size="small"
@click="deleteRow(scope.row.id)"
@click="handleDelete(scope.$index, scope.row)"
icon="Delete"
>
删除
</el-button>
@ -132,313 +109,247 @@
</table>
</div>
-->
<div class="mt-4 bg-gray-50 p-3 rounded">
<div class="mt-4 bg-gray-50 p-3 rounded">
<div class="flex justify-end gap-8">
<span>主要材料费小计: {{ mainMaterialTotalAmount }} </span>
<span class="font-semibold">合计: {{ totalAmount }} 万元</span>
<span>主要材料费小计: {{ format2TenThousandNumber(mainMaterialTotalAmount) }} </span>
<span class="font-semibold">合计: {{ formatNumber(totalAmount) }} 万元</span>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive, watch, computed, nextTick } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { rdBudgetMaterialCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetMaterialCost/types';
import { getBaseUnitInfoList } from '@/api/oa/base/unitInfo';
import { UnitInfoVO } from '@/api/oa/base/unitInfo/types';
// IDprops
const props = defineProps<{
projectId: string
}>()
projectId: string;
}>();
//
const emit = defineEmits<{
update: [data: { materialCost: number }]
}>()
const MATERIAL_TYPE = {
MAIN: '1', //
OTHER: '2' //
};
//
const materialData = reactive<any[]>([])
//
const unitOptions = ref([
'kg', 'g', 't', 'm', 'cm', 'mm', 'm²', 'm³',
'个', '件', '套', '批', '箱', '袋', '瓶',
'小时', '天', '月', '年', '次'
]);
const materialData = ref<rdBudgetMaterialCostVO[]>([]);
const toDeletedMaterialCostIdList = ref([]);
//
const otherMaterial = reactive({
id: -1,
isOtherMaterial: true,
sortOrder: 1,
materialName: '其他材料费',
unit: '',
quantity: 0,
unitPrice: 0,
amount: 0
})
materialType: MATERIAL_TYPE.OTHER,
unitId: undefined,
unitPrice: undefined,
amount: undefined,
price: 0
});
// +
const allMaterialData = computed(() => {
return [...materialData, otherMaterial]
})
return [...materialData.value, otherMaterial];
});
const unitOptions = ref<UnitInfoVO[]>([]);
//
// const unitOptions = ref(['kg', 'g', 't', 'm', 'cm', 'mm', 'm²', 'm³', '', '', '', '', '', '', '', '', '', '', '', '']);
//
const totalAmount = ref(0)
const totalQuantity = ref(0)
const totalAmount = ref(0);
const totalQuantity = ref(0);
//
const mainMaterialTotalAmount = computed(() => {
return materialData.reduce((sum, item) => sum + (item.amount || 0), 0)
})
const mainMaterialTotalQuantity = computed(() => {
return materialData.reduce((sum, item) => sum + (item.quantity || 0), 0)
})
return materialData.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
//
const selectedRows = ref<any[]>([])
const selectedRows = ref<any[]>([]);
//
const addRowCount = ref(1)
const addRowCount = ref(1);
// ID
let idCounter = 1
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00';
return parseFloat(value).toFixed(2);
};
// ID
watch(() => props.projectId, (newProjectId) => {
if (newProjectId) {
loadMaterialData(newProjectId)
}
}, { immediate: true })
// ,
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
return (parseFloat(value) / 10000).toFixed(2);
};
const checkSelectable = (row: rdBudgetMaterialCostVO, index: number) => {
return row.materialType === MATERIAL_TYPE.MAIN;
};
//
watch(() => [materialData, otherMaterial], () => {
calculateTotal()
}, { deep: true })
//
const loadMaterialData = async (projectId: string) => {
try {
// API使
console.log('加载材料费数据:', projectId)
//
materialData.splice(0, materialData.length)
//
const mockData = [
{
id: idCounter++,
materialName: '特种合金材料',
specification: '型号X-50',
unit: 'kg',
quantity: 100,
unitPrice: 500,
amount: 50000,
usage: '样品制作',
remarks: ''
},
{
id: idCounter++,
materialName: '电子元件包',
specification: '套装',
unit: '套',
quantity: 5,
unitPrice: 8000,
amount: 40000,
usage: '电路开发',
remarks: ''
},
{
id: idCounter++,
materialName: '包装材料',
specification: '标准型',
unit: '批',
quantity: 2,
unitPrice: 10000,
amount: 20000,
usage: '样品包装',
remarks: ''
},
{
id: idCounter++,
materialName: '实验试剂',
specification: '分析纯',
unit: '瓶',
quantity: 20,
unitPrice: 2000,
amount: 40000,
usage: '性能测试',
remarks: ''
}
]
//
mockData.forEach(item => materialData.push(item))
//
calculateTotal()
} catch (error) {
ElMessage.error('加载材料费数据失败')
console.error('加载材料费数据失败:', error)
}
}
watch(
() => [materialData, otherMaterial],
() => {
calculateTotal();
},
{ deep: true }
);
//
const addSpecifiedRows = async () => {
if (!addRowCount.value || addRowCount.value <= 0) {
ElMessage.warning('请输入有效的行数')
return
ElMessage.warning('请输入有效的行数');
return;
}
const currentSortOrder = materialData.value && materialData.value.length > 0 ? Math.max(...materialData.value.map((item) => item.sortOrder)) : 0;
//
for (let i = 0; i < addRowCount.value; i++) {
materialData.push({
id: idCounter++,
materialData.value.push({
sortOrder: currentSortOrder + i + 1,
materialName: '',
specification: '',
unit: '',
quantity: 0,
unitPrice: 0,
amount: 0,
usage: '',
remarks: ''
})
materialType: MATERIAL_TYPE.MAIN,
unitId: undefined,
unitPrice: undefined,
amount: undefined,
price: 0
});
}
otherMaterial.sortOrder = currentSortOrder + 1 + addRowCount.value;
// DOM
await nextTick()
scrollToBottom()
}
await nextTick();
scrollToBottom();
};
//
const scrollToBottom = () => {
const tableContainer = document.querySelector('.el-table__body-wrapper')
const tableContainer = document.querySelector('.el-table__body-wrapper');
if (tableContainer) {
tableContainer.scrollTop = tableContainer.scrollHeight
tableContainer.scrollTop = tableContainer.scrollHeight;
}
}
};
//
const handleSelectionChange = (val: any[]) => {
//
selectedRows.value = val.filter(row => !row.isOtherMaterial)
}
//
const deleteSelectedRows = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要删除的行')
return
}
ElMessageBox.confirm(
`确定要删除选中的 ${selectedRows.value.length} 行吗?`,
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
// ID
const selectedIds = selectedRows.value.map(row => row.id)
//
const newData = materialData.filter(item => !selectedIds.includes(item.id))
materialData.splice(0, materialData.length, ...newData)
//
selectedRows.value = []
//
calculateTotal()
ElMessage.success('删除成功')
}).catch(() => {
//
})
}
selectedRows.value = val.filter((row) => !row.isOtherMaterial);
};
//
const deleteRow = (id: number) => {
ElMessageBox.confirm(
'确定要删除这一行吗?',
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
const handleDelete = (index: number, row: rdBudgetMaterialCostVO) => {
materialData.value.splice(index, 1);
//
materialData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.materialCostId) {
toDeletedMaterialCostIdList.value.push(row.materialCostId);
}
otherMaterial.sortOrder = otherMaterial.sortOrder - 1;
calculateTotal();
};
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedRows.value.forEach((selectedRow) => {
if (selectedRow.materialCostId) {
toDeletedMaterialCostIdList.value.push(selectedRow.materialCostId);
}
).then(() => {
const index = materialData.findIndex(item => item.id === id)
if (index > -1) {
materialData.splice(index, 1)
calculateTotal()
ElMessage.success('删除成功')
}
}).catch(() => {
//
})
}
});
materialData.value = materialData.value.filter((item) => !selectedRows.value.includes(item));
//
materialData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
otherMaterial.sortOrder = otherMaterial.sortOrder - selectedRows.value.length;
calculateTotal();
selectedRows.value = [];
};
//
const calculateAmount = () => {
//
materialData.forEach(item => {
item.amount = (item.quantity || 0) * (item.unitPrice || 0)
})
materialData.value.forEach((item) => {
item.price = (item.amount || 0) * (item.unitPrice || 0);
});
//
otherMaterial.amount = (otherMaterial.quantity || 0) * (otherMaterial.unitPrice || 0)
otherMaterial.price = (otherMaterial.amount || 0) * (otherMaterial.unitPrice || 0);
//
calculateTotal()
}
calculateTotal();
};
//
const calculateTotal = () => {
totalAmount.value = mainMaterialTotalAmount.value + otherMaterial.amount
totalQuantity.value = mainMaterialTotalQuantity.value + (otherMaterial.quantity || 0)
const mainAmount = parseFloat(((Number(mainMaterialTotalAmount.value) || 0) / 10000).toFixed(2));
const otherAmount = parseFloat(((Number(otherMaterial.price) || 0) / 10000).toFixed(2));
totalAmount.value = (mainAmount + otherAmount).toFixed(2);
};
// update
emit('update', { materialCost: totalAmount.value })
}
//
const getTotalAmount = () => {
return (Number(totalAmount.value) || 0) * 10000;
};
//
const getUnitInfoList = async () => {
const res = await getBaseUnitInfoList({});
unitOptions.value = res.data;
};
onMounted(() => {
getUnitInfoList();
});
//
const refreshData = () => {
if (props.projectId) {
loadMaterialData(props.projectId)
}
}
};
//
const resetData = () => {
materialData.splice(0, materialData.length)
totalAmount.value = 0
totalQuantity.value = 0
idCounter = 1
materialData.value.splice(0, materialData.value.length);
totalAmount.value = 0;
totalQuantity.value = 0;
//
otherMaterial.unit = ''
otherMaterial.quantity = 0
otherMaterial.unitPrice = 0
otherMaterial.amount = 0
}
// otherMaterial.unit = '';
// otherMaterial.quantity = 0;
otherMaterial.unitPrice = 0;
otherMaterial.amount = 0;
};
//
const getFormData = () => {
return {
materialData: [...materialData],
materialData: [...materialData.value],
otherMaterial: { ...otherMaterial },
totalAmount: totalAmount.value,
totalQuantity: totalQuantity.value
}
}
};
};
//
defineExpose({
refreshData,
resetData,
getFormData
})
materialData,
otherMaterial,
allMaterialData,
toDeletedMaterialCostIdList,
getTotalAmount
});
</script>
<style scoped>
@ -489,18 +400,29 @@ defineExpose({
}
/* 列宽设置,与表格列对齐 */
.checkbox-col { width: 55px; }
.index-col { width: 60px; }
.checkbox-col {
width: 55px;
}
.index-col {
width: 60px;
}
.merged-cell {
width: 200px;
text-align: left;
font-weight: bold;
}
.empty-cell { width: 180px; }
.empty-cell {
width: 180px;
}
.total-value {
width: 120px;
font-weight: bold;
}
.total-bold {
color: #f56c6c;
font-size: 16px;

@ -4,72 +4,47 @@
<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="addRowCount"
:min="1"
:max="10"
:step="1"
size="small"
/>
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="addRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" @click="confirmAdd" size="small">
<el-icon><Plus /></el-icon>
<el-icon>
<Plus />
</el-icon>
添加
</el-button>
<el-button @click="deleteSelectedRows" size="small" :disabled="selectedRows.length === 0">
<el-icon><Delete /></el-icon>
<el-button type="danger" @click="handleBatchDelete" size="small" :disabled="selectedRows.length === 0">
<el-icon>
<Delete />
</el-icon>
删除
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="otherCostList"
style="width: 100%"
border
@selection-change="handleSelectionChange"
>
<el-table v-loading="loading" :data="otherCostList" style="width: 100%" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="index" label="序号" width="80" type="index" />
<el-table-column prop="project" label="项目" min-width="300">
<el-table-column prop="sortOrder" label="序号" width="80" type="index" />
<el-table-column prop="itemDesc" label="项目" min-width="200">
<template #default="scope">
<el-input
v-model="scope.row.project"
placeholder="请输入项目名称"
size="small"
:data-row-index="scope.$index"
/>
<el-input v-model="scope.row.itemDesc" placeholder="请输入项目名称" :data-row-index="scope.$index" />
</template>
</el-table-column>
<el-table-column prop="amount" label="金额(万元)" width="120">
<el-table-column prop="price" label="金额(万元)" width="180">
<template #default="scope">
<el-input-number
v-model="scope.row.amount"
:min="0"
:step="0.01"
:precision="2"
size="small"
@change="updateTotal"
/>
<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="deleteRow(scope.row)"
icon="Delete"
/>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 合计行 -->
<div class="mt-4 bg-gray-50 p-3 rounded text-right font-semibold">
合计: {{ totalAmount.toFixed(2) }} 万元
</div>
<div class="mt-4 bg-gray-50 p-3 rounded text-right font-semibold">合计: {{ formatNumber(totalAmount) }} 万元</div>
</el-card>
<!-- 添加对话框已移除直接在界面上添加行数输入框 -->
@ -77,161 +52,128 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
//
interface OtherCostItem {
id?: string
project: string
amount: number
}
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetOtherCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetOtherCost/types';
// Props
const props = defineProps<{
projectId?: string
}>()
// Emits
const emit = defineEmits<{
update: [data: {
totalAmount: number
}]
}>()
projectId?: string;
}>();
//
const loading = ref(false)
const otherCostList = ref<OtherCostItem[]>([])
const selectedRows = ref<OtherCostItem[]>([])
const addDialogVisible = ref(false)
const addRowCount = ref(1)
const loading = ref(false);
const otherCostList = ref<rdBudgetOtherCostVO[]>([]);
const selectedRows = ref<rdBudgetOtherCostVO[]>([]);
const addRowCount = ref(1);
const toDeletedOtherCostIdList = ref([]);
// ,
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 totalAmount = computed(() => {
return otherCostList.value.reduce((sum, item) => sum + (item.amount || 0), 0)
})
//
const updateTotal = () => {
emit('update', {
totalAmount: totalAmount.value
})
}
return otherCostList.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0);
});
const getOtherCost = () => {
return {
otherTotal: (totalAmount.value || 0) * 10000
};
};
//
//
const confirmAdd = () => {
const startIndex = otherCostList.value.length
const currentSortOrder = otherCostList.value && otherCostList.value.length > 0 ? Math.max(...otherCostList.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < addRowCount.value; i++) {
const newItem: OtherCostItem = {
project: '',
amount: 0
}
otherCostList.value.push(newItem)
const newItem: rdBudgetOtherCostVO = {
sortOrder: currentSortOrder + i + 1,
itemDesc: '',
price: 0
};
otherCostList.value.push(newItem);
}
//
setTimeout(() => {
const tableElement = document.querySelector('.el-table__body-wrapper')
const tableElement = document.querySelector('.el-table__body-wrapper');
if (tableElement) {
tableElement.scrollTop = tableElement.scrollHeight
tableElement.scrollTop = tableElement.scrollHeight;
}
//
const firstNewInput = document.querySelector(`[data-row-index="${startIndex}"]`)
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"]`);
if (firstNewInput) {
(firstNewInput as HTMLInputElement).focus()
(firstNewInput as HTMLInputElement).focus();
}
}, 100)
}, 100);
ElMessage.success(`成功添加 ${addRowCount.value}`)
}
//
const deleteRow = (row: OtherCostItem) => {
const index = otherCostList.value.findIndex(item => item === row)
if (index !== -1) {
otherCostList.value.splice(index, 1)
updateTotal()
ElMessage.success('删除成功')
}
}
//
const deleteSelectedRows = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要删除的行')
return
}
selectedRows.value.forEach(row => {
const index = otherCostList.value.findIndex(item => item === row)
if (index !== -1) {
otherCostList.value.splice(index, 1)
}
})
selectedRows.value = []
updateTotal()
ElMessage.success('删除成功')
}
};
//
const handleSelectionChange = (selection: OtherCostItem[]) => {
selectedRows.value = selection
}
const handleSelectionChange = (selection: rdBudgetOtherCostVO[]) => {
selectedRows.value = selection;
};
//
const handleDelete = (index: number, row: rdBudgetOtherCostVO) => {
otherCostList.value.splice(index, 1);
//
otherCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.otherCostId) {
toDeletedOtherCostIdList.value.push(row.otherCostId);
}
};
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedRows.value.forEach((selectedRow) => {
if (selectedRow.otherCostId) {
toDeletedOtherCostIdList.value.push(selectedRow.otherCostId);
}
});
otherCostList.value = otherCostList.value.filter((item) => !selectedRows.value.includes(item));
//
otherCostList.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = [];
};
// projectId
watch(() => props.projectId, (newProjectId) => {
if (newProjectId) {
loadData(newProjectId)
}
}, { immediate: true })
//
const loadData = async (projectId: string) => {
loading.value = true
try {
// API
await new Promise(resolve => setTimeout(resolve, 500))
//
otherCostList.value = [
{
id: '1',
project: '知识产权申请费',
amount: 1.5
},
{
id: '2',
project: '办公耗材费',
amount: 0.8
},
{
id: '3',
project: '其他杂费',
amount: 0.5
}
]
updateTotal()
} catch (error) {
ElMessage.error('加载数据失败')
console.error('加载数据失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.projectId,
(newProjectId) => {
if (newProjectId) {
}
},
{ immediate: true }
);
//
defineExpose({
otherCostList,
totalAmount,
updateTotal,
loadData
})
toDeletedOtherCostIdList,
getOtherCost
});
</script>
<style scoped>
@ -243,10 +185,6 @@ defineExpose({
margin-bottom: 16px;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-input-number) {
width: 100%;
}

@ -5,83 +5,68 @@
<div class="card-header">
<span>测试化验加工费明细</span>
<div class="flex items-center gap-2">
<span class="text-sm" style="white-space: nowrap;">行数:</span>
<el-input-number
v-model="addRowCount"
:min="1"
:max="10"
:step="1"
size="small"
/>
<el-button type="primary" size="small" @click="addRow"></el-button>
<el-button size="small" @click="deleteSelectedRows" :disabled="selectedRows.length === 0">删除</el-button>
<span class="text-sm" style="white-space: nowrap">行数:</span>
<el-input-number v-model="addRowCount" :min="1" :max="100" :step="1" size="small" />
<el-button type="primary" size="small" @click="addRow">
<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="testData" border style="width: 100%" row-key="id" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="testName" label="测试项目" width="200">
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="testingContent" label="测试项目" min-width="180">
<template #default="scope">
<el-input v-model="scope.row.testName" 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="testUnit" label="测试单位" width="180">
<el-table-column prop="testingUnitName" label="测试化验单位" width="180">
<template #default="scope">
<el-input v-model="scope.row.testUnit" placeholder="请输入测试单位" />
<el-input v-model="scope.row.testingUnitName" placeholder="请输入测试化验单位" />
</template>
</el-table-column>
<el-table-column prop="sampleCount" label="样品数量" width="120">
<el-table-column prop="unitId" label="单位" width="180">
<template #default="scope">
<el-input
v-model.number="scope.row.sampleCount"
type="number"
placeholder="样品数量"
@change="calculateAmount"
/>
<el-input v-model="scope.row.unitId" placeholder="请输入单位" />
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价" width="120">
<el-table-column prop="unitPrice" label="单价(元)" width="160">
<template #default="scope">
<el-input
v-model.number="scope.row.unitPrice"
type="number"
placeholder="单价"
@change="calculateAmount"
/>
<el-input-number v-model="scope.row.unitPrice" placeholder="单价" :min="0" :precision="2" size="small" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="amount" label="金额" width="120">
<el-table-column prop="amount" label="数量" width="160">
<template #default="scope">
<el-input v-model.number="scope.row.amount" type="number" placeholder="金额" disabled />
<el-input-number v-model="scope.row.amount" :min="0" :precision="2" size="small" @change="calculateAmount" />
</template>
</el-table-column>
<el-table-column prop="testDate" label="测试日期" width="120">
<el-table-column prop="price" label="金额(万元)" width="150">
<template #default="scope">
<el-date-picker
v-model="scope.row.testDate"
type="date"
placeholder="选择日期"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注">
<template #default="scope">
<el-input v-model="scope.row.remarks" placeholder="请输入备注" />
{{ 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="deleteRow(scope.row.id)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-row">
<div class="total-item">
<span>测试化验加工费总计</span>
<el-input v-model.number="totalAmount" type="number" disabled style="width: 150px; margin-left: 10px;" />
<span>测试化验加工费总计(万元){{ formatNumber(totalAmount) }}</span>
</div>
</div>
</el-card>
@ -89,178 +74,164 @@
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { ref, reactive, watch, computed } from 'vue';
import { ElMessage } from 'element-plus';
import { rdBudgetTestingCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTestingCost/types';
// IDprops
const props = defineProps<{
projectId: string
}>()
projectId: string;
}>();
//
const emit = defineEmits<{
update: [data: { totalAmount: number }]
}>()
update: [data: { totalAmount: number }];
}>();
//
const testData = reactive<any[]>([])
const testData = ref<rdBudgetTestingCostVO[]>([]);
const toDeletedTestCostIdList = ref([]);
//
const selectedRows = ref<any[]>([])
const selectedRows = ref<rdBudgetTestingCostVO[]>([]);
//
const addRowCount = ref(1)
const addRowCount = ref(1);
//
const totalAmount = ref(0)
const totalAmount = computed(() => {
return testData.value.reduce((sum, item) => {
const amountInTenThousand = (Number(item.price) || 0) / 10000;
const formattedAmount = parseFloat(amountInTenThousand.toFixed(2));
return sum + formattedAmount;
}, 0);
});
// ID
let idCounter = 1
// ID
watch(() => props.projectId, (newProjectId) => {
if (newProjectId) {
loadTestData(newProjectId)
}
}, { immediate: true })
watch(
() => props.projectId,
(newProjectId) => {
if (newProjectId) {
}
},
{ immediate: true }
);
//
const loadTestData = async (projectId: string) => {
try {
// API使
console.log('加载测试化验加工费数据:', projectId)
// ,
const formatNumber = (value: number) => {
if (!value) return '0.00';
return parseFloat(value).toFixed(2);
};
//
testData.splice(0, testData.length)
//
const mockData = [
{
id: idCounter++,
testName: '材料强度测试',
testUnit: '国家材料测试中心',
sampleCount: 5,
unitPrice: 10000,
amount: 50000,
testDate: '2023-06-15',
remarks: ''
},
{
id: idCounter++,
testName: '成分分析',
testUnit: '第三方检测机构',
sampleCount: 3,
unitPrice: 10000,
amount: 30000,
testDate: '2023-07-20',
remarks: ''
}
]
//
mockData.forEach(item => testData.push(item))
//
calculateTotal()
} catch (error) {
ElMessage.error('加载测试化验加工费数据失败')
console.error('加载测试化验加工费数据失败:', error)
}
}
// ,
const format2TenThousandNumber = (value: number) => {
if (!value) return '0.00';
return (parseFloat(value) / 10000).toFixed(2);
};
//
const addRow = () => {
const startIndex = testData.length
const currentSortOrder = testData.value && testData.value.length > 0 ? Math.max(...testData.value.map((item) => item.sortOrder)) : 0;
for (let i = 0; i < addRowCount.value; i++) {
testData.push({
id: idCounter++,
testName: '',
testUnit: '',
sampleCount: 0,
unitPrice: 0,
amount: 0,
testDate: '',
remarks: ''
})
testData.value.push({
sortOrder: currentSortOrder + i + 1,
testingContent: '',
testingUnitName: '',
unitId: undefined,
unitPrice: undefined,
amount: undefined,
price: 0
});
}
ElMessage.success(`成功添加 ${addRowCount.value}`)
//
setTimeout(() => {
const firstNewInput = document.querySelector(`[data-row-index="${startIndex}"]`)
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"]`);
if (firstNewInput) {
(firstNewInput as HTMLInputElement).focus()
(firstNewInput as HTMLInputElement).focus();
}
}, 100)
}
}, 100);
};
//
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
const handleSelectionChange = (selection: rdBudgetTestingCostVO[]) => {
selectedRows.value = selection;
};
//
const deleteSelectedRows = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请先选择要删除的行')
return
//
const handleDelete = (index: number, row: rdBudgetTestingCostVO) => {
testData.value.splice(index, 1);
//
testData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
if (row.testingCostId) {
toDeletedTestCostIdList.value.push(row.testingCostId);
}
};
const idsToDelete = new Set(selectedRows.value.map(row => row.id))
const newTestData = testData.filter(item => !idsToDelete.has(item.id))
//
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行');
return;
}
selectedRows.value.forEach((selectedRow) => {
if (selectedRow.testingCostId) {
toDeletedTestCostIdList.value.push(selectedRow.testingCostId);
}
});
testData.splice(0, testData.length, ...newTestData)
selectedRows.value = []
calculateTotal()
ElMessage.success('删除成功')
}
testData.value = testData.value.filter((item) => !selectedRows.value.includes(item));
//
testData.value.forEach((item, index) => {
item.sortOrder = index + 1;
});
selectedRows.value = [];
};
//
const calculateAmount = () => {
testData.forEach(item => {
item.amount = (item.sampleCount || 0) * (item.unitPrice || 0)
})
calculateTotal()
}
testData.value.forEach((item) => {
item.price = (item.amount || 0) * (item.unitPrice || 0);
});
};
//
const calculateTotal = () => {
totalAmount.value = testData.reduce((sum, item) => sum + (item.amount || 0), 0)
// update
emit('update', { totalAmount: totalAmount.value })
}
//
const getTestingAmount = () => {
return {
testingTotal: (Number(totalAmount.value) || 0) * 10000
};
};
//
const refreshData = () => {
if (props.projectId) {
loadTestData(props.projectId)
}
}
};
//
const resetData = () => {
testData.splice(0, testData.length)
totalAmount.value = 0
idCounter = 1
}
testData.value.splice(0, testData.value.length);
totalAmount.value = 0;
idCounter = 1;
};
//
const getFormData = () => {
return {
testData: [...testData],
testData: [...testData.value],
totalAmount: totalAmount.value
}
}
};
};
//
defineExpose({
refreshData,
resetData,
getFormData
})
testData,
toDeletedTestCostIdList,
getTestingAmount
});
</script>
<style scoped>

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save