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.

1982 lines
66 KiB

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="budget-submission-form">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-info"></i>
基本信息
</div>
<el-row :gutter="20">
<el-col :span="8">
<div class="info-display-item">
<label class="info-label">申请科室</label>
<div class="info-content">{{ formData.departmentName || '暂无' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="info-display-item">
<label class="info-label">预算年度</label>
<div class="info-content">{{ formData.budgetYear || '暂无' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="info-display-item">
<label class="info-label">分配金额</label>
<div class="info-content">{{ formData.allocatedAmount || '0.00' }} 万元</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-display-item">
<label class="info-label">子项名称</label>
<div class="info-content">{{ formData.subItemName || '暂无' }}</div>
</div>
</el-col>
<el-col :span="12">
<div class="info-display-item">
<label class="info-label">大项目名称</label>
<div class="info-content">{{ formData.projectName || '暂无' }}</div>
</div>
</el-col>
</el-row>
</div>
<!-- 申请信息只读展示 -->
<div v-if="showApplicationInfo" class="form-section">
<div class="section-title">
<i class="el-icon-document"></i>
申请信息
</div>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-display-item">
<label class="info-label">申请依据:</label>
<div class="info-content">{{ parentSubmissionInfo.application_basis || '暂无申请依据' }}</div>
</div>
</el-col>
<el-col :span="12">
<div class="info-display-item">
<label class="info-label">申请目的:</label>
<div class="info-content">{{ parentSubmissionInfo.application_purpose || '暂无申请目的' }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-display-item">
<label class="info-label">申请理由:</label>
<div class="info-content">{{ parentSubmissionInfo.application_reason || '暂无申请理由' }}</div>
</div>
</el-col>
<el-col :span="12">
<div class="info-display-item">
<label class="info-label">项目内容</label>
<div class="info-content">{{ parentSubmissionInfo.project_content || '暂无项目内容' }}</div>
</div>
</el-col>
</el-row>
</div>
<!-- 项目内容分解 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-s-grid"></i>
项目内容分解
<div v-if="!isViewMode" style="margin-left: 20px; display: flex; align-items: center;">
<el-checkbox
v-model="selectAll"
@change="onSelectAllChange"
style="margin-right: 10px;"
>
全选
</el-checkbox>
<span style="color: #666; font-size: 12px;">勾选需要填写的经济分类</span>
</div>
</div>
<div class="economic-table-container">
<el-table
:data="economicCategoriesWithData"
border
class="economic-table"
:summary-method="getSummaries"
show-summary
style="width: 100%;"
>
<el-table-column v-if="!isViewMode" label="选择" width="60" align="center">
<template slot-scope="scope">
<el-checkbox
v-model="scope.row.selected"
@change="onEconomicItemToggle(scope.row, scope.$index)"
/>
</template>
</el-table-column>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="经济分类" min-width="130">
<template slot-scope="scope">
<span>{{ scope.row.categoryName }}</span>
</template>
</el-table-column>
<el-table-column label="金额(万元)" width="150">
<template slot-scope="scope">
<el-input
v-if="!isViewMode && scope.row.selected"
v-model.number="scope.row.amount"
type="number"
:min="0"
:step="0.1"
placeholder="请输入金额"
@input="validateTotal"
/>
<span v-else-if="scope.row.selected">{{ formatCurrency(scope.row.amount) }}</span>
<span v-else class="disabled-text">-</span>
</template>
</el-table-column>
<el-table-column label="测算依据" min-width="300">
<template slot-scope="scope">
<el-input
v-if="!isViewMode && scope.row.selected"
v-model="scope.row.calculationBasis"
type="textarea"
:rows="2"
placeholder="请输入测算依据..."
maxlength="500"
/>
<div v-else-if="scope.row.selected" style="word-break: break-all;">{{ scope.row.calculationBasis || '-' }}</div>
<span v-else class="disabled-text">-</span>
</template>
</el-table-column>
<el-table-column label="附件上传" width="300" align="center">
<template slot-scope="scope">
<div v-if="!isViewMode && scope.row.selected">
<el-upload
:action="uploadAction"
:headers="uploadHeaders"
:data="{ categoryId: scope.row.categoryId }"
:file-list="scope.row.fileList || []"
:on-success="(response, file, fileList) => handleUploadSuccess(response, file, fileList, scope.row)"
:on-remove="(file, fileList) => handleUploadRemove(file, fileList, scope.row)"
:on-error="handleUploadError"
multiple
:limit="5"
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
>
<el-button size="mini" type="primary" icon="el-icon-upload">上传</el-button>
</el-upload>
</div>
<div v-else-if="scope.row.selected && scope.row.fileList && scope.row.fileList.length > 0">
<div v-for="file in scope.row.fileList" :key="file.uid" class="file-item">
<i :class="getFileIcon(file.name || file.original_name)"></i>
<el-link
type="primary"
@click="openFile(file)"
:underline="false"
class="file-link"
>
{{ file.name || file.original_name || '附件' }}
</el-link>
<el-button
type="text"
size="mini"
@click="downloadFile(file)"
class="download-btn"
>
下载
</el-button>
</div>
</div>
<span v-else class="disabled-text">-</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分配验证提示 -->
<div v-if="!isAmountValid" class="amount-warning">
<i class="el-icon-warning"></i>
<span>项目内容分解金额总计:{{ formatCurrency(totalEconomicAmount) }} 万元,</span>
<span :class="{ 'error': totalEconomicAmount !== allocatedAmountNum }">
{{ totalEconomicAmount > allocatedAmountNum ? '超出' : '不足' }}分配金额
{{ formatCurrency(Math.abs(totalEconomicAmount - allocatedAmountNum)) }} 万元
</span>
</div>
</div>
<!-- 申请资金表格 -->
<!-- 项目周期(一次性、阶段性、长期性) -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-time"></i>
项目周期(一次性、阶段性、长期性)
</div>
<el-form-item prop="implementationSchedule" class="no-label-margin">
<el-input
v-model="formData.implementationSchedule"
type="textarea"
:rows="4"
placeholder="请详细说明项目周期(一次性、阶段性、长期性)..."
:readonly="isViewMode"
maxlength="1000"
show-word-limit
/>
</el-form-item>
<!-- 项目周期(一次性、阶段性、长期性)详细表格 -->
<div class="implementation-schedule-table-container">
<el-table
:data="[formData.implementationScheduleDetail]"
border
class="implementation-schedule-table"
style="width: 100%;"
>
<!-- 当年实施进度安排 -->
<!-- 下一年实施进度安排 -->
<!-- 第三年实施进度安排 -->
</el-table>
</div>
<!-- 当年分月明细表格 -->
<!-- 当年月度金额验证提示 -->
<!-- 年度金额与项目内容分解合计验证提示 -->
</div>
<!-- 项目预计取得绩效 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-s-data"></i>
项目预计取得绩效
<el-button
v-if="!isViewMode"
type="primary"
size="mini"
@click="addPerformanceIndicator"
style="margin-left: 10px;"
>
添加指标
</el-button>
</div>
<div class="performance-table-container">
<el-table
:data="formData.performanceIndicators"
border
class="performance-table"
size="mini"
style="width: 100%;"
:row-class-name="getRowClassName"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="一级指标" width="100" min-width="100">
<template slot-scope="scope">
<el-select
v-if="!isViewMode && !scope.row.isRequired"
v-model="scope.row.level1"
placeholder="选择"
size="mini"
style="width: 100%;"
@change="onLevel1Change(scope.$index)"
>
<el-option
v-for="option in getFilteredLevel1Options()"
:key="option.id"
:label="option.name"
:value="option.name"
/>
</el-select>
<span v-else>{{ scope.row.level1 || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="二级指标" width="120" min-width="120">
<template slot-scope="scope">
<el-select
v-if="!isViewMode && !scope.row.isRequired"
v-model="scope.row.level2"
placeholder="选择"
size="mini"
style="width: 100%;"
@change="onLevel2Change(scope.$index)"
>
<el-option
v-for="option in getFilteredLevel2Options(scope.row.level1)"
:key="option.id"
:label="option.name"
:value="option.name"
/>
</el-select>
<span v-else>{{ scope.row.level2 || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="三级指标" min-width="200">
<template slot-scope="scope">
<div v-if="!isViewMode && !scope.row.isRequired">
<el-select
v-if="getFilteredLevel3Options(scope.row.level1, scope.row.level2).length > 0"
v-model="scope.row.level3"
placeholder="选择三级指标"
size="mini"
style="width: 100%;"
filterable
>
<el-option
v-for="option in getFilteredLevel3Options(scope.row.level1, scope.row.level2)"
:key="option.id"
:label="option.name"
:value="option.name"
/>
</el-select>
<el-input
v-else
v-model="scope.row.level3"
placeholder="请输入三级指标名称"
size="mini"
style="width: 100%;"
maxlength="100"
/>
</div>
<span v-else>{{ scope.row.level3 || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="计算符号" width="80" min-width="80">
<template slot-scope="scope">
<el-select
v-if="!isViewMode"
v-model="scope.row.symbol"
placeholder="符号"
size="mini"
style="width: 100%;"
>
<el-option label=">" value=">" />
<el-option label="=" value="=" />
<el-option label="≥" value="≥" />
<el-option label="<" value="<" />
<el-option label="≤" value="≤" />
<el-option label="定性" value="定性" />
</el-select>
<span v-else>{{ scope.row.symbol || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="半年指标值" width="130" min-width="130">
<template slot-scope="scope">
<el-input
v-if="!isViewMode"
v-model="scope.row.halfYearValue"
size="mini"
placeholder="指标值"
maxlength="50"
/>
<span v-else>{{ scope.row.halfYearValue || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="全年指标值" width="130" min-width="130">
<template slot-scope="scope">
<el-input
v-if="!isViewMode"
v-model="scope.row.fullYearValue"
size="mini"
placeholder="指标值"
maxlength="50"
/>
<span v-else>{{ scope.row.fullYearValue || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="计量单位" width="80" min-width="80">
<template slot-scope="scope">
<el-input
v-if="!isViewMode"
v-model="scope.row.unit"
size="mini"
placeholder="单位"
maxlength="20"
/>
<span v-else>{{ scope.row.unit || '-' }}</span>
</template>
</el-table-column>
<el-table-column v-if="!isViewMode" label="操作" width="60" align="center">
<template slot-scope="scope">
<el-button
v-if="!scope.row.isRequired"
type="danger"
size="mini"
@click="removePerformanceIndicator(scope.$index)"
icon="el-icon-delete"
/>
<el-tooltip v-else content="必填指标不可删除" placement="top">
<el-button
type="info"
size="mini"
icon="el-icon-lock"
disabled
/>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 其他需要说明事项 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-edit-outline"></i>
其他需要说明事项
</div>
<el-form-item class="no-label-margin">
<el-input
v-model="formData.otherNotes"
type="textarea"
:rows="3"
placeholder="请说明其他需要说明的事项..."
:readonly="isViewMode"
maxlength="500"
show-word-limit
/>
</el-form-item>
<div class="note-text">
<strong>备注:</strong>需同时提供申请依据和相关支撑材料。
</div>
</div>
<!-- 附件上传 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-folder-opened"></i>
附件上传
</div>
<el-form-item class="no-label-margin">
<!-- 编辑模式下的上传组件 -->
<el-upload
v-if="!isViewMode"
:action="uploadAction"
:headers="uploadHeaders"
:data="{ type: 'general' }"
:file-list="formData.generalFiles || []"
:on-success="(response, file, fileList) => handleGeneralUploadSuccess(response, file, fileList)"
:on-remove="(file, fileList) => handleGeneralUploadRemove(file, fileList)"
:on-error="handleUploadError"
multiple
:limit="10"
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.zip,.rar"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">支持 PDF、Word、Excel、图片、压缩包等格式单个文件不超过50MB</div>
</el-upload>
<!-- 查看模式下的文件列表 -->
<div v-else>
<div v-if="formData.generalFiles && formData.generalFiles.length > 0" class="general-files-list">
<div v-for="file in formData.generalFiles" :key="file.uid || file.id" class="general-file-item">
<i :class="getFileIcon(file.name || file.original_name)"></i>
<el-link
type="primary"
@click="openFile(file)"
:underline="false"
class="file-link"
>
{{ file.name || file.original_name || '附件' }}
</el-link>
<el-button
type="text"
size="mini"
@click="downloadFile(file)"
class="download-btn"
>
下载
</el-button>
</div>
</div>
<div v-else class="no-files-text">
<i class="el-icon-folder-opened"></i>
<span>暂无附件</span>
</div>
</div>
</el-form-item>
</div>
</el-form>
<!-- 操作按钮 -->
<div class="form-actions">
<el-button @click="cancel">{{ isViewMode ? '' : '' }}</el-button>
<el-button
v-if="!isViewMode"
type="primary"
@click="submit"
:disabled="!canSubmit"
>
{{ mode === 'create' ? '提交' : '保存修改' }}
</el-button>
</div>
</div>
</template>
<script>
import {
getEconomicCategories,
getPackageDecomposition,
getPackageProjectNames,
getPerformanceIndicatorTree,
getRequiredPerformanceIndicators
} from '@/api/budget/departmentSubmission.js'
export default {
name: 'BudgetSubmissionForm',
props: {
packageData: {
type: Object,
required: true
},
mode: {
type: String,
default: 'create' // create, edit, view
},
showApplicationInfo: {
type: Boolean,
default: true // 默认显示申请信息在抽屉中可以设置为false
}
},
data() {
return {
formData: {
packageId: '',
departmentName: '',
budgetYear: '',
allocatedAmount: '',
subItemName: '',
projectName: '',
economicItems: [],
implementationSchedule: '',
implementationScheduleDetail: {
currentYearContent: '',
currentYearAmount: 0,
currentYearMonth1: 0,
currentYearMonth2: 0,
currentYearMonth3: 0,
currentYearMonth4: 0,
currentYearMonth5: 0,
currentYearMonth6: 0,
currentYearMonth7: 0,
currentYearMonth8: 0,
currentYearMonth9: 0,
currentYearMonth10: 0,
currentYearMonth11: 0,
currentYearMonth12: 0,
nextYearContent: '',
thirdYearContent: ''
},
performanceIndicators: [],
otherNotes: '',
generalFiles: []
},
fundApplicationData: [{
newProject: {
totalAmount: 0,
current: 0,
next: 0,
hasFixedAssets: false,
noFixedAssets: false
},
oldProject: {
contractTotal: 0,
previousYearsTotal: 0,
previousYearBalance: 0,
contractNumber: '',
current: 0,
hasFixedAssets: false,
noFixedAssets: false
}
}],
parentSubmissionInfo: {
application_basis: '',
application_purpose: '',
application_reason: '',
project_content: ''
},
economicCategories: [],
economicCategoriesWithData: [],
selectAll: false,
// 绩效指标相关数据
performanceIndicatorTree: [],
level1Options: [],
level2OptionsMap: {},
level3OptionsMap: {},
requiredIndicators: [],
formRules: {
implementationSchedule: [
{ required: true, message: '请输入项目周期(一次性、阶段性、长期性)', trigger: 'blur' }
]
},
uploadAction: process.env.VUE_APP_UPLOAD_API || '/api/upload', // 上传接口地址
uploadHeaders: {
'Authorization': 'Bearer ' + (this.$store.state.user.token || localStorage.getItem('token') || '')
},
}
},
computed: {
isViewMode() {
return this.mode === 'view'
},
allocatedAmountNum() {
return parseFloat(this.formData.allocatedAmount) || 0
},
totalEconomicAmount() {
return this.economicCategoriesWithData.reduce((total, item) => {
if (item.selected) {
return total + (parseFloat(item.amount) || 0)
}
return total
}, 0)
},
isAmountValid() {
// 第一阶段不限制总计分解金额与分配金额的对等
return true
},
newProjectTotal() {
return parseFloat(this.fundApplicationData[0].newProject.totalAmount) || 0
},
newProjectYearlyTotal() {
return parseFloat(this.fundApplicationData[0].newProject.current) + parseFloat(this.fundApplicationData[0].newProject.next)
},
isNewProjectAmountValid() {
return Math.abs(this.newProjectYearlyTotal - this.newProjectTotal) < 0.01
},
nextYearLabel() {
// 预算年度如"2025年",则 nextYearLabel 为"2026年"
if (this.formData.budgetYear && /^\d{4}年$/.test(this.formData.budgetYear)) {
const year = parseInt(this.formData.budgetYear)
if (!isNaN(year)) {
return (year + 1) + '年'
}
}
return '次年'
},
thirdYearLabel() {
// 预算年度如"2025年",则 thirdYearLabel 为"2027年"
if (this.formData.budgetYear && /^\d{4}年$/.test(this.formData.budgetYear)) {
const year = parseInt(this.formData.budgetYear)
if (!isNaN(year)) {
return (year + 2) + '年'
}
}
return '第三年'
},
currentYearMonthlyTotal() {
return this.formData.implementationScheduleDetail.currentYearMonth1 +
this.formData.implementationScheduleDetail.currentYearMonth2 +
this.formData.implementationScheduleDetail.currentYearMonth3 +
this.formData.implementationScheduleDetail.currentYearMonth4 +
this.formData.implementationScheduleDetail.currentYearMonth5 +
this.formData.implementationScheduleDetail.currentYearMonth6 +
this.formData.implementationScheduleDetail.currentYearMonth7 +
this.formData.implementationScheduleDetail.currentYearMonth8 +
this.formData.implementationScheduleDetail.currentYearMonth9 +
this.formData.implementationScheduleDetail.currentYearMonth10 +
this.formData.implementationScheduleDetail.currentYearMonth11 +
this.formData.implementationScheduleDetail.currentYearMonth12
},
isCurrentYearAmountValid() {
// 如果当年金额为0则不进行校验允许用户先填写月度金额
if (this.formData.implementationScheduleDetail.currentYearAmount === 0) {
return true
}
// 如果月度金额总计为0则不进行校验允许用户先填写当年金额
if (this.currentYearMonthlyTotal === 0) {
return true
}
// 进行精度校验允许0.01万元的误差
return Math.abs(this.currentYearMonthlyTotal - this.formData.implementationScheduleDetail.currentYearAmount) < 0.01
},
// 年度金额与项目内容分解合计是否一致
isYearAmountEqualToEconomicTotal() {
const currentYearAmount = parseFloat(this.formData.implementationScheduleDetail.currentYearAmount) || 0
const economicTotal = this.totalEconomicAmount
// 如果其中一个为0则不进行校验允许用户先填写一部分
if (currentYearAmount === 0 || economicTotal === 0) {
return true
}
// 进行精度校验允许0.01万元的误差
return Math.abs(currentYearAmount - economicTotal) < 0.01
},
canSubmit() {
// 检查是否有选中的经济分类项目
const selectedItems = this.economicCategoriesWithData.filter(item => item.selected)
if (selectedItems.length === 0) return false
// 检查项目周期(一次性、阶段性、长期性)是否填写
if (!this.formData.implementationSchedule || this.formData.implementationSchedule.trim() === '') return false
// 检查选中的经济分类项目是否都有有效的金额和测算依据
const hasValidEconomicItems = selectedItems.every(item => {
const amount = parseFloat(item.amount)
return !isNaN(amount) &&
amount >= 0 &&
item.calculationBasis &&
item.calculationBasis.trim() !== ''
})
// 检查金额分配是否正确
return hasValidEconomicItems && this.isAmountValid
}
},
async created() {
await this.initFormData()
await this.loadEconomicCategories()
await this.loadPerformanceIndicators()
if (this.mode === 'edit' || this.mode === 'view') {
await this.loadExistingData()
}
// 初始化选择状态
this.updateSelectAllState()
},
methods: {
async initFormData() {
this.formData.packageId = this.packageData.id
this.formData.departmentName = this.packageData.department ? this.packageData.department.name : ''
this.formData.budgetYear = this.packageData.budget_year ? this.packageData.budget_year.year + '年' : ''
this.formData.allocatedAmount = this.formatCurrency(this.packageData.allocated_amount)
// 从后端获取项目名称信息
try {
const response = await getPackageProjectNames(this.packageData.id)
if (response && !response.errcode) {
this.formData.subItemName = response.sub_item_name
this.formData.projectName = response.project_name
} else if (response && response.errcode) {
// 静默处理,不显示错误提示,允许用户继续操作
console.warn('获取项目名称信息失败:', response.errmsg)
}
} catch (error) {
// 静默处理HTTP错误不显示错误提示
console.warn('获取项目名称失败:', error.message)
}
// 加载package的submission信息
if (this.packageData.level == 1 || this.packageData.level == 2) {
this.parentSubmissionInfo = {
application_basis: (this.packageData.application_basis) || '',
application_purpose: (this.packageData.application_purpose) || '',
application_reason: (this.packageData.application_reason) || '',
project_content: (this.packageData.project_content) || ''
}
} else if (this.packageData.level == 3) {
this.parentSubmissionInfo = {
application_basis: (this.packageData.parent.submission && this.packageData.parent.submission.application_basis) || '',
application_purpose: (this.packageData.parent.submission && this.packageData.parent.submission.application_purpose) || '',
application_reason: (this.packageData.parent.submission && this.packageData.parent.submission.application_reason) || '',
project_content: (this.packageData.parent.submission && this.packageData.parent.submission.project_content) || ''
}
}
// 调试信息
console.log('packageData:', this.packageData)
console.log('parentSubmissionInfo:', this.parentSubmissionInfo)
},
async loadEconomicCategories() {
try {
const response = await getEconomicCategories()
if (response && !response.errcode) {
this.economicCategories = response || []
this.initEconomicCategoriesWithData()
} else if (response && response.errcode) {
// 经济分类是必需的,但提供更友好的错误提示
if (response.errmsg.includes('权限')) {
this.$message.warning('暂无权限获取经济分类数据,请联系管理员或稍后重试')
} else {
this.$message.error(response.errmsg || '加载经济分类失败')
}
}
} catch (error) {
console.error('加载经济分类失败:', error)
this.$message.warning('网络异常,无法加载经济分类数据,请检查网络连接或稍后重试')
}
},
initEconomicCategoriesWithData() {
this.economicCategoriesWithData = this.economicCategories.map(category => ({
categoryId: category.id,
categoryName: category.name,
selected: false,
amount: '',
calculationBasis: '',
fileList: [] // 添加文件列表字段
}))
},
async loadExistingData() {
try {
const response = await getPackageDecomposition(this.packageData.id)
if (response && !response.errcode) {
const data = response
// 填充表单数据 - 注意subItemName 和 projectName 不应该从这里获取,而应该从 initFormData 中的 getPackageProjectNames 获取
this.formData = {
...this.formData,
implementationSchedule: data.implementation_schedule || '',
implementationScheduleDetail: {
currentYearContent: data.implementation_current_year_content || '',
currentYearAmount: parseFloat(data.implementation_current_year_amount) || 0,
currentYearMonth1: parseFloat(data.implementation_current_year_month_1) || 0,
currentYearMonth2: parseFloat(data.implementation_current_year_month_2) || 0,
currentYearMonth3: parseFloat(data.implementation_current_year_month_3) || 0,
currentYearMonth4: parseFloat(data.implementation_current_year_month_4) || 0,
currentYearMonth5: parseFloat(data.implementation_current_year_month_5) || 0,
currentYearMonth6: parseFloat(data.implementation_current_year_month_6) || 0,
currentYearMonth7: parseFloat(data.implementation_current_year_month_7) || 0,
currentYearMonth8: parseFloat(data.implementation_current_year_month_8) || 0,
currentYearMonth9: parseFloat(data.implementation_current_year_month_9) || 0,
currentYearMonth10: parseFloat(data.implementation_current_year_month_10) || 0,
currentYearMonth11: parseFloat(data.implementation_current_year_month_11) || 0,
currentYearMonth12: parseFloat(data.implementation_current_year_month_12) || 0,
nextYearContent: data.implementation_next_year_content || '',
thirdYearContent: data.implementation_third_year_content || ''
},
performanceIndicators: this.processPerformanceIndicatorsData(data.performance_indicators || []),
otherNotes: data.other_notes || '',
generalFiles: data.general_files || [] // 填充附件列表
}
// 数据加载完成后,检查并修复当年金额与月度金额的一致性
this.$nextTick(() => {
this.checkAndFixCurrentYearAmount()
})
// 填充申请资金表格数据
if (data.new_project_total_amount !== undefined) {
this.fundApplicationData[0].newProject.totalAmount = data.new_project_total_amount || 0
this.fundApplicationData[0].newProject.current = data.new_project_year_current || 0
this.fundApplicationData[0].newProject.next = data.new_project_year_next || 0
this.fundApplicationData[0].newProject.hasFixedAssets = data.new_project_has_fixed_assets || false
this.fundApplicationData[0].newProject.noFixedAssets = !data.new_project_has_fixed_assets
this.fundApplicationData[0].oldProject.contractTotal = data.old_project_contract_total || 0
this.fundApplicationData[0].oldProject.previousYearsTotal = data.old_project_previous_years_total || 0
this.fundApplicationData[0].oldProject.previousYearBalance = data.old_project_previous_year_balance || 0
this.fundApplicationData[0].oldProject.contractNumber = data.old_project_contract_number || ''
this.fundApplicationData[0].oldProject.current = data.old_project_year_current || 0
this.fundApplicationData[0].oldProject.hasFixedAssets = data.old_project_has_fixed_assets || false
this.fundApplicationData[0].oldProject.noFixedAssets = !data.old_project_has_fixed_assets
}
// 填充经济分类数据
if (data.economic_items && data.economic_items.length > 0) {
data.economic_items.forEach(item => {
const category = this.economicCategoriesWithData.find(cat => cat.categoryId === item.categoryId)
if (category) {
category.selected = true
category.amount = item.amount
category.calculationBasis = item.calculationBasis
// 加载附件信息
if (item.files && item.files.length > 0) {
category.fileList = item.files.map(file => ({
uid: file.id,
name: file.original_name || file.name,
url: file.url,
fileId: file.id
}))
}
}
})
}
// 加载通用附件信息
if (data.general_files && data.general_files.length > 0) {
this.formData.generalFiles = data.general_files.map(file => ({
uid: file.id,
name: file.original_name || file.name,
url: file.url,
fileId: file.id
}))
}
} else if (response && response.errcode) {
// 静默处理,只在控制台记录
console.warn('加载分解表数据失败:', response.errmsg)
// 只有在编辑或查看模式下且不是权限问题时才显示错误
if (this.mode !== 'create' && !response.errmsg.includes('权限')) {
this.$message.warning('无法加载已有的分解表数据,将使用空白表单')
}
}
// 加载完现有数据后,处理必填指标
if (this.requiredIndicators.length > 0) {
this.addRequiredIndicators()
}
} catch (error) {
console.warn('加载分解表数据失败:', error.message)
// 静默处理HTTP错误不显示任何提示
// 即使加载失败,也要处理必填指标
if (this.requiredIndicators.length > 0) {
this.addRequiredIndicators()
}
}
},
// 处理绩效指标数据,为已有数据添加必要的标识
processPerformanceIndicatorsData(performanceIndicators) {
return performanceIndicators.map(item => ({
...item,
isRequired: false, // 先设置为非必填,后续会根据实际情况更新
indicatorId: item.indicator_id || null
}))
},
onSelectAllChange(value) {
this.economicCategoriesWithData.forEach(category => {
category.selected = value
})
},
onEconomicItemToggle(item, index) {
// 清空数据当取消选择时
if (!item.selected) {
item.amount = ''
item.calculationBasis = ''
}
// 更新全选状态
this.updateSelectAllState()
},
updateSelectAllState() {
const selectedCount = this.economicCategoriesWithData.filter(item => item.selected).length
this.selectAll = selectedCount === this.economicCategoriesWithData.length && selectedCount > 0
},
// 申请资金表格相关方法
calculateNewProjectTotal() {
// 新项目总额 = 当年 + 次年
const current = parseFloat(this.fundApplicationData[0].newProject.current) || 0
const next = parseFloat(this.fundApplicationData[0].newProject.next) || 0
this.fundApplicationData[0].newProject.totalAmount = current + next
},
validateNewProjectAmounts() {
this.calculateNewProjectTotal()
},
onNewProjectFixedAssetsChange() {
// 确保只能选择一个选项
if (this.fundApplicationData[0].newProject.hasFixedAssets) {
this.fundApplicationData[0].newProject.noFixedAssets = false
}
if (this.fundApplicationData[0].newProject.noFixedAssets) {
this.fundApplicationData[0].newProject.hasFixedAssets = false
}
},
onOldProjectFixedAssetsChange() {
// 确保只能选择一个选项
if (this.fundApplicationData[0].oldProject.hasFixedAssets) {
this.fundApplicationData[0].oldProject.noFixedAssets = false
}
if (this.fundApplicationData[0].oldProject.noFixedAssets) {
this.fundApplicationData[0].oldProject.hasFixedAssets = false
}
},
// 项目周期(一次性、阶段性、长期性)相关方法
calculateCurrentYearTotal() {
// 当年金额 = 1月到12月的总和
this.formData.implementationScheduleDetail.currentYearAmount = this.currentYearMonthlyTotal
},
/**
* 检查并修复当年金额与月度金额的一致性
* 在修改模式下如果当年金额为0但月度金额有值则自动计算当年金额
*/
checkAndFixCurrentYearAmount() {
const monthlyTotal = this.currentYearMonthlyTotal
const currentYearAmount = this.formData.implementationScheduleDetail.currentYearAmount
// 如果当年金额为0但月度金额有值则自动设置当年金额
if (currentYearAmount === 0 && monthlyTotal > 0) {
this.formData.implementationScheduleDetail.currentYearAmount = monthlyTotal
console.log('自动修复当年金额:', monthlyTotal)
}
// 如果当年金额与月度金额不一致,给出提示但不强制修复
else if (Math.abs(monthlyTotal - currentYearAmount) > 0.01) {
console.warn('当年金额与月度金额不一致:', {
currentYearAmount,
monthlyTotal,
difference: Math.abs(monthlyTotal - currentYearAmount)
})
}
},
/**
* 当年金额变化时的处理
*/
onCurrentYearAmountChange() {
// 当年金额变化时,不自动修改月度金额,让用户手动调整
// 这样可以避免覆盖用户已填写的数据
console.log('当年金额已更新:', this.formData.implementationScheduleDetail.currentYearAmount)
},
// 加载绩效指标数据
async loadPerformanceIndicators() {
try {
console.log('开始加载绩效指标数据...')
// 获取完整的指标树
const treeResponse = await getPerformanceIndicatorTree()
console.log('指标树响应:', treeResponse)
if (treeResponse) {
this.performanceIndicatorTree = treeResponse || []
this.processIndicatorTree(treeResponse || [])
console.log('指标树处理完成:', this.level1Options.length, '个一级指标')
} else {
console.error('获取指标树失败:', treeResponse)
this.performanceIndicatorTree = []
}
// 获取必须填报的指标
const requiredResponse = await getRequiredPerformanceIndicators()
console.log('必填指标响应:', requiredResponse)
if (requiredResponse) {
this.requiredIndicators = requiredResponse || []
this.initRequiredIndicators()
console.log('必填指标处理完成:', this.requiredIndicators.length, '个必填指标')
} else {
console.error('获取必填指标失败:', requiredResponse)
this.requiredIndicators = [] // 确保即使失败也有默认值
}
} catch (error) {
console.error('加载绩效指标数据失败:', error)
// 确保异常情况下也有默认值
if (!Array.isArray(this.requiredIndicators)) {
this.requiredIndicators = []
}
if (!Array.isArray(this.performanceIndicatorTree)) {
this.performanceIndicatorTree = []
}
}
},
// 处理指标树数据,构建选项映射
processIndicatorTree(tree) {
if (!Array.isArray(tree)) {
console.error('指标树数据格式错误:', tree)
return
}
this.level1Options = tree.map(item => ({
id: item.id,
name: item.name,
required: item.required || false
}))
console.log('一级指标选项:', this.level1Options)
// 构建二级和三级选项映射
tree.forEach(level1 => {
if (level1.children && Array.isArray(level1.children)) {
this.level2OptionsMap[level1.id] = level1.children.map(item => ({
id: item.id,
name: item.name,
required: item.required || false
}))
level1.children.forEach(level2 => {
if (level2.children && Array.isArray(level2.children)) {
this.level3OptionsMap[level2.id] = level2.children.map(item => ({
id: item.id,
name: item.name,
required: item.required || false
}))
}
})
}
})
console.log('二级选项映射:', this.level2OptionsMap)
console.log('三级选项映射:', this.level3OptionsMap)
},
// 初始化必须填报的指标
initRequiredIndicators() {
// 在编辑模式下先不初始化必填指标等待loadExistingData完成后再处理
if (this.mode === 'edit' || this.mode === 'view') {
return
}
this.addRequiredIndicators()
},
// 添加必须填报的指标
addRequiredIndicators() {
console.log('开始处理必填指标,总数:', this.requiredIndicators.length)
if (!Array.isArray(this.requiredIndicators) || this.requiredIndicators.length === 0) {
console.log('没有必填指标需要处理')
return
}
// 获取三级必填指标
const requiredLevel3Indicators = this.requiredIndicators.filter(indicator => {
return indicator.required && this.isLevel3Indicator(indicator)
})
console.log('找到的三级必填指标:', requiredLevel3Indicators)
requiredLevel3Indicators.forEach(indicator => {
const level1 = this.findLevel1ByIndicatorId(indicator.id)
const level2 = this.findLevel2ByIndicatorId(indicator.id)
console.log(`指标 ${indicator.name} 的层级信息:`, { level1: level1?.name, level2: level2?.name })
if (level1 && level2) {
// 检查是否已存在相同的指标通过指标ID或者三个层级名称匹配
const existingIndex = this.formData.performanceIndicators.findIndex(item =>
item.indicatorId === indicator.id ||
(item.level1 === level1.name && item.level2 === level2.name && item.level3 === indicator.name)
)
if (existingIndex === -1) {
// 不存在,添加到最前面
const newIndicator = {
level1: level1.name,
level2: level2.name,
level3: indicator.name,
symbol: '',
halfYearValue: '',
fullYearValue: '',
unit: '',
isRequired: true,
indicatorId: indicator.id
}
this.formData.performanceIndicators.unshift(newIndicator)
console.log('添加新的必填指标:', newIndicator)
} else {
// 已存在,标记为必填并移到前面
const existingItem = this.formData.performanceIndicators[existingIndex]
existingItem.isRequired = true
existingItem.indicatorId = indicator.id
// 移动到最前面
this.formData.performanceIndicators.splice(existingIndex, 1)
this.formData.performanceIndicators.unshift(existingItem)
console.log('更新现有指标为必填:', existingItem)
}
}
})
console.log('必填指标处理完成,当前指标总数:', this.formData.performanceIndicators.length)
},
// 判断是否为三级指标
isLevel3Indicator(indicator) {
if (!indicator || !indicator.id) {
return false
}
// 通过在performanceIndicatorTree中查找该指标来判断层级
if (!Array.isArray(this.performanceIndicatorTree)) {
return false
}
for (const level1 of this.performanceIndicatorTree) {
if (level1.children && Array.isArray(level1.children)) {
for (const level2 of level1.children) {
if (level2.children && Array.isArray(level2.children)) {
for (const level3 of level2.children) {
if (level3.id === indicator.id) {
return true
}
}
}
}
}
}
return false
},
// 根据指标ID查找一级指标
findLevel1ByIndicatorId(indicatorId) {
if (!Array.isArray(this.performanceIndicatorTree) || !indicatorId) {
return null
}
for (const level1 of this.performanceIndicatorTree) {
if (level1.children && Array.isArray(level1.children)) {
for (const level2 of level1.children) {
if (level2.children && Array.isArray(level2.children)) {
for (const level3 of level2.children) {
if (level3.id === indicatorId) {
return level1
}
}
}
}
}
}
return null
},
// 根据指标ID查找二级指标
findLevel2ByIndicatorId(indicatorId) {
if (!Array.isArray(this.performanceIndicatorTree) || !indicatorId) {
return null
}
for (const level1 of this.performanceIndicatorTree) {
if (level1.children && Array.isArray(level1.children)) {
for (const level2 of level1.children) {
if (level2.children && Array.isArray(level2.children)) {
for (const level3 of level2.children) {
if (level3.id === indicatorId) {
return level2
}
}
}
}
}
}
return null
},
// 获取过滤后的一级选项(排除已被必填指标占用的)
getFilteredLevel1Options() {
if (!Array.isArray(this.level1Options)) {
return []
}
// 获取所有必填指标的一级指标名称
const usedLevel1Names = this.formData.performanceIndicators
.filter(item => item.isRequired && item.level1)
.map(item => item.level1)
return this.level1Options.filter(option => !usedLevel1Names.includes(option.name))
},
// 获取过滤后的二级选项
getFilteredLevel2Options(level1Name) {
if (!level1Name || !Array.isArray(this.level1Options)) {
return []
}
const level1 = this.level1Options.find(item => item.name === level1Name)
if (!level1 || !this.level2OptionsMap[level1.id]) {
return []
}
// 获取所有必填指标中该一级指标下已使用的二级指标名称
const usedLevel2Names = this.formData.performanceIndicators
.filter(item => item.isRequired && item.level1 === level1Name && item.level2)
.map(item => item.level2)
return this.level2OptionsMap[level1.id].filter(option => !usedLevel2Names.includes(option.name))
},
// 获取过滤后的三级选项
getFilteredLevel3Options(level1Name, level2Name) {
if (!level1Name || !level2Name || !Array.isArray(this.level1Options)) {
return []
}
const level1 = this.level1Options.find(item => item.name === level1Name)
if (!level1 || !this.level2OptionsMap[level1.id]) {
return []
}
const level2Options = this.level2OptionsMap[level1.id]
const level2 = level2Options.find(item => item.name === level2Name)
if (!level2 || !this.level3OptionsMap[level2.id]) {
return []
}
// 获取所有必填指标中该二级指标下已使用的三级指标名称
const usedLevel3Names = this.formData.performanceIndicators
.filter(item => item.isRequired && item.level1 === level1Name && item.level2 === level2Name && item.level3)
.map(item => item.level3)
return this.level3OptionsMap[level2.id].filter(option => !usedLevel3Names.includes(option.name))
},
// 获取二级选项(保留原方法以兼容性)
getLevel2Options(level1Name) {
return this.getFilteredLevel2Options(level1Name)
},
// 获取三级选项(保留原方法以兼容性)
getLevel3Options(level1Name, level2Name) {
return this.getFilteredLevel3Options(level1Name, level2Name)
},
// 一级指标变化时清空下级选项
onLevel1Change(index) {
const indicator = this.formData.performanceIndicators[index]
indicator.level2 = ''
indicator.level3 = ''
// 强制刷新组件以更新下拉选项
this.$forceUpdate()
},
// 二级指标变化时清空三级选项
onLevel2Change(index) {
const indicator = this.formData.performanceIndicators[index]
indicator.level3 = ''
// 强制刷新组件以更新三级选项
this.$forceUpdate()
},
// 获取表格行的样式类名
getRowClassName({row, rowIndex}) {
return row.isRequired ? 'required-row' : ''
},
addPerformanceIndicator() {
this.formData.performanceIndicators.push({
level1: '',
level2: '',
level3: '',
symbol: '',
halfYearValue: '',
fullYearValue: '',
unit: '',
isRequired: false,
indicatorId: null
})
},
removePerformanceIndicator(index) {
// 如果是必填指标,不允许删除
const indicator = this.formData.performanceIndicators[index]
if (indicator.isRequired) {
this.$message.warning('必填指标不可删除')
return
}
this.formData.performanceIndicators.splice(index, 1)
},
validateTotal() {
// 触发计算属性重新计算
this.$forceUpdate()
},
getSummaries(param) {
const { columns, data } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0 || (index === 1 && !this.isViewMode)) {
sums[index] = '合计'
return
}
// 金额列的索引(考虑是否有选择列)
const amountColumnIndex = this.isViewMode ? 2 : 3
if (index === amountColumnIndex) {
const values = data.filter(item => item.selected).map(item => Number(item.amount) || 0)
const total = values.reduce((prev, curr) => {
const value = Number(curr)
if (!isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
sums[index] = this.formatCurrency(total)
} else {
sums[index] = ''
}
})
return sums
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num) ? num.toFixed(1) : '0.0'
},
submit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
// 获取选中的经济分类项目
const selectedItems = this.economicCategoriesWithData.filter(item => item.selected)
// 1. 验证经济分类项目选择
if (selectedItems.length === 0) {
this.$message.error('请至少选择一个经济分类项目')
return
}
// 2. 验证经济分类项目完整性
const invalidItems = selectedItems.filter(item => {
const amount = parseFloat(item.amount)
return item.amount === '' ||
item.amount === null ||
item.amount === undefined ||
isNaN(amount) ||
amount < 0 ||
!item.calculationBasis ||
item.calculationBasis.trim() === ''
})
if (invalidItems.length > 0) {
this.$message.error('请完善所有选中经济分类项目的信息金额必须为有效数字且不能小于0测算依据不能为空')
return
}
// 3. 第一阶段不限制金额分配验证
// 金额分配验证已移除,允许用户自由填写
// 4. 验证绩效指标完整性
const invalidPerformanceItems = this.formData.performanceIndicators.filter(item =>
!item.level1 ||
!item.level2 ||
!item.level3 ||
!item.symbol ||
(!item.halfYearValue && !item.fullYearValue)
)
if (invalidPerformanceItems.length > 0) {
this.$message.error('请完善所有绩效指标的信息(一级指标、二级指标、三级指标、计算符号必填,半年或全年指标值至少填写一个)')
return
}
// 5. 验证项目周期(一次性、阶段性、长期性)字数
if (this.formData.implementationSchedule && this.formData.implementationSchedule.length > 1000) {
this.$message.error('项目周期一次性、阶段性、长期性不能超过1000个字符')
return
}
// 6. 验证其他说明事项字数
if (this.formData.otherNotes && this.formData.otherNotes.length > 500) {
this.$message.error('其他需要说明事项不能超过500个字符')
return
}
// 构造提交数据 - 只提交必要字段
const submitData = {
packageId: this.formData.packageId,
implementationSchedule: this.formData.implementationSchedule.trim(),
implementationScheduleDetail: this.formData.implementationScheduleDetail,
otherNotes: this.formData.otherNotes ? this.formData.otherNotes.trim() : '',
economicItems: selectedItems.map(item => ({
categoryId: item.categoryId,
amount: parseFloat(item.amount),
calculationBasis: item.calculationBasis.trim(),
fileIds: item.fileList ? item.fileList.map(f => f.fileId).filter(Boolean) : [] // 提交文件ID列表
})),
performanceIndicators: this.formData.performanceIndicators.map(item => ({
level1: item.level1,
level2: item.level2,
level3: item.level3.trim(),
symbol: item.symbol,
halfYearValue: item.halfYearValue ? item.halfYearValue.trim() : '',
fullYearValue: item.fullYearValue ? item.fullYearValue.trim() : '',
unit: item.unit ? item.unit.trim() : ''
})),
// 修复字段映射问题 - 将前端字段映射为后端期望的字段名
fundApplication: {
newProject: {
totalAmount: this.fundApplicationData[0].newProject.totalAmount,
yearCurrent: this.fundApplicationData[0].newProject.current, // current -> yearCurrent
yearNext: this.fundApplicationData[0].newProject.next, // next -> yearNext
hasFixedAssets: this.fundApplicationData[0].newProject.hasFixedAssets
},
oldProject: {
contractTotal: this.fundApplicationData[0].oldProject.contractTotal,
previousYearsTotal: this.fundApplicationData[0].oldProject.previousYearsTotal,
previousYearBalance: this.fundApplicationData[0].oldProject.previousYearBalance,
contractNumber: this.fundApplicationData[0].oldProject.contractNumber,
yearCurrent: this.fundApplicationData[0].oldProject.current, // current -> yearCurrent
hasFixedAssets: this.fundApplicationData[0].oldProject.hasFixedAssets
}
},
generalFileIds: this.formData.generalFiles ? this.formData.generalFiles.map(f => f.fileId).filter(Boolean) : [] // 提交通用附件ID列表
}
this.$emit('submit', submitData)
} else {
this.$message.error('请完善必填的表单信息')
}
})
},
cancel() {
this.$emit('cancel')
},
// 附件上传相关方法
handleUploadSuccess(response, file, fileList, row) {
// response 本身就是处理后的数据包含文件ID
console.log('response', response)
if (response && response.data) {
// 保存文件信息到行数据
row.fileList = fileList.map(f => ({
...f,
fileId: f.response.data.id
}))
this.$message.success('附件上传成功')
} else {
this.$message.error('附件上传失败')
}
},
handleUploadRemove(file, fileList, row) {
// 从文件列表中移除
const index = fileList.findIndex(f => f.uid === file.uid)
if (index !== -1) {
fileList.splice(index, 1)
row.fileList = fileList
}
},
handleGeneralUploadSuccess(response, file, fileList) {
// response 本身就是处理后的数据包含文件ID
if (response && response.data) {
// 保存文件信息
this.formData.generalFiles = fileList.map(f => ({
...f,
fileId: f.response.data.id
}))
this.$message.success('附件上传成功')
} else {
this.$message.error('附件上传失败')
}
},
handleGeneralUploadRemove(file, fileList) {
// 从文件列表中移除
const index = fileList.findIndex(f => f.uid === file.uid)
if (index !== -1) {
fileList.splice(index, 1)
this.formData.generalFiles = fileList
}
},
handleUploadError(err, file, fileList) {
console.error('附件上传失败:', err)
this.$message.error('附件上传失败,请检查网络或文件大小')
},
// 打开文件方法
openFile(fileInfo) {
if (!fileInfo.url) {
this.$message.warning('文件链接不存在,无法打开')
return
}
try {
window.open(fileInfo.url, '_blank')
} catch (error) {
console.error('打开文件失败:', error)
this.$message.error('打开文件失败')
}
},
// 文件下载方法
downloadFile(fileInfo) {
if (!fileInfo.url) {
this.$message.warning('文件链接不存在,无法下载')
return
}
try {
const fileName = fileInfo.name || fileInfo.original_name || '附件'
const a = document.createElement('a')
a.setAttribute('href', fileInfo.url)
a.setAttribute('download', fileName)
a.setAttribute('target', '_blank')
a.click()
} catch (error) {
console.error('下载文件失败:', error)
this.$message.error('下载文件失败')
}
},
// 根据文件名获取文件图标
getFileIcon(fileName) {
if (!fileName) return 'el-icon-document'
const extension = fileName.split('.').pop().toLowerCase()
const iconMap = {
pdf: 'el-icon-document',
doc: 'el-icon-document-copy',
docx: 'el-icon-document-copy',
xls: 'el-icon-s-grid',
xlsx: 'el-icon-s-grid',
jpg: 'el-icon-picture',
jpeg: 'el-icon-picture',
png: 'el-icon-picture',
gif: 'el-icon-picture',
zip: 'el-icon-folder-opened',
rar: 'el-icon-folder-opened',
txt: 'el-icon-document',
default: 'el-icon-document'
}
return iconMap[extension] || iconMap.default
},
}
}
</script>
<style scoped>
.budget-submission-form {
padding: 20px;
max-height: 75vh;
overflow-y: auto;
}
.form-section {
margin-bottom: 30px;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 20px;
}
.section-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #303133;
margin-bottom: 20px;
padding-bottom: 8px;
border-bottom: 2px solid #409EFF;
}
.section-title i {
margin-right: 8px;
color: #409EFF;
}
/* 申请信息只读展示样式 */
.info-display-item {
margin-bottom: 16px;
}
.info-label {
font-weight: bold;
color: #606266;
margin-bottom: 8px;
display: inline-block;
}
.info-content {
color: #303133;
line-height: 1.6;
padding: 8px 12px;
background-color: #f5f7fa;
border-radius: 4px;
min-height: 20px;
}
.economic-table-container,
.performance-table-container {
margin: 15px 0;
}
.fund-application-table-container {
margin: 15px 0;
overflow-x: auto;
width: 100%;
}
.fund-application-table-container .el-table {
width: 100% !important;
min-width: 1200px; /* 设置最小宽度确保内容不被压缩 */
}
.fund-application-table {
font-size: 14px;
width: 100%;
}
.implementation-schedule-table-container {
margin: 15px 0;
overflow-x: auto;
width: 100%;
}
.implementation-schedule-table-container .el-table {
width: 100% !important;
min-width: 1400px; /* 设置最小宽度确保内容不被压缩 */
}
.implementation-schedule-table {
font-size: 14px;
width: 100%;
}
.economic-table,
.performance-table,
.fund-application-table {
font-size: 14px;
}
.economic-table .el-input,
.performance-table .el-input,
.fund-application-table .el-input {
font-size: 12px;
}
.amount-warning,
.fund-warning {
margin-top: 10px;
padding: 10px;
background-color: #fdf6ec;
border: 1px solid #f5dab1;
border-radius: 4px;
color: #e6a23c;
}
.fund-info {
margin-top: 10px;
padding: 10px;
background-color: #f0f9ff;
border: 1px solid #b3d8ff;
border-radius: 4px;
color: #409EFF;
}
.section-subtitle {
display: flex;
align-items: center;
font-size: 14px;
font-weight: bold;
color: #606266;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 1px solid #e4e7ed;
}
.section-subtitle i {
margin-right: 8px;
color: #909399;
}
.monthly-detail-table-container {
margin: 15px 0;
overflow-x: auto;
width: 100%;
}
.monthly-detail-table-container .el-table {
width: 100% !important;
min-width: 1200px;
}
.monthly-detail-table {
font-size: 14px;
width: 100%;
}
.file-item {
display: flex;
align-items: center;
margin-bottom: 5px;
font-size: 12px;
padding: 2px 0;
}
.file-item i {
margin-right: 8px;
color: #409EFF;
font-size: 14px;
}
.file-link {
color: #409EFF;
font-size: 12px;
margin-right: 8px;
cursor: pointer;
flex: 1;
text-align: left;
}
.file-link:hover {
color: #66b1ff;
}
.download-btn {
padding: 2px 4px;
font-size: 10px;
color: #909399;
height: auto;
line-height: 1;
}
.download-btn:hover {
color: #409EFF;
}
.file-name {
color: #606266;
word-break: break-all;
}
.note-text {
margin-top: 10px;
color: #666;
font-size: 12px;
}
.form-actions {
margin-top: 30px;
text-align: right;
padding-top: 20px;
border-top: 1px solid #ebeef5;
}
/* 表格样式优化 */
.economic-table >>> .el-table__footer-wrapper .el-table__footer {
font-weight: bold;
}
.performance-table >>> .el-table__cell {
padding: 4px 0;
}
.performance-table >>> .el-input__inner {
height: 28px;
line-height: 28px;
}
.performance-table >>> .el-select .el-input__inner {
height: 28px;
line-height: 28px;
}
/* 必填指标行样式 */
.performance-table >>> .required-row {
background-color: #f0f9ff !important;
border-left: 3px solid #409eff !important;
}
.performance-table >>> .required-row td {
background-color: #f0f9ff !important;
}
.performance-table >>> .required-row:hover td {
background-color: #ecf5ff !important;
}
/* 取消标签左边距 */
.no-label-margin >>> .el-form-item__label {
width: 0 !important;
margin-left: 0 !important;
}
.no-label-margin >>> .el-form-item__content {
margin-left: 0 !important;
}
/* 禁用状态的文本样式 */
.disabled-text {
color: #c0c4cc;
font-style: italic;
}
/* 通用附件列表样式 */
.general-files-list {
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 12px;
background-color: #fafafa;
}
.general-file-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #ebeef5;
font-size: 14px;
}
.general-file-item:last-child {
border-bottom: none;
}
.general-file-item i {
margin-right: 12px;
color: #409EFF;
font-size: 16px;
}
.general-file-item .file-link {
flex: 1;
text-align: left;
font-size: 14px;
}
.general-file-item .download-btn {
padding: 4px 8px;
font-size: 12px;
}
.no-files-text {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: #909399;
font-size: 14px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
}
.no-files-text i {
font-size: 32px;
margin-bottom: 8px;
color: #c0c4cc;
}
</style>