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.
274 lines
7.7 KiB
Vue
274 lines
7.7 KiB
Vue
<template>
|
|
<div class="test-cost-container">
|
|
<el-card class="table-card">
|
|
<template #header>
|
|
<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="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"
|
|
show-summary
|
|
:summary-method="
|
|
() => {
|
|
const cols = ['', '', '合计(万元)', '', '', '', '', ''];
|
|
cols[cols.length - 1] = formatNumber(totalAmount);
|
|
return cols;
|
|
}
|
|
"
|
|
@selection-change="handleSelectionChange"
|
|
>
|
|
<el-table-column type="selection" width="55" />
|
|
<el-table-column prop="sortOrder" label="序号" width="80" align="center" />
|
|
<el-table-column prop="testingContent" label="测试化验加工的内容" min-width="200">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.testingContent" placeholder="请输入测试化验加工内容" :data-row-index="scope.$index" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="testingUnitName" label="测试化验加工单位" width="200">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.testingUnitName" placeholder="请输入测试化验加工单位" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="unitName" label="单位" width="180">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.unitName" placeholder="请输入单位" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="unitPrice" label="单价(元)" width="160">
|
|
<template #default="scope">
|
|
<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="160">
|
|
<template #default="scope">
|
|
<el-input-number v-model="scope.row.amount" :min="0" :precision="2" size="small" @change="calculateAmount" />
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="price" label="金额(万元)" width="150">
|
|
<template #default="scope">
|
|
{{ 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="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-left font-semibold">
|
|
<p>注:是指支付给外单位(包括公司内部经济核算单位)的检验、测试、化验及加工等费用。</p>
|
|
</div>
|
|
</el-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, watch, computed } from 'vue';
|
|
import { ElMessage } from 'element-plus';
|
|
import { rdBudgetTestingCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTestingCost/types';
|
|
|
|
// 接收项目ID作为props
|
|
const props = defineProps<{
|
|
projectId: string;
|
|
}>();
|
|
|
|
// 定义事件
|
|
const emit = defineEmits<{
|
|
update: [data: { totalAmount: number }];
|
|
}>();
|
|
|
|
// 测试化验加工费数据
|
|
const testData = ref<rdBudgetTestingCostVO[]>([]);
|
|
const toDeletedTestCostIdList = ref([]);
|
|
// 选中的行
|
|
const selectedRows = ref<rdBudgetTestingCostVO[]>([]);
|
|
|
|
// 添加行数
|
|
const addRowCount = ref(1);
|
|
|
|
// 总金额
|
|
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变化
|
|
watch(
|
|
() => props.projectId,
|
|
(newProjectId) => {
|
|
if (newProjectId) {
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
// 格式化数字,元转换为万元
|
|
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 addRow = () => {
|
|
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.value.push({
|
|
sortOrder: currentSortOrder + i + 1,
|
|
testingContent: '',
|
|
testingUnitName: '',
|
|
unitId: undefined,
|
|
unitPrice: undefined,
|
|
amount: undefined,
|
|
price: 0
|
|
});
|
|
}
|
|
|
|
// 延迟聚焦到新添加行的第一个输入框
|
|
setTimeout(() => {
|
|
const firstNewInput = document.querySelector(`[data-row-index="${currentSortOrder}"]`);
|
|
if (firstNewInput) {
|
|
(firstNewInput as HTMLInputElement).focus();
|
|
}
|
|
}, 100);
|
|
};
|
|
|
|
// 处理选择变化
|
|
const handleSelectionChange = (selection: rdBudgetTestingCostVO[]) => {
|
|
selectedRows.value = selection;
|
|
};
|
|
|
|
// 删除单行
|
|
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 handleBatchDelete = () => {
|
|
if (selectedRows.value.length === 0) {
|
|
ElMessage.warning('请选择要删除的行');
|
|
return;
|
|
}
|
|
selectedRows.value.forEach((selectedRow) => {
|
|
if (selectedRow.testingCostId) {
|
|
toDeletedTestCostIdList.value.push(selectedRow.testingCostId);
|
|
}
|
|
});
|
|
|
|
testData.value = testData.value.filter((item) => !selectedRows.value.includes(item));
|
|
// 重新编号
|
|
testData.value.forEach((item, index) => {
|
|
item.sortOrder = index + 1;
|
|
});
|
|
selectedRows.value = [];
|
|
};
|
|
|
|
// 计算单项金额
|
|
const calculateAmount = () => {
|
|
testData.value.forEach((item) => {
|
|
item.price = (item.amount || 0) * (item.unitPrice || 0);
|
|
});
|
|
};
|
|
|
|
// 获取总金额(供父组件调用)
|
|
const getTestingAmount = () => {
|
|
return {
|
|
testingTotal: (Number(totalAmount.value) || 0) * 10000
|
|
};
|
|
};
|
|
|
|
// 刷新数据
|
|
const refreshData = () => {
|
|
if (props.projectId) {
|
|
}
|
|
};
|
|
|
|
// 重置数据
|
|
const resetData = () => {
|
|
testData.value.splice(0, testData.value.length);
|
|
totalAmount.value = 0;
|
|
};
|
|
|
|
// 获取表单数据
|
|
const getFormData = () => {
|
|
return {
|
|
testData: [...testData.value],
|
|
totalAmount: totalAmount.value
|
|
};
|
|
};
|
|
|
|
// 暴露方法给父组件
|
|
defineExpose({
|
|
testData,
|
|
toDeletedTestCostIdList,
|
|
getTestingAmount
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.test-cost-container {
|
|
padding: 10px;
|
|
}
|
|
|
|
.table-card {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.total-row {
|
|
margin-top: 20px;
|
|
text-align: right;
|
|
}
|
|
|
|
.total-item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|