You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

395 lines
14 KiB
Vue

This file contains ambiguous Unicode characters!

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

<template>
<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="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="sortOrder" label="序号" width="80" align="center" />
<el-table-column prop="tripLocation" label="出差地点" width="120">
<template #default="scope">
<el-input v-model="scope.row.tripLocation" />
</template>
</el-table-column>
<el-table-column prop="reason" label="事由" width="150">
<template #default="scope">
<el-input v-model="scope.row.reason" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column prop="frequency" label="次数" width="120">
<template #default="scope">
<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="peopleNumber" label="人数" width="120">
<template #default="scope">
<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="130">
<template #default="scope">
<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="stayStandard" label="住宿标准(元)" width="140">
<template #default="scope">
<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="travelExpenses" label="往返路费(元)" width="140">
<template #default="scope">
<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="stayCosts" label="住宿费(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.stayCosts) }}
</template>
</el-table-column>
<el-table-column prop="subsidyCosts" label="补贴(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.subsidyCosts) }}
</template>
</el-table-column>
<el-table-column prop="subtotalCosts" label="小计(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.subtotalCosts) }}
</template>
</el-table-column>
</el-table-column>
<!-- 降成本后预算成本 -->
<el-table-column label="降成本后预算成本" align="center">
<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.reduceTripLocation" />
</template>
</el-table-column>
<el-table-column prop="reduceReason" label="事由" width="150">
<template #default="scope">
<el-input v-model="scope.row.reduceReason" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column prop="reduceFrequency" label="次数" width="120">
<template #default="scope">
<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="reducePeopleNumber" label="人数" width="120">
<template #default="scope">
<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="reduceDayNumber" label="天数" width="120">
<template #default="scope">
<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="reduceStayStandard" label="住宿标准(元)" width="140">
<template #default="scope">
<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="reduceTravelExpenses" label="往返路费(元)" width="140">
<template #default="scope">
<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="reduceStayCosts" label="住宿费(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reduceStayCosts) }}
</template>
</el-table-column>
<el-table-column prop="reduceSubsidyCosts" label="补贴(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reduceSubsidyCosts) }}
</template>
</el-table-column>
<el-table-column prop="reduceSubtotalCosts" label="小计(元)" width="100">
<template #default="scope">
{{ formatNumber(scope.row.reduceSubtotalCosts) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="reduceProposal" label="降成本方案" width="200">
<template #default="scope">
<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, scope.row)" icon="Delete">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加行数弹窗 -->
<el-dialog v-model="showAddDialog" title="添加行数" width="400px">
<el-form>
<el-form-item label="添加行数">
<el-input-number v-model="addRowCount" :min="1" :max="10" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="handleAddRows">确定</el-button>
</template>
</el-dialog>
</el-card>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { budgetTravelCostVO } from '@/api/oa/erp/budgetInfo/market/budgetTravelCost/types';
//
const travelCostList = ref<budgetTravelCostVO[]>([]);
const toDeletedTravelCostIdList = ref([]);
// 选中的行
const selectedRows = ref([]);
// 添加行弹窗
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 calculateBudgetSubtotal = (row: any) => {
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.stayCosts = peopleNumber * days * stayStandard;
// 补贴 = 50 × 人数 × 天数
row.subsidyCosts = 50 * peopleNumber * days;
// 小计 = (往返路费 + 住宿费 + 补贴)
row.subtotalCosts = travelExpenses + row.stayCosts + row.subsidyCosts;
};
// 计算降成本后小计
const calculateReducedSubtotal = (row: any) => {
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.reduceStayCosts = peopleNumber * days * stayStandard;
// 补贴 = 50 × 人数 × 天数
row.reduceSubsidyCosts = 50 * peopleNumber * days;
// 小计 = (往返路费 + 住宿费 + 补贴)
row.reduceSubtotalCosts = travelExpenses + row.reduceStayCosts + row.reduceSubsidyCosts;
};
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection;
};
// 添加行
const handleAddRows = () => {
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++) {
travelCostList.value.push({
sortOrder: currentSortOrder + i + 1,
tripLocation: '',
reason: '',
frequency: 0,
peopleNumber: 0,
days: 0,
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: ''
});
}
};
// 删除单行
const handleDelete = (index: number, row: budgetTravelCostVO) => {
travelCostList.value.splice(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;
}
selectedRows.value.forEach((row) => {
if (row.travelCostId) {
toDeletedTravelCostIdList.value.push(row.travelCostId);
}
});
travelCostList.value = travelCostList.value.filter((item) => !selectedRows.value.includes(item));
// 重新编号
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[] = [];
columns.forEach((column: any, index: number) => {
if (index === 7) {
sums[index] = '合计';
return;
}
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);
if (!Number.isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
if (column.property === 'subtotalCosts' || column.property === 'reduceSubtotalCosts') {
sums[index] = formatNumber(sums[index]);
} else {
sums[index] = formatNumber(sums[index]);
}
} else {
sums[index] = '';
}
} else {
sums[index] = '';
}
});
return sums;
};
// 获取总金额(供父组件调用)
const getTotalAmount = () => {
const totalBudgetAmount = travelCostList.value.reduce((sum, item) => sum + Number(item.subtotalCosts), 0);
const totalReducedAmount = travelCostList.value.reduce((sum, item) => sum + Number(item.reduceSubtotalCosts), 0);
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
};
};
// 暴露方法给父组件
defineExpose({
getTotalAmount,
travelCostList,
toDeletedTravelCostIdList
});
</script>
<style scoped>
.travel-cost {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
text-align: center;
font-size: 16px;
font-weight: bold;
margin-bottom: 20px;
}
.actions {
margin-bottom: 20px;
}
.actions .el-button {
margin-right: 10px;
}
</style>