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

640 lines
20 KiB

8 months ago
<template>
<div class="split-package-form">
<el-form ref="basicForm" :model="basicInfo" :rules="basicRules" label-width="120px">
<!-- 基本信息 -->
<div class="section-title">
<i class="el-icon-edit-outline"></i>
基本信息
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预算名称">
<el-input
:value="packageData.name"
placeholder="预算名称"
readonly
disabled
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分配金额">
<el-input
:value="formatCurrency(packageData.allocated_amount)"
placeholder="分配金额"
readonly
disabled
>
<template slot="append">万元</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="申请依据" prop="basis">
<el-input
v-model="basicInfo.basis"
type="textarea"
:rows="3"
placeholder="请输入申请依据"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="申请目的" prop="purpose">
<el-input
v-model="basicInfo.purpose"
type="textarea"
:rows="3"
placeholder="请输入申请目的"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请理由" prop="reason">
<el-input
v-model="basicInfo.reason"
type="textarea"
:rows="3"
placeholder="请输入申请理由"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="项目内容" prop="content">
<el-input
v-model="basicInfo.content"
type="textarea"
:rows="4"
placeholder="请详细描述项目内容"
maxlength="1000"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 执行科室选择与金额分配 -->
<div class="section-title">
<i class="el-icon-s-cooperation"></i>
执行科室分配
</div>
<div class="department-allocation">
<div class="add-department">
<el-select
v-model="selectedDepartment"
placeholder="选择执行科室"
style="width: 200px; margin-right: 10px;"
clearable
>
<el-option
v-for="dept in availableDepartments"
:key="dept.id"
:label="dept.name"
:value="dept.id"
/>
</el-select>
<el-button type="primary" @click="addDepartment" :disabled="!selectedDepartment">
添加科室
</el-button>
</div>
<el-table
:data="departmentAllocations"
style="width: 100%; margin-top: 15px;"
border
:summary-method="getSummaries"
show-summary
>
<el-table-column type="index" label="序号" width="60" align="center" />
<!-- 隐含department_id显示名称 -->
<el-table-column label="执行科室" width="220">
<template slot-scope="scope">
<div style="display: flex; align-items: center;">
<el-tag type="info" style="margin-right: 8px;">{{ scope.row.departmentName }}</el-tag>
<el-tooltip content="科室ID" placement="top">
<span style="color: #bbb; font-size: 12px;">[ID: {{ scope.row.departmentId || scope.row.department_id }}]</span>
</el-tooltip>
<!-- 隐藏域确保department_id在表格数据中 -->
<input type="hidden" :value="scope.row.departmentId || scope.row.department_id" />
12 months ago
<input type="hidden" :value="scope.row.packageId || scope.row.package_id" />
8 months ago
</div>
</template>
</el-table-column>
<el-table-column label="分配金额" width="200" align="center">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.allocatedAmount"
type="number"
12 months ago
:min="0.1"
8 months ago
:step="0.1"
placeholder="请输入分配金额"
@input="validateAllocation"
>
<template slot="append">万元</template>
</el-input>
</template>
</el-table-column>
<el-table-column prop="childPackageName" label="执行科室包名称" width="250">
<template slot-scope="scope">
<el-input
v-model="scope.row.childPackageName"
placeholder="请输入执行科室包名称"
maxlength="200"
/>
</template>
</el-table-column>
<el-table-column prop="description" label="说明">
<template slot-scope="scope">
<el-input
v-model="scope.row.description"
placeholder="请输入说明(可选)"
maxlength="300"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope">
<el-button
type="danger"
size="mini"
@click="removeDepartment(scope.$index)"
icon="el-icon-delete"
circle
/>
</template>
</el-table-column>
</el-table>
<div class="allocation-summary" v-if="departmentAllocations.length > 0">
<div class="summary-item" :class="{ 'error': totalAllocated > packageData.allocated_amount }">
<span>已分配总额{{ formatCurrency(totalAllocated) }} 万元</span>
</div>
<div class="summary-item">
<span>待分配金额{{ formatCurrency(packageData.allocated_amount - totalAllocated) }} 万元</span>
</div>
<div class="summary-item" v-if="totalAllocated !== packageData.allocated_amount" style="color: #F56C6C;">
<i class="el-icon-warning"></i>
分配金额必须等于预算分配金额
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="form-actions">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
{{ packageData.isEditMode ? '确认修改' : '确认分配' }}
</el-button>
</div>
</div>
</template>
<script>
import { listdeptNoAuth } from '@/api/system/department.js'
export default {
name: 'SplitPackageForm',
props: {
packageData: {
type: Object,
required: true
}
},
data() {
return {
submitting: false,
selectedDepartment: '',
departmentOptions: [],
basicInfo: {
basis: '',
purpose: '',
reason: '',
content: ''
},
submissionData: null,
basicRules: {
basis: [
{ required: true, message: '请输入申请依据', trigger: 'blur' },
{ min: 10, max: 500, message: '申请依据长度在10到500个字符', trigger: 'blur' }
],
purpose: [
{ required: true, message: '请输入申请目的', trigger: 'blur' },
{ min: 10, max: 500, message: '申请目的长度在10到500个字符', trigger: 'blur' }
],
reason: [
{ required: true, message: '请输入申请理由', trigger: 'blur' },
{ min: 10, max: 500, message: '申请理由长度在10到500个字符', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入项目内容', trigger: 'blur' },
{ min: 20, max: 1000, message: '项目内容长度在20到1000个字符', trigger: 'blur' }
]
},
departmentAllocations: []
}
},
computed: {
availableAmount() {
return this.packageData.allocated_amount
},
availableDepartments() {
const allocatedDeptIds = this.departmentAllocations.map(item => item.departmentId)
return this.departmentOptions.filter(dept => !allocatedDeptIds.includes(dept.id))
},
totalAllocated() {
return this.departmentAllocations.reduce((sum, item) => {
return sum + (parseFloat(item.allocatedAmount) || 0)
}, 0)
}
},
async created() {
await this.loadDepartments()
await this.loadSubmissionData()
// 如果是编辑模式,加载现有分配数据
if (this.packageData.isEditMode) {
await this.loadExistingAllocations()
}
},
methods: {
async loadDepartments() {
try {
const response = await listdeptNoAuth()
if (response && Array.isArray(response)) {
this.departmentOptions = response
}
} catch (error) {
console.error('加载科室失败:', error)
this.$message.error('加载科室列表失败')
}
},
async loadSubmissionData() {
// 加载申请依据等基本信息
try {
console.log('Loading submission data for package:', this.packageData)
// 1. 优先从父级预算包的申请信息字段中加载
if (this.packageData.application_basis ||
this.packageData.application_purpose ||
this.packageData.application_reason ||
this.packageData.project_content) {
this.basicInfo = {
basis: this.packageData.application_basis || '',
purpose: this.packageData.application_purpose || '',
reason: this.packageData.application_reason || '',
content: this.packageData.project_content || ''
}
console.log('从父级预算包加载申请信息:', this.basicInfo)
return
}
// 2. 尝试从submission中加载
if (this.packageData.submission) {
this.basicInfo = {
basis: this.packageData.submission.application_basis || '',
purpose: this.packageData.submission.application_purpose || '',
reason: this.packageData.submission.application_reason || '',
content: this.packageData.submission.project_content || ''
}
console.log('从submission加载申请信息:', this.basicInfo)
return
}
// 3. 如果是编辑模式但没有数据尝试从API获取
if (this.packageData.isEditMode) {
try {
// 这里可以调用API获取BudgetSubmission数据
// 暂时使用默认值,避免表单为空
this.basicInfo = {
basis: '修改分配',
purpose: '调整预算分配',
reason: '根据实际需求调整执行科室分配',
content: '对预算包进行重新分配'
}
console.log('使用默认申请信息:', this.basicInfo)
} catch (error) {
console.error('从API加载申请信息失败:', error)
}
}
} catch (error) {
console.error('加载提交数据失败:', error)
}
},
async loadExistingAllocations() {
// 编辑模式直接使用API返回的children数据
try {
if (this.packageData.children && this.packageData.children.length > 0) {
this.departmentAllocations = this.packageData.children.map(child => ({
12 months ago
packageId: child.id,
8 months ago
departmentId: child.department.id,
departmentName: child.department.name,
allocatedAmount: child.allocated_amount,
childPackageName: child.name,
description: child.description || ''
}))
}
// 加载申请依据信息
if (this.packageData.submission) {
this.basicInfo = {
basis: this.packageData.submission.application_basis || '',
purpose: this.packageData.submission.application_purpose || '',
reason: this.packageData.submission.application_reason || '',
content: this.packageData.submission.project_content || ''
}
}
} catch (error) {
console.error('加载现有分配数据失败:', error)
this.$message.error('加载现有分配数据失败')
}
},
addDepartment() {
if (!this.selectedDepartment) return
const dept = this.departmentOptions.find(d => d.id === this.selectedDepartment)
if (!dept) return
this.departmentAllocations.push({
departmentId: dept.id,
departmentName: dept.name,
allocatedAmount: 0,
childPackageName: `${this.packageData.name}-${dept.name}`,
description: ''
})
this.selectedDepartment = ''
},
removeDepartment(index) {
this.departmentAllocations.splice(index, 1)
this.validateAllocation()
},
validateAllocation() {
this.$nextTick(() => {
// 触发金额分配验证
})
},
getSummaries(param) {
const { columns } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计'
} else if (index === 2) { // 分配金额列
sums[index] = this.formatCurrency(this.totalAllocated) + ' 万元'
} else {
sums[index] = ''
}
})
return sums
},
async handleSubmit() {
// 验证基本信息表单
const basicValid = await this.$refs.basicForm.validate().catch(() => false)
if (!basicValid) {
this.$message.error('请完善基本信息')
return
}
// 验证科室分配
if (this.departmentAllocations.length === 0) {
this.$message.error('请至少选择一个执行科室')
return
}
// 验证金额分配
if (Math.abs(this.totalAllocated - this.packageData.allocated_amount) > 0.0001) {
this.$message.error('分配金额总和必须等于预算分配金额')
return
}
// 验证执行科室包名称和数据完整性
for (let i = 0; i < this.departmentAllocations.length; i++) {
const item = this.departmentAllocations[i]
// 验证科室ID
if (item.departmentId === null || item.departmentId === undefined || item.departmentId === '') {
this.$message.error(`${i+1}执行科室ID不能为空请重新选择科室`)
return
}
// 验证科室名称
if (!item.departmentName || !item.departmentName.trim()) {
this.$message.error(`${i+1}行:执行科室名称不能为空`)
return
}
// 验证包名称
if (!item.childPackageName || !item.childPackageName.trim()) {
this.$message.error(`请输入${item.departmentName}的执行科室包名称`)
return
}
// 验证分配金额
if (!item.allocatedAmount || item.allocatedAmount <= 0) {
this.$message.error(`请为${item.departmentName}输入有效的分配金额`)
return
}
// 验证金额格式
if (isNaN(parseFloat(item.allocatedAmount))) {
this.$message.error(`${item.departmentName}的分配金额格式不正确`)
return
}
}
// 验证科室是否重复
const departmentIds = this.departmentAllocations.map(item => item.departmentId)
const uniqueDepartmentIds = [...new Set(departmentIds)]
if (departmentIds.length !== uniqueDepartmentIds.length) {
this.$message.error('存在重复的执行科室,请检查')
return
}
this.submitting = true
try {
// 编辑模式下,需要提供原有子包信息用于后端差异对比
const originalChildren = this.packageData.isEditMode && this.packageData.children
? this.packageData.children.map(child => child.id)
: []
const splitData = {
package_id: this.packageData.id,
is_edit_mode: this.packageData.isEditMode || false,
original_child_package_ids: originalChildren, // 原有子包ID列表用于后端删除判断
budget_submission: {
application_basis: this.basicInfo.basis,
application_purpose: this.basicInfo.purpose,
application_reason: this.basicInfo.reason,
project_content: this.basicInfo.content
},
department_allocations: this.departmentAllocations.map(item => {
// 确保 department_id 不为空
if (item.departmentId === null || item.departmentId === undefined || item.departmentId === '') {
throw new Error(`科室 ${item.departmentName} 的department_id为空`)
}
const allocation = {
department_id: item.departmentId,
12 months ago
package_id: item.packageId,
8 months ago
allocated_amount: parseFloat(item.allocatedAmount),
package_name: item.childPackageName,
description: item.description || ''
}
// 编辑模式下查找对应的原有子包ID
if (this.packageData.isEditMode && this.packageData.children) {
const existingChild = this.packageData.children.find(
child => child.department_id === item.departmentId
)
if (existingChild) {
allocation.existing_package_id = existingChild.id // 用于更新现有子包
allocation.action = 'update' // 标记为更新操作
} else {
allocation.action = 'create' // 标记为新增操作
}
} else {
allocation.action = 'create' // 新建模式都是创建
}
return allocation
})
}
console.log('提交的分配数据:', splitData)
this.$emit('submit', splitData)
} catch (error) {
console.error('提交失败:', error)
// 根据错误类型显示不同的错误信息
if (error.message && error.message.includes('department_id为空')) {
this.$message.error(error.message)
} else if (error.response && error.response.data && error.response.data.message) {
this.$message.error(error.response.data.message)
} else if (error.message) {
this.$message.error(error.message)
} else {
this.$message.error('拆包操作失败,请检查数据完整性')
}
} finally {
this.submitting = false
}
},
handleCancel() {
this.$emit('cancel')
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.0000'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { minimumFractionDigits: 4, maximumFractionDigits: 4 })
: '0.0000'
}
}
}
</script>
<style scoped>
.split-package-form {
padding: 20px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #409EFF;
}
.section-title i {
margin-right: 8px;
color: #409EFF;
}
.amount-hint {
font-size: 12px;
color: #909399;
margin-top: 5px;
}
.department-allocation {
margin: 30px 0;
}
.add-department {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.allocation-summary {
margin-top: 20px;
padding: 15px;
background: #f5f7fa;
border-radius: 4px;
border-left: 4px solid #409EFF;
}
.summary-item {
margin-bottom: 8px;
font-weight: bold;
}
.summary-item:last-child {
margin-bottom: 0;
}
.summary-item.error {
color: #F56C6C;
}
.form-actions {
text-align: right;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #EBEEF5;
}
.form-actions .el-button {
margin-left: 10px;
}
</style>