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