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.
251 lines
7.6 KiB
Vue
251 lines
7.6 KiB
Vue
|
4 months ago
|
<template>
|
||
|
|
<div class="budget-table-container">
|
||
|
|
<h2 class="title-center">项目经费预算表</h2>
|
||
|
|
|
||
|
|
<el-form :model="formData" label-width="120px" class="mb-4">
|
||
|
|
<el-row :gutter="20">
|
||
|
|
<el-col :span="12">
|
||
|
|
<el-form-item label="项目名称">
|
||
|
|
<el-input v-model="formData.projectName" readonly />
|
||
|
|
</el-form-item>
|
||
|
|
</el-col>
|
||
|
|
<el-col :span="12">
|
||
|
|
<el-form-item label="项目号">
|
||
|
|
<el-input v-model="formData.projectCode" readonly />
|
||
|
|
</el-form-item>
|
||
|
|
</el-col>
|
||
|
|
<el-col :span="12">
|
||
|
|
<el-form-item label="项目预算期间">
|
||
|
|
<el-input v-model="formData.durationOperation" />
|
||
|
|
</el-form-item>
|
||
|
|
</el-col>
|
||
|
|
<el-col :span="12">
|
||
|
|
<el-form-item label="金额单位">
|
||
|
|
<el-input v-model="formData.unit" readonly value="万元" />
|
||
|
|
</el-form-item>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
</el-form>
|
||
|
|
|
||
|
|
<el-table :data="budgetData" style="width: 100%" border>
|
||
|
|
<el-table-column prop="index" label="序号" width="80" />
|
||
|
|
<el-table-column prop="subject" label="预算科目名称" />
|
||
|
|
<el-table-column prop="amount" label="项目经费" width="260" align="right">
|
||
|
|
<template #default="scope">
|
||
|
|
{{ scope.row.amount.toFixed(2) }}
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
</el-table>
|
||
|
|
|
||
|
|
<el-form :model="footerForm" label-width="160px" class="mt-4">
|
||
|
|
<el-row>
|
||
|
|
<el-col :span="12">
|
||
|
|
<el-form-item label="编制(项目经理)">
|
||
|
|
<el-select
|
||
|
|
v-model="footerForm.creator"
|
||
|
|
placeholder="请选择项目经理"
|
||
|
|
filterable
|
||
|
|
clearable
|
||
|
|
:filter-method="(query) => creatorSearchQuery = query"
|
||
|
|
:remote="false"
|
||
|
|
>
|
||
|
|
<el-option
|
||
|
|
v-for="user in filteredCreatorList"
|
||
|
|
:key="user.id"
|
||
|
|
:label="formatUserDisplay(user)"
|
||
|
|
:value="user.name"
|
||
|
|
>
|
||
|
|
<div class="flex items-center">
|
||
|
|
<span class="mr-2">{{ user.department }}</span>
|
||
|
|
<span>{{ user.name }}</span>
|
||
|
|
</div>
|
||
|
|
</el-option>
|
||
|
|
</el-select>
|
||
|
|
</el-form-item>
|
||
|
|
</el-col>
|
||
|
|
<el-col :span="12">
|
||
|
|
<el-form-item label="审核(评审组长)">
|
||
|
|
<el-select
|
||
|
|
v-model="footerForm.reviewer"
|
||
|
|
placeholder="请选择评审组长"
|
||
|
|
filterable
|
||
|
|
clearable
|
||
|
|
:filter-method="(query) => reviewerSearchQuery = query"
|
||
|
|
:remote="false"
|
||
|
|
>
|
||
|
|
<el-option
|
||
|
|
v-for="user in filteredReviewerList"
|
||
|
|
:key="user.id"
|
||
|
|
:label="formatUserDisplay(user)"
|
||
|
|
:value="user.name"
|
||
|
|
>
|
||
|
|
<div class="flex items-center">
|
||
|
|
<span class="mr-2">{{ user.department }}</span>
|
||
|
|
<span>{{ user.name }}</span>
|
||
|
|
</div>
|
||
|
|
</el-option>
|
||
|
|
</el-select>
|
||
|
|
</el-form-item>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
</el-form>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||
|
|
|
||
|
|
// 响应式数据
|
||
|
|
const formData = reactive({
|
||
|
|
projectName: '',
|
||
|
|
projectCode: '',
|
||
|
|
budgetPeriod: null,
|
||
|
|
unit: '万元'
|
||
|
|
});
|
||
|
|
|
||
|
|
const footerForm = reactive({
|
||
|
|
creator: '',
|
||
|
|
reviewer: ''
|
||
|
|
});
|
||
|
|
|
||
|
|
// 用户下拉列表数据
|
||
|
|
const userList = ref([
|
||
|
|
{ id: '1', name: '张三', department: '研发部' },
|
||
|
|
{ id: '2', name: '李四', department: '市场部' },
|
||
|
|
{ id: '3', name: '王五', department: '财务部' },
|
||
|
|
{ id: '4', name: '赵六', department: '人力资源部' },
|
||
|
|
{ id: '5', name: '钱七', department: '研发部' }
|
||
|
|
]);
|
||
|
|
|
||
|
|
// 搜索关键词
|
||
|
|
const creatorSearchQuery = ref('');
|
||
|
|
const reviewerSearchQuery = ref('');
|
||
|
|
|
||
|
|
// 过滤后的用户列表
|
||
|
|
const filteredCreatorList = computed(() => {
|
||
|
|
return userList.value.filter(user =>
|
||
|
|
user.name.includes(creatorSearchQuery.value)
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
const filteredReviewerList = computed(() => {
|
||
|
|
return userList.value.filter(user =>
|
||
|
|
user.name.includes(reviewerSearchQuery.value)
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
// 格式化显示文本
|
||
|
|
const formatUserDisplay = (user) => {
|
||
|
|
return `${user.department} - ${user.name}`;
|
||
|
|
};
|
||
|
|
|
||
|
|
// 预算数据
|
||
|
|
const budgetData = ref([
|
||
|
|
{ index: 1, subject: '设备费', amount: 0 },
|
||
|
|
{ index: 2, subject: '材料费', amount: 0 },
|
||
|
|
{ index: 3, subject: '差旅费', amount: 0 },
|
||
|
|
{ index: 4, subject: '会议费', amount: 0 },
|
||
|
|
{ index: 5, subject: '国际合作与交流费', amount: 0 },
|
||
|
|
{ index: 6, subject: '咨询开发费', amount: 0 },
|
||
|
|
{ index: 7, subject: '人工费', amount: 0 },
|
||
|
|
{ index: 8, subject: '劳务费', amount: 0 },
|
||
|
|
{ index: 9, subject: '资料/文献费', amount: 0 },
|
||
|
|
{ index: 10, subject: '测试化验费', amount: 0 },
|
||
|
|
{ index: 11, subject: '其他费用', amount: 0 },
|
||
|
|
{ index: '', subject: '合计', amount: 0 }
|
||
|
|
]);
|
||
|
|
|
||
|
|
// 计算合计
|
||
|
|
const totalAmount = computed(() => {
|
||
|
|
return budgetData.value.slice(0, -1).reduce((sum, item) => sum + item.amount, 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
// 更新费用项
|
||
|
|
const updateCostItem = (subject, amount) => {
|
||
|
|
const index = budgetData.value.findIndex(item => item.subject === subject)
|
||
|
|
if (index !== -1) {
|
||
|
|
// 使用展开运算符创建新对象,确保响应式更新
|
||
|
|
const updatedItem = { ...budgetData.value[index], amount: amount }
|
||
|
|
budgetData.value.splice(index, 1, updatedItem)
|
||
|
|
|
||
|
|
// 确保合计行也被正确更新
|
||
|
|
const totalIndex = budgetData.value.length - 1
|
||
|
|
const totalRow = { ...budgetData.value[totalIndex], amount: totalAmount.value }
|
||
|
|
budgetData.value.splice(totalIndex, 1, totalRow)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新所有费用数据
|
||
|
|
const updateCostData = (costData) => {
|
||
|
|
// 设备费
|
||
|
|
updateCostItem('设备费', costData.equipmentCost || 0)
|
||
|
|
// 材料费
|
||
|
|
updateCostItem('材料费', costData.materialCost || 0)
|
||
|
|
// 差旅费
|
||
|
|
updateCostItem('差旅费', costData.travelCost || 0)
|
||
|
|
// 会议费
|
||
|
|
updateCostItem('会议费', costData.meetingCost || 0)
|
||
|
|
// 国际合作与交流费
|
||
|
|
updateCostItem('国际合作与交流费', costData.internationalExchangeCost || 0)
|
||
|
|
// 咨询开发费
|
||
|
|
updateCostItem('咨询开发费', costData.consultingDevelopmentCost || 0)
|
||
|
|
// 人工费
|
||
|
|
updateCostItem('人工费', costData.laborCost || 0)
|
||
|
|
// 劳务费
|
||
|
|
updateCostItem('劳务费', costData.serviceCost || 0)
|
||
|
|
// 资料/文献费
|
||
|
|
updateCostItem('资料/文献费', costData.literatureCost || 0)
|
||
|
|
// 测试化验费
|
||
|
|
updateCostItem('测试化验费', costData.testingCost || 0)
|
||
|
|
// 其他费用
|
||
|
|
updateCostItem('其他费用', costData.otherCost || 0)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 接收项目信息
|
||
|
|
const props = defineProps({
|
||
|
|
projectInfo: {
|
||
|
|
type: Object,
|
||
|
|
default: () => ({})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// 监听项目信息变化
|
||
|
|
watch(() => props.projectInfo, (newProjectInfo) => {
|
||
|
|
if (newProjectInfo) {
|
||
|
|
formData.projectName = newProjectInfo.projectName || ''
|
||
|
|
formData.projectCode = newProjectInfo.projectCode || ''
|
||
|
|
formData.budgetPeriod = newProjectInfo.budgetPeriod || null
|
||
|
|
formData.unit = newProjectInfo.amountUnit || '万元'
|
||
|
|
}
|
||
|
|
}, { deep: true, immediate: true })
|
||
|
|
|
||
|
|
// 获取预算数据
|
||
|
|
const getBudgetData = () => {
|
||
|
|
return {
|
||
|
|
formData: { ...formData },
|
||
|
|
footerForm: { ...footerForm },
|
||
|
|
budgetData: budgetData.value.map(item => ({ ...item }))
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
// 暴露方法给父组件
|
||
|
|
defineExpose({
|
||
|
|
updateCostItem,
|
||
|
|
updateCostData,
|
||
|
|
getBudgetData
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.budget-table-container {
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.title-center {
|
||
|
|
text-align: center;
|
||
|
|
margin-bottom: 30px;
|
||
|
|
font-size: 20px;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
</style>
|