|
|
|
|
|
<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>
|