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.

381 lines
13 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>
<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-table
:data="travelData"
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">
<template #default="scope">
<el-input v-model="scope.row.destination" />
</template>
</el-table-column>
<el-table-column prop="purpose" label="事由" width="150">
<template #default="scope">
<el-input v-model="scope.row.purpose" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column prop="times" label="次数" width="80" align="right">
<template #default="scope">
<el-input v-model="scope.row.times" @input="calculateBudgetSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="personCount" label="人数" width="80" align="right">
<template #default="scope">
<el-input v-model="scope.row.personCount" @input="calculateBudgetSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="days" label="天数" width="80" align="right">
<template #default="scope">
<el-input v-model="scope.row.days" @input="calculateBudgetSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="accommodationStandard" label="住宿标准(元)" width="120" align="right">
<template #default="scope">
<el-input v-model="scope.row.accommodationStandard" @input="calculateBudgetSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="roundTripFee" label="往返路费(元)" width="120" align="right">
<template #default="scope">
<el-input v-model="scope.row.roundTripFee" @input="calculateBudgetSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="accommodationFee" label="住宿费(元)" width="120" align="right">
<template #default="scope">
{{ formatNumber(scope.row.accommodationFee) }}
</template>
</el-table-column>
<el-table-column prop="subsidy" label="补贴(元)" width="120" align="right">
<template #default="scope">
{{ formatNumber(scope.row.subsidy) }}
</template>
</el-table-column>
<el-table-column prop="budgetSubtotal" label="小计(万元)" width="120" align="right">
<template #default="scope">
{{ formatNumber(scope.row.budgetSubtotal) }}
</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">
<template #default="scope">
<el-input v-model="scope.row.reducedDestination" />
</template>
</el-table-column>
<el-table-column prop="reducedPurpose" label="事由" width="150">
<template #default="scope">
<el-input v-model="scope.row.reducedPurpose" type="textarea" :rows="2" />
</template>
</el-table-column>
<el-table-column prop="reducedTimes" label="次数" width="80" align="right">
<template #default="scope">
<el-input v-model="scope.row.reducedTimes" @input="calculateReducedSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="reducedPersonCount" label="人数" width="80" align="right">
<template #default="scope">
<el-input v-model="scope.row.reducedPersonCount" @input="calculateReducedSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="reducedDays" label="天数" width="80" align="right">
<template #default="scope">
<el-input v-model="scope.row.reducedDays" @input="calculateReducedSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="reducedAccommodationStandard" label="住宿标准(元)" width="120" align="right">
<template #default="scope">
<el-input v-model="scope.row.reducedAccommodationStandard" @input="calculateReducedSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="reducedRoundTripFee" label="往返路费(元)" width="120" align="right">
<template #default="scope">
<el-input v-model="scope.row.reducedRoundTripFee" @input="calculateReducedSubtotal(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="reducedAccommodationFee" label="住宿费(元)" width="120" align="right">
<template #default="scope">
{{ formatNumber(scope.row.reducedAccommodationFee) }}
</template>
</el-table-column>
<el-table-column prop="reducedSubsidy" label="补贴(元)" width="120" align="right">
<template #default="scope">
{{ formatNumber(scope.row.reducedSubsidy) }}
</template>
</el-table-column>
<el-table-column prop="reducedSubtotal" label="小计(万元)" width="120" align="right">
<template #default="scope">
{{ formatNumber(scope.row.reducedSubtotal) }}
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="costReductionPlan" label="降成本方案" width="200">
<template #default="scope">
<el-input v-model="scope.row.costReductionPlan" 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>
</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>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
//
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 selectedRows = ref([])
//
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 })
}
// 计算预算小计
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
// 住宿费 = 人数 × 天数 × 住宿标准
row.accommodationFee = personCount * days * accommodationStandard
// 补贴 = 50 × 人数 × 天数
row.subsidy = 50 * personCount * days
// 小计 = (往返路费 + 住宿费 + 补贴) / 10000
row.budgetSubtotal = (roundTripFee + row.accommodationFee + row.subsidy) / 10000 // 转换为万元
}
// 计算降成本后小计
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
// 住宿费 = 人数 × 天数 × 住宿标准
row.reducedAccommodationFee = personCount * days * accommodationStandard
// 补贴 = 50 × 人数 × 天数
row.reducedSubsidy = 50 * personCount * days
// 小计 = (往返路费 + 住宿费 + 补贴) / 10000
row.reducedSubtotal = (roundTripFee + row.reducedAccommodationFee + row.reducedSubsidy) / 10000 // 转换为万元
}
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 添加行
const handleAddRows = () => {
const currentMaxSerial = Math.max(...travelData.value.map(item => item.serialNumber))
for (let i = 0; i < addRowCount.value; i++) {
travelData.value.push({
serialNumber: currentMaxSerial + i + 1,
destination: '',
purpose: '',
times: 0,
personCount: 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: ''
})
}
showAddDialog.value = false
addRowCount.value = 1
}
// 删除单行
const handleDelete = (index: number) => {
travelData.value.splice(index, 1)
// 重新编号
travelData.value.forEach((item, index) => {
item.serialNumber = index + 1
item.reducedSerialNumber = index + 1
})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的行')
return
}
travelData.value = travelData.value.filter(item => !selectedRows.value.includes(item))
// 重新编号
travelData.value.forEach((item, index) => {
item.serialNumber = index + 1
item.reducedSerialNumber = index + 1
})
selectedRows.value = []
}
// 合计方法
const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
columns.forEach((column: any, index: number) => {
if (index === 0) {
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 (!values.every((value: number) => Number.isNaN(value))) {
sums[index] = values.reduce((prev: number, curr: number) => {
const value = Number(curr)
if (!Number.isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
sums[index] = formatNumber(sums[index])
} else {
sums[index] = ''
}
} else {
sums[index] = ''
}
})
return sums
}
// 获取总金额(供父组件调用)
const getTotalAmount = () => {
const totalBudgetAmount = travelData.value.reduce((sum, item) => sum + item.budgetSubtotal, 0)
const totalReducedAmount = travelData.value.reduce((sum, item) => sum + item.reducedSubtotal, 0)
return {
budgetAmount: totalBudgetAmount,
reducedAmount: totalReducedAmount
}
}
// 暴露方法给父组件
defineExpose({
getTotalAmount,
travelData
})
</script>
<style scoped>
.travel-cost {
padding: 20px;
}
.title {
text-align: center;
font-size: 16px;
font-weight: bold;
margin-bottom: 20px;
}
.actions {
margin-bottom: 20px;
}
.actions .el-button {
margin-right: 10px;
}
</style>